在一组数据中检查连续的日期并返回范围

7
我有一些以Y-m-d格式表示的日期数组,其中可能包含十个相隔一天的日期。例如,以下是完整的日期集合:
2011-01-01, 2011-01-02, 2011-01-03, 2011-01-04, 2011-01-05, 2011-01-06, 2011-01-07, 2011-01-08, 2011-01-09, 2011-01-10
从该集合创建的数组可以是任意组合的日期--所有日期、一个日期、一些连续的日期、全部连续的日期等。
目前,我将它们输出为它们返回的形式。例如,这里是可能的结果:
2011-01-02
2011-01-03
2011-01-04
2011-01-08
(实际输出的内容更像“星期五,1月2日……”,但我们将保持简单的日期字符串)
我想将其压缩,使得如果有三个或更多连续的日期,则它们将成为一个范围,例如上面的例子将变为:
2011-01-02 至 2011-01-04
2011-01-08
最终会变成:
周日,1月2日 - 周二,1月4日
周六,1月8日
是否有一种方法可以循环检查时间差异、创建范围的开始时间和结束时间,然后收集零散的日期?

此问题中提供的所有日期都是连续的,请问您具体要求什么? - ajreal
可能的日期列表是连续的。返回的实际日期可以是该日期列表中的任意组合(请参见我的“可能结果”示例)。 - Kerri
2个回答

14

如果假设您正在使用5.3版本,并且日期按时间顺序排序,您可以将每个日期转换为DateTime对象(如果尚未这样做),然后使用DateTime::diff()在数组上进行迭代,生成一个DateInterval对象,您可以使用它来比较迭代中的当前日期与上一个日期。您可以将连续的日期分组到子数组中,并使用shift()pop()获取该子数组中的第一个和最后一个日期。

编辑

我思考了一下。以下是一个相当简单粗糙的实现,但应该可以工作:

// assuming a chronologically
// ordered array of DateTime objects 

$dates = array(
    new DateTime('2010-12-30'), 
    new DateTime('2011-01-01'), 
    new DateTime('2011-01-02'), 
    new DateTime('2011-01-03'), 
    new DateTime('2011-01-06'), 
    new DateTime('2011-01-07'), 
    new DateTime('2011-01-10'),
);

// process the array

$lastDate = null;
$ranges = array();
$currentRange = array();

foreach ($dates as $date) {    

    if (null === $lastDate) {
        $currentRange[] = $date;
    } else {

        // get the DateInterval object
        $interval = $date->diff($lastDate);

        // DateInterval has properties for 
        // days, weeks. months etc. You should 
        // implement some more robust conditions here to 
        // make sure all you're not getting false matches
        // for diffs like a month and a day, a year and 
        // a day and so on...

        if ($interval->days === 1) {
            // add this date to the current range
            $currentRange[] = $date;    
        } else {
            // store the old range and start anew
            $ranges[] = $currentRange;
            $currentRange = array($date);
        }
    }

    // end of iteration... 
    // this date is now the last date     
    $lastDate = $date;
}

// messy... 
$ranges[] = $currentRange;

// print dates

foreach ($ranges as $range) {

    // there'll always be one array element, so 
    // shift that off and create a string from the date object 
    $startDate = array_shift($range);
    $str = sprintf('%s', $startDate->format('D j M'));

    // if there are still elements in $range
    // then this is a range. pop off the last 
    // element, do the same as above and concatenate
    if (count($range)) {
        $endDate = array_pop($range);
        $str .= sprintf(' to %s', $endDate->format('D j M'));
    }

    echo "<p>$str</p>";
}

输出:

Thu 30 Dec
Sat 1 Jan to Mon 3 Jan
Thu 6 Jan to Fri 7 Jan
Mon 10 Jan

2
谢谢!我几周前更新到了5.3版本,主要是为了使用所有新的DateTime功能。我会研究一下并尝试一下,并回报结果。 - Kerri
@Darragh,有没有什么办法可以将两个日期包含在同一个范围内?我的设置是关于班次的,有时一天会有两个班次。因此,在我的日期数组中,一个日期可能会出现两次,并且应该包含在一个日期范围内,但是使用您的代码标记为不同的日期。先感谢您! - chocolata
嗨@maartenmachiels - 我猜对这个问题的超快速回答可能是基于小时而不是(或者甚至除了)天来进行间隔比较。目前代码只在天数上进行比较 - 请参见以下代码行 if ($interval->days === 1) { // etc. - Darragh Enright
1
这对于在数组中查找连续天数非常有效(比我需要的稍微多了一点)。在 PHP 7.4 中运行无误。 - Jason
谢谢@Jason - 哇,这是一个古老的答案,很高兴看到它仍然有效:D - Darragh Enright

0
这个任务可以/应该只用一个循环来完成。我将利用一个自定义函数来处理格式化和有条件呈现的范围表达式。在确定范围时,永远没有理由“暂时”保存超过两个日期,因为如果在一个组中有三个或更多日期,只会使用第一个和最后一个。
脚本将遵守提问者的要求,处理重复日期,但最终假设输入数据保证按升序预排序。
分解如下:
# loop each date 
    # declare the date as a Date Time class object
    # if a group has been started already...
        # if the new date is more than one day ahead of the last encountered date...
            # format the grouped data and save to the result array 
            # clear the grouping array and add the new date as the first date in the new group.
        # otherwise, add the new date to new group (because it belongs there) as the end of range value. 
    # otherwise, start a group and use the new date as the starting value.
# after iterating all of the input data, if the temp array is holding any data, format the grouped data and save to the result array.

代码:(演示)
$dates = [
    '2010-12-30',
    '2011-01-01',
    '2011-01-02',
    '2011-01-03',
    '2011-01-06',
    '2011-01-06',
    '2011-01-07',
    '2011-01-10',
];

function formatRange(object $start, ?object $end = null): string
{
    $last = $end ?? $start;
    return sprintf(
        '%s%s',
        $start->format('l j M'),
        $last === $start
            ? ''
            : ' to ' . $last->format('l j M')
    );
}

$result = [];
foreach ($dates as $date) {
    $obj = new Datetime($date);
    if (isset($temp['start'])) {
        if ($obj->diff($temp['end'] ?? $temp['start'])->days > 1) {
            $result[] = formatRange(...$temp);
            $temp = ['start' => $obj];
        } else {
            $temp['end'] = $obj;
        }
    } else {
        $temp['start'] = $obj;
    }
}
if (isset($temp)) {
    $result[] = formatRange(...$temp);
}
var_export($result);

输出:

array (
  0 => 'Thursday 30 Dec',
  1 => 'Saturday 1 Jan to Monday 3 Jan',
  2 => 'Thursday 6 Jan to Friday 7 Jan',
  3 => 'Monday 10 Jan',
)

我其他的回答也涉及到了对分组日期/时间数据进行相关合并的问题:


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