Home > Enterprise >  Sort a 2d array based on a column containing AM/PM-formatted time ranges
Sort a 2d array based on a column containing AM/PM-formatted time ranges

Time:11-22

I have a 2D array named $props, the structure is as follows,

$props = [
    ['name' => 'Mathmatics', 'time' => '03:01:PM - 04:50:PM'],
    ['name' => 'History', 'time' => '11:30:AM - 01:30:PM'],
    ['name' => 'French', 'time' => '01:31:PM - 03:00:PM'],
];

I need to sort the array by 'time' key, to get the following result:

[
    ['name' => 'History', 'time' => '11:30:AM - 01:30:PM'],
    ['name' => 'French', 'time' => '01:31:PM - 03:00:PM'],
    ['name' => 'Mathmatics', 'time' => '03:01:PM - 04:50:PM'],
];

I have found a solution with usort, the solution is as follows:

usort($props, function ($a, $b) {
    return $a["time"] - $b["time"];
});

However, this is not working maybe because of special format of my time (but I will have to follow this specific time format.) and shows an error and do nothing to the array. The error:

Notice: A non well formed numeric value encountered in C:\xampp.....

CodePudding user response:

given the format of the date (03:01:PM - 04:50:PM etc.) it needs to be made sort able, while keeping the original value.

from your question it could be seen that only the first part (03:01:PM) is in use for sorting. even if not the case, lets keep it for the example (it can be easily extended).

given the C locale, transforming the time allows to get a string that can be just sorted (binary string order):

          03:01:PM
         /   |    \
    (\d\d):(\d\d):(AM|PM)
       \1     \2     \3
              -->   
             \3\1\2
             PM0301

given a single $time as input, the transformation can be done with a regular expression search and replace:

preg_replace(
    '~^(\d ):(\d ):(AM|PM) - .*$~',
    '\3\1\2',
    $time
);
# ~> "PM0301"

Now to actually sort the $props array, sort the array of all transformed $times and $props:

$times = preg_replace(
    '~^(\d\d):(\d\d):(AM|PM) - .*$~',
    '\3\1\2',
    array_column($props, 'time')
);

array_multisort($times, $props);

Now $props is sorted according to $times:

[
        [
            'name' => 'History',
            'time' => '11:30:AM - 01:30:PM',
        ],
        [
            'name' => 'French',
            'time' => '01:31:PM - 03:00:PM',
        ],
        [
            'name' => 'Mathematics',
            'time' => '03:01:PM - 04:50:PM',
        ],
];

Example on 3v4l.org.

CodePudding user response:

This solution uses usort with a special sorting function. Date objects are created using substr and DateTime::createFromFormat. The spaceship operator is use for comparison.

$arr = [
  ['name' => 'Mathmatics', 'time' => '03:01:PM - 04:50:PM'],
  ['name' => 'History', 'time' => '11:30:AM - 01:30:PM'],
  ['name' => 'French', 'time' => '01:31:PM - 03:00:PM'],
];

usort($arr,function($a,$b){
  $dta = DateTime::createFromFormat('h:i:A',substr($a['time'],0,8));
  $dtb = DateTime::createFromFormat('h:i:A',substr($b['time'],0,8));
  return $dta <=> $dtb;
});

print_r($arr);

Try self: https://3v4l.org/Fq9U5

CodePudding user response:

For an approach with no regex and fewest iterated function calls, use array_map() with iterated calls of createFromFormat(), and use that generated sorting array as the first parameter of array_multisort() to sort the input array.

Note that in the createFromFormat() method demands that all remaining characters are ignored.

Code: (Demo)

array_multisort(
    array_map(
        fn($row) => DateTime::createFromFormat('h:i:A ', $row['time']),
        $props
    ),
    $props
);

var_export($props);
  • Related