将一个日期数组分成连续的块

7

可能是重复问题:
检查集合中连续的日期并作为范围返回

我有一个从MySQL查询获得的日期数组。 我需要将数组分成多个数组,以便每个数组中的日期是连续的。

所以,如果我开始

$datearray = array("2013-05-05", "2013-05-06", "2013-05-07", "2013-05-08", "2013-06-19", "2013-06-20", "2013-06-21");

我需要能够将其分割为:
$firstdatearray = array("2013-05-05", "2013-05-06", "2013-05-07", "2013-05-08");
$seconddatearray = array("2013-06-29", "2013-06-30", "2013-07-01");

最终我将能够打印

3月5日至8日、6月29日至7月1日

我该怎么做呢?我不知道从哪里开始。


3
你不知道从哪里开始?我敢打赌你至少有一个可以做的想法。 - hakre
所以你应该尝试着去做一些事情,当你遇到具体的实现问题时再提出问题。 - Mike Brant
1
类似的,适应性强:https://dev59.com/al7Va4cB1Zd3GeqPHCvH - goat
7个回答

5

这是完整的可用答案。(欢迎使用!)

您需要循环遍历$datearray中的每个值。

<?php

$datearray = array("2013-05-05", "2013-05-06", "2013-05-07", "2013-05-08", "2013-06-19", "2013-06-20", "2013-06-21");
asort($datearray);
$resultArray = array();
$index = -1;
$last = 0;
$out = "";

foreach ($datearray as $date) {
    $ts = strtotime($date);
    if (false !== $ts) {
        $diff = $ts - $last;

        if ($diff > 86400) {
            $index = $index + 1;
            $resultArray[$index][] = $date;
        } elseif ($diff > 0) {
            $resultArray[$index][] = $date;
        } else {
            // Error! dates are not in order from small to large
        }
        $last = $ts;
    }
}

foreach ($resultArray as $a) {
    if (count($a) > 1) {
        $firstDate = $a[0];
        $firstDateBits = explode('-',$firstDate);
        $lastDate = $a[count($a)-1];
        $lastDateBits = explode('-',$lastDate);

        if ($firstDateBits[1] === $lastDateBits[1]) {
            $out .= intval($firstDateBits[2]) . '-' . intval($lastDateBits[2]) . ' ' . date("M",strtotime($firstDate)) . ', ';  
        } else {
            $out .= date("M d",strtotime($firstDate)) . '-' . date("M d",strtotime($lastDate)) . ', ';  
        }
    }
}

这是输出结果:
5-8 May, 19-21 Jun

谢谢mrbinky。看起来很有前途。我正在努力将其完全开发成我的应用程序。等我有更多消息再回来。 - TrapezeArtist
那基本上是“完整的工作答案”。只有很少的细节需要调整就可以运行: 一开始我没有注意到 $out = "" 这行,所以我自己重新发明了这部分。 在最后的 $out .= 行中使用 date("M d" 等格式会导致日期颠倒并且始终为两位字符。改为使用 date("j M" 等格式解决了这个问题。 日期列表以逗号结尾,所以我使用 rtrim 来清理它。 - TrapezeArtist
感谢@mrbinky3000提供了非常有帮助的解决方案。这比我预期的要容易得多。还要感谢其他所有花时间查看它的人。我相信这里还有其他可行的解决方案,但这是我采用的一个。 - TrapezeArtist
一旦我开始使用这个解决方案,我发现了另一个问题。它不能适应单个日期不连续的情况。我只需要在if...else例程中添加额外的代码来考虑这一点。可以在www.littlehotels.co.uk/spain/noves.php上查看代码的运行情况。点击“房间价格”选项卡。 - TrapezeArtist

1

假设使用 PHP 5.3 或更高版本。

您可以利用 DateTime、DateInterval 和一些算法工作。

// create the same array but with DateTime objects to represent the dates
$dt_array = array_map(function ($e) { return new DateTime($e); }, $datearray);


$intervals = array();
$len_dt_array_m1 = count($dt_array) - 1;
if ($len_dt_array_m1 >= 0) {
    $current_interval = &$intervals[];
}

// now we traverse the array left to right.
// if the difference between the current date and the next is not +1 day, we assume a new interval has begun.
for ($i = 0; $i < $len_dt_array_m1; ++$i) {
    $current_dt = $dt_array[$i];
    $next_dt = $dt_array[$i+1];
    $diff = $current_dt->diff($next_dt);
    $current_interval[] = $current_dt->format('Y-m-d');
    if ($diff->days != 1 || $diff->invert != 0) {
        $current_interval = &$intervals[];
    }
}

// add last dt to the interval
if ($len_dt_array_m1 >= 0) {
    $current_interval[] = $dt_array[$len_dt_array_m1]->format('Y-m-d');
}

print_r($intervals);

0

你手头的所有日期都在同一年内。你可以将每个日期转换为该年份中的天数。

然后,你会得到一个数字数组。对于该数组,你可以按照这里概述的方式进行操作:

另一种方法是基于前一个日期计算下一个日期,然后将其与数组中的下一个日期进行比较。如果两者相等,则扩展当前时间跨度;如果不相等,则创建新的时间跨度。然后将数组减少到时间跨度:

$consecutiveDates = function ($result, $date) {

    if ($count = count($result)) {
        $next = clone $result[$count - 1][1];
        $next->add(new DateInterval('P1D'));
    }

    $date = new DateTime($date);

    if (!$count || $date != $next) {
        $result[$count++] = [$date];
    }

    $result[$count - 1][1] = $date;

    return $result;
};

$reduced = array_reduce($datearray, $consecutiveDates, []);

这将为您的数组产生以下结果:
Array
(
    [0] => Array
        (
            [0] => DateTime Object
                (
                    [date] => 2013-05-05 00:00:00
                    [timezone_type] => 3
                    [timezone] => Europe/London
                )

            [1] => DateTime Object
                (
                    [date] => 2013-05-08 00:00:00
                    [timezone_type] => 3
                    [timezone] => Europe/London
                )

        )

    [1] => Array
        (
            [0] => DateTime Object
                (
                    [date] => 2013-06-19 00:00:00
                    [timezone_type] => 3
                    [timezone] => Europe/London
                )

            [1] => DateTime Object
                (
                    [date] => 2013-06-21 00:00:00
                    [timezone_type] => 3
                    [timezone] => Europe/London
                )

        )

)

这两个条目现在可以使用映射函数轻松地映射到您的输出风格中:
$consecutiveDatesString = function ($pair) {

    list($start, $end) = $pair;

    return $start == $end
        ? $start->format('j M')
        : $start->format($start->format('M') != $end->format('M') ? 'j M' : 'j')
            . $end->format(' - j M');
};

$consecutiveDatesStrings = array_map($consecutiveDatesString, $reduced);

这就导致了更紧凑的结果:
Array
(
    [0] => 5 - 8 May
    [1] => 19 - 21 Jun
)

最后,要打印逗号分隔的内容:

echo implode(', ', $consecutiveDatesStrings), "\n";

这会给出,猜猜看:

5 - 8 May, 19 - 21 Jun

0

这个会起作用:

$datearray = array("2013-05-05", "2013-05-06", "2013-05-07", "2013-05-08", "2013-06-19", "2013-06-20", "2013-06-21");
$current_date_array_index = 0;
$dates = array();

for($i=0,$c=count($datearray);$i<$c;$i++){
    if(strtotime($dates[$current_date_array_index][count($dates[$current_date_array_index])-1]." +1 day") != strtotime($datearray[$i])){
        $current_date_array_index++;
    }
    $dates[$current_date_array_index][] = $datearray[$i];
}

foreach($dates as $date){
    if(count($date) == 1){
        $output[] = date('j M',strtotime($date[0]));
    }else{
        $output[] = date('j',strtotime($date[0]))." - ".date('j M',strtotime($date[count($date)-1]));
    }
}

echo implode($output,", "); // output: 5 - 8 May, 19 - 21 Jun

值得注意的是,如果日期跨越了月份,它会显示类似于“29-5 Mar”的内容 - 第29天未定义,因此我建议您使用“5 Mar-8 Mar”。请注意。

0
尝试像这样做:

$prev = null;
$groups = array();
$idx = 0;

foreach($datearray as $day)
{
    if ($prev == null)
    {
        $prev = $day;
    } else {
        $currentDay = strtotime($day);
        $prevDay =strtotime($prev);
        if ($currentDay + 86400 > $prevDay)
        {
        // New Array
        $idx++;
    }

    $groups[$idx][] = $day;

    }
}

// TODO : Sort the array into date order

注意:我尚未测试上述代码,因此可能包含拼写错误。

我只是在数组中迭代每一天,并确定它是否与前一天连续。如果是,则将其添加到同一组中;如果不是,则将其添加到不同的数组索引中。之后,您可能需要使用某种php排序函数对日期进行排序。然后迭代数组组以确定日期范围。


0
class Date_Array_Split
{
    private $date_arrays = array();

    public function __construct( Array $dates, $split_into = 2 )
    {
        // Sort the array
        asort( $dates );

        // Calculate the array size to pass to array_chunk
        $size = ceil( count( $dates ) / $split_into );

        // Break up the array into pieces
        $dates = array_chunk( $dates, $size );

        $this->date_arrays = $dates;
    }

    public function __toString()
    {
        $string = array();  // Oh, the irony!

        // Iterate through the chunks
        foreach( $this->date_arrays as $date_array )
        {
            // Here's the oldest date in the chunk
            $date_min = min( $date_array );

            // Here's the newest date in the chunk
            $date_max = max( $date_array );

            // Default format for output
            $date_min_format = 'j M';

            // Accomodate the slight formatting change
            if( date( 'my', strtotime( $date_min ) ) === date( 'my', strtotime( $date_max ) ) )
            {
                // Moth and year are the same, omit the month
                $date_min_format = 'j';
            }

            // String-i-fy the dates for output
            $date_min_string = date( $date_min_format, strtotime( $date_min ) );
            $date_max_string = date( 'j M', strtotime( $date_max ) );

            // Add them to the output array
            $string[] = sprintf( '%s - %s', $date_min_string, $date_max_string );
        }

        // Return the output array separated by commas
        return implode( $string, ", " );
    }
}

$dates_array = array(
    "2013-05-05", 
    "2013-06-20", 
    "2013-05-07", 
    "2013-05-08", 
    "2014-05-09", 
    "2013-05-09", 
    "2013-06-19", 
    "2013-06-21"
);

$dates = new Date_Array_Split( $dates_array );

echo( $dates );

输出:5月9日至5月9日,6月19日至5月9日

请注意,这样可以正确处理年份。这就是为什么输出看起来有点奇怪。可能需要在输出中考虑到这一点。


0

我无法帮你实现具体代码,但你可以像这样做:

  • 找到范围的起始点和终止点
  • 计算中间的步数(1步=1天)
  • 使用mktime(或DateTime或strftime)根据起始日期+步长计算结束日期)
  • 按您喜欢的方式打印出来。

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