将今天之前的日期按升序排序,今天之后的日期按降序排序

Sort dates before today in ascending order and after today in descending order

我有以下数组

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

我想对过去的 ASC 中的每个日期进行排序 以及未来 DESC 中的每个日期。

我该怎么做?

“今天”为 2022-06-02,我的预期结果:

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
)

有趣use-case。为此,在自定义排序之后,第一部分将以升序排列过去的日期,而数组的第二部分将以降序排列 present/future 日期。为此,您可以使用 usort,如下所示:

  • 如果 2 个日期是 present/future 日期,比较并 return 安全地比较值,较大的日期先于较小的日期。
  • 如果 2 个日期不满足上述步骤,则将它们按原样与 return 值进行比较。

片段:

<?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

更新:

正如@IMSoP 在评论中提到的,您可以使代码更简洁,而无需手动附加午夜时间字符串。 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

这是一份 usort() 的工作,带有您提供的比较功能。

您的比较函数被调用为 cmp($a, $b)。 $a 和 $b 是数组中的两项。如果 $a 在输出中位于 $b 之前,则您 return -1。如果 $a 在 $b 之后,return 1. 如果它们相同 return 0. 所以,你需要一个比较函数来实现你特定的前后逻辑。

只要需要将一个日期与另一个日期进行比较以进行排序,usort() 函数就会调用您的比较函数。它使用不同的值对调用您的函数 O(n log(n)) 次。

是这样的吗? NOT,重复 NOT,已调试。

$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;
}

然后像这样在 usort() 中使用该函数。

usort($a, 'cmpDates');

专业提示:您必须绝对确定您的比较函数的输出确定性地仅取决于它的两个输入,而不取决于任何可能改变的东西。这就是此示例将当前时间填充到全局变量中的原因:我们不希望时间在排序过程中发生变化,否则结果可能会很奇怪。

专业提示 某些语言的比较函数允许 return 负数/零数/正数。 php 要求您 return -1, 0, 1.

您的逻辑要求仅当两个日期都大于今天时才应按 DESC 排序。在 usort() 的自定义回调中使用三元条件传递正确的乘数来调整排序方向。

代码:(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);

输出:

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',
)

更简洁但效率较低的方法可能如下所示:(Demo)

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