按照即将到来的日期排序

3
我是一名有帮助的助手,可以为您翻译文本。
所以,我建立了一个包含各种日期(生日、纪念日和假期)的数组。我想按照接下来发生的日期对数组进行排序,基本上是将十月到九月排序(跨越到下一年)。
因此,如果我的数组是:
$a = ([0]=>"1980-04-14", [1]=>"2007-06-08", 
  [2]=>"2008-12-25", [3]=>"1978-11-03")

我想将其排序,以便排列。
$a = ([0]=>"1978-11-03", [1]=>"2008-12-25", 
  [2]=>"1980-04-14", [3]=>"2007-06-08")

因为现在是十月,所以下一个“事件”会发生在十一月。

我正在尝试使用usort函数,其中我的cmp函数是

function cmp($a, $b)
{
  $a_tmp = split("-", $a);
  $b_tmp = split("-", $b);
  return strcmp($a_tmp[1], $b_tmp[1]);
} 

我不确定如何修改这个以达到我想要的效果。


我喜欢那个日期。显然是打错了,现在已经更正过来了。 - Jack B Nimble
6个回答

3
function relative_year_day($date) {
    $value = date('z', strtotime($date)) - date('z');

    if ($value < 0)
        $value += 365;

    return $value;
}

function cmp($a, $b)
{
    $aValue = relative_year_day($a);
    $bValue = relative_year_day($b);

    if ($aValue == $bValue)
        return 0;

    return ($aValue < $bValue) ? -1 : 1;
}

$a = array("1980-04-14", "2007-06-08",
    "2008-12-25", "1978-11-03");

usort($a, "cmp");

1
我会尝试确定事件的原始年份,然后再加上足够多的整年份以确保该值大于您的参考日期(通常是今天的日期)。或者,可能大于或等于参考日期。然后,您可以按照简单的日期顺序进行排序。
编辑添加:
我对PHP的流利程度不够,但这里有一个Perl解决方案。
#!/bin/perl -w

# Sort sequence of dates by next occurrence of anniversary.
# Today's "birthdays" count as low (will appear first in sequence)

use strict;

my $refdate = "2008-10-05";

my @list = (
    "1980-04-14", "2007-06-08",
    "2008-12-25", "1978-11-03",
    "2008-10-04", "2008-10-05",
    "2008-10-06", "2008-02-29"
);

sub date_on_or_after
{
    my($actdate, $refdate) = @_;
    my($answer) = $actdate;
    if ($actdate lt $refdate)   # String compare OK with ISO8601 format
    {
        my($act_yy, $act_mm, $act_dd) = split /-/, $actdate;
        my($ref_yy, $ref_mm, $ref_dd) = split /-/, $refdate;
        $ref_yy++ if ($act_mm < $ref_mm || ($act_mm == $ref_mm && $act_dd < $ref_dd));
        $answer = "$ref_yy-$act_mm-$act_dd";
    }
    return $answer;
}

sub anniversary_compare
{
    my $r1 = date_on_or_after($a, $refdate);
    my $r2 = date_on_or_after($b, $refdate);
    return $r1 cmp $r2;
}

my @result = sort anniversary_compare @list;

print "Before:\n";
print "* $_\n" foreach (@list);
print "Reference date: $refdate\n";
print "After:\n";
print "* $_\n" foreach (@result);

显然,这并不是非常高效的 - 要使其高效,您需要计算一次 date_on_or_after() 值,然后按这些值排序。 Perl 的比较有点奇怪 - 变量 $a 和 $b 是神奇的,看起来好像毫无头绪。

运行脚本时会产生:

Before:
* 1980-04-14
* 2007-06-08
* 2008-12-25
* 1978-11-03
* 2008-10-04
* 2008-10-05
* 2008-10-06
* 2008-02-29
Reference date: 2008-10-05
After:
* 2008-10-05
* 2008-10-06
* 1978-11-03
* 2008-12-25
* 2008-02-29
* 1980-04-14
* 2007-06-08
* 2008-10-04

请注意,它在很大程度上回避了2月29日会发生什么的问题,因为这样做是“有效的”。基本上,它将生成“日期”2009-02-29,该日期在序列比较中正确。如果数据包括2000-02-28,则2000-02-28的周年纪念日将列在2008-02-29的周年纪念日之前。

我怀疑我的代码也不是很高效。但是我也觉得我的代码没有02-29的bug。 - Jack B Nimble

0

所以我想到的是,只需将小于目标月份的任何月份加上12即可。现在它可以实现。

因此,这就是最终的函数。

function cmp($a, $b)
{
    $a_tmp = explode('-', $a['date']);
    $b_tmp = explode('-', $b['date']);
    if ($a_tmp[1] < date('m')) {
        $a_tmp[1] += 12;
    }
    if ($b_tmp[1] < date('m')) {
        $b_tmp[1] += 12;
    }
    return strcmp($a_tmp[1] . $a_tmp[2], $b_tmp[1] . $b_tmp[2]);
} 

如果您尝试在列表中包含已经过去的某个月份中的日期(例如,如果您包含了2000年10月1日),那么这是行不通的。 - Randy
当其中一个日期是2008年2月29日时会发生什么? - Jonathan Leffler
假设今天是2008年10月5日,您的代码如何处理2008年10月4日、2008年10月5日、2008年10月6日的条目?特别要注意的是,下一个2008年10月4日的庆祝活动比考虑中的所有其他日期都要晚。您还没有定义今天的日期是否算作今年或明年。 - Jonathan Leffler
而且,不要介意 - 我之前评论中的“show”是多余的。 - Jonathan Leffler
有可能会显示10-04,但目前我并不担心。如果需要,在当前日期之后的任何一天上加30即可。目前只是字符串排序,所以像02-29和10-45这样的日期并不重要,只是按顺序排序。 - Jack B Nimble
只需使用一年中的日期('date("z", mktime(0,0,0,$d,$m,$y))')进行比较,您可以忽略月份和年份,不必担心闰年。 - cfeduke

0
使用strtotime()将所有日期转换为时间戳,然后再将它们添加到数组中,这样你就可以按升序(也是按时间顺序)对数组进行排序。现在你所要做的就是处理过去的日期,这可以通过将它们与当前时间戳进行比较来轻松完成。
例如:
for ($i=0; $i<count($a); $i++){
  if ($currentTimestamp > $a[$i]){
    unset($a[$i]);
  }
}

0
没有必要重新发明轮子。如果您不关心键,可以使用这个。
$a = array_combine(array_map('strtotime', $a), $a);
ksort($a);

或者如果你想定义自己的回调函数。

function dateCmp($date1, $date2) {
  return (strtotime($date1) > strtotime($date2))?1:-1;
}

usort($a, 'dateCmp');

如果你想保持键的正确关联,只需调用uasort即可。
uasort($a, 'dateCmp');

我进行了快速的速度检查,回调函数慢了一个数量级。


-1

不要比较字符串,而是使用自1970年以来的秒数(整数):

$date1 = split("-", $a);
$date2 = split("-", $b);
$seconds1 = mktime(0,0,0,$date1[1],$date1[2],$date1[0]);
$seconds2 = mktime(0,0,0,$date2[1],$date2[2],$date2[0]);
// eliminate years
$seconds1 %= 31536000;
$seconds2 %= 31536000;
return $seconds1 - $seconds2;

另外我不懂PHP,但我认为主旨是正确的。

编辑:比较函数被封装以执行比较操作,没有其他作用。为了按照原问题排序列表,请对包含今天日期的数组进行排序,找到数组中的今天日期,然后按位置升序将该位置之前的元素移动到末尾。


嗯,您需要将ticks1和ticks2模31536000来消除年份,但这样做可以正确地工作,我很抱歉。 - cfeduke

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接