Home > Back-end >  Sort dates before today in ascending order and after today in descending order
Sort dates before today in ascending order and after today in descending order

Time:06-03

I have the following array

$array = [
    '2022-06-30', 
    '2022-04-20', 
    '2021-04-07',
    '2022-09-09', 
    '2022-03-08',
    '2021-08-07', 
    '2022-10-12'
];

and I want to sort every date in the past ASC and every date in the future DESC.

How can I do this?

With "today" being 2022-06-02, my expected result:

array (
  0 => '2021-04-07',  // before today, ASC
  1 => '2021-08-07',  // before today, ASC
  2 => '2022-03-08',  // before today, ASC
  3 => '2022-04-20',  // before today, ASC
  4 => '2022-10-12',  // after today, DESC
  5 => '2022-09-09',  // after today, DESC
  6 => '2022-06-30',  // after today, DESC
)

CodePudding user response:

This is a job for usort() with a comparison function you provide.

Your comparison function gets called like cmp($a, $b). $a and $b are two items from your array. If $a goes before $b in the output, you return -1. If $a goes after $b, return 1. If they're the same return 0. So, you'll need a comparision function implementing your specific before / after logic.

The usort() function calls your comparison function whenever it needs to compare one date with another to do your sort. It calls your function O(n log(n)) times with different pairs of values.

Something like this? NOT, repeat NOT, debugged.

$nowTimeForMySort = time();

function cmpDates ($a, $b) {
    global $nowTimeForMySort;
    /* equal times, return 0 */
    if ($a == $b) return 0;

    $aTime = strtotime($a);
    $bTime = strtotime($b);

    /* if one date is in the past and the other in the future,
     * always put the date in the past before the date in the future. */
    if ($aTime <= $nowTimeForMySort && $bTime  > $nowTimeForMySort) return -1;
    if ($aTime >  $nowTimeForMySort && $bTime <= $nowTimeForMySort) return 1;

    /* when we get here, both dates are either past or future. */
    if ($aTime <= $nowTimeForMySort) {
       /* past dates: ascending */
       if ($aTime < $bTime) return -1;
       if ($aTime > $bTime) return 1;
    } else {
       /* future dates: descending */
       if ($aTime < $bTime) return 1; 
       if ($aTime > $bTime) return -1;
    }
    return 0;
}

Then use that function in usort() like this.

usort($a, 'cmpDates');

Pro tip: You must be absolutely sure that your comparison function's output depends only, deterministically, on its two inputs and not on anything that might change. That's why this example stuffs the current time into a global variable: we don't want the time to change while the sort is in progress or the results might be very strange.

Pro tip Some languages' comparison functions allow the return of negative / zero / positive numbers. php's requires you to return -1, 0, 1.

CodePudding user response:

Your logic requires that sorting DESC should only occur when both dates are greater than today. Inside the custom callback of usort() use a ternary condition to deliver the correct multiplier to adjust the sorting direction.

Code: (Demo)

$array = [
    '2022-06-30', 
    '2022-04-20', 
    '2021-04-07',
    '2022-09-09', 
    '2022-03-08',
    '2021-08-07', 
    '2022-10-12'
];

$today = '2022-06-02'; // date('Y-m-d');

usort($array, fn($a, $b) => ($a > $today && $b > $today ? -1 : 1) * ($a <=> $b));
var_export($array);

Output:

array (
  0 => '2021-04-07',
  1 => '2021-08-07',
  2 => '2022-03-08',
  3 => '2022-04-20',
  4 => '2022-10-12',
  5 => '2022-09-09',
  6 => '2022-06-30',
)

A more concise, but less efficient approach could look like this: (Demo)

usort($array, fn($a, $b) => (min($a, $b) > $today ? -1 : 1) * ($a <=> $b));

CodePudding user response:

Interesting use-case. For this, after the custom sort, the first portion will have past dates in ascending order and second portion of the array will have present/future dates in decreasing order. For this, you can use usort as below:

  • If the 2 dates are present/future dates, compare and return the values safely with greater date coming first than the smaller date.
  • If the 2 dates doesn't satisfy the above step, compare them as is and return the values.

Snippet:

<?php

$today_dt = DateTime::createFromFormat('Y-m-d H:i:s', date("Y-m-d") . " 00:00:00");

usort($array, function($a, $b) use ($today_dt){
    $d1 =  DateTime::createFromFormat('Y-m-d H:i:s', $a . " 00:00:00");
    $d2 =  DateTime::createFromFormat('Y-m-d H:i:s', $b . " 00:00:00");
    
    if($d1 >= $today_dt && $d2 >= $today_dt){
        return $d2 <=> $d1; // DESC
    }
    
    return $d1 <=> $d2; // ASC
});

Online Demo

Update:

As mentioned by @IMSoP in the comments, you can make the code more concise without having to manually attach the midnight time string. More info

<?php

$array = ['2022-09-09', '2022-06-30', '2022-04-20', '2021-04-07', '2022-03-08', '2021-08-07', '2022-10-12'];

$today_dt = new DateTime('today midnight');

usort($array, function($a, $b) use ($today_dt){
    $d1 =  DateTime::createFromFormat('!Y-m-d', $a);
    $d2 =  DateTime::createFromFormat('!Y-m-d', $b);
    if($d1 >= $today_dt && $d2 >= $today_dt){
        return $d2 <=> $d1; // DESC
    }
    return $d1 <=> $d2; // ASC
});

print_r($array);

Online Demo

  • Related