PHP 将两个独立的相互冲突的日期范围组合成唯一的对

PHP combine two seperate conflicting date ranges into unique pairs

第一个:

  1. 2014-04-05 至 2014-06-27
  2. 2014-06-28 至 2014-10-19

设置二:

  1. 2014-04-05 至 2014-05-02
  2. 2014-05-03 至 2014-05-31
  3. 2014-06-01 至 2014-10-19

我需要输出的是:

  1. 2014-04-05 至 2014-05-02
  2. 2014-05-03 至 2014-05-31
  3. 2014-06-01 至 2014-06-27
  4. 2014-06-28 至 2014-10-19

我尝试使用一个函数来检查重叠:

!($lhs['RecordOnset'] > $rhs['RecordOffset'] || $lhs['RecordOffset'] < $rhs['RecordOnset'])

并使用 for 循环检查重叠:

for($i = 1; $i < sizeof($arr1); $i++) {
    for($j = 1; $j < sizeof($arr2); $j++) {
        $record = $arr1[$i];
        if($result = $this->intersects($arr1[$i], $arr2[$j])) {
            // $result;
        }
    }
}

我遇到的问题是当我打破日期范围时,它不会检查循环时创建的新范围。我无法对此使用 SQL,因此我必须想出一个编程解决方案。我尝试了几种不同的方法,包括一些 foreach 循环。

数据以日期格式接收,如数组所示:

$arr1 = array(array('start'=>'04/05/2014', 'end'=> '2014-06-27'), array('start'=>'2014-06-28', 'end'=> '2014-10-19'));

$arr2 = array(array('start'=>'04/05/2014', 'end'=> '2014-05-02'), array('start'=>'2014-05-03', 'end'=> '2014-05-31'),array('start'=>'2014-06-01', 'end'=> '2014-10-19'));

第二对将是一个单独的数组,因为它可能具有相同的键。

非常感谢任何指导或帮助。 PHP 的日期范围在线资源非常有限。

这是我的解决方案:

<?php
$array1 = array(
    array('s'=>'2014-04-05','e'=>'2014-06-27'),
    array('s'=>'2014-06-28','e'=>'2014-10-19')
);
$array2 = array(
    array('s'=>'2014-04-05','e'=>'2014-05-02'),
    array('s'=>'2014-05-03','e'=>'2014-05-31'),
    array('s'=>'2014-06-01','e'=>'2014-10-19')
);

//merge arrays together
$merged_array = array_merge($array1,$array2);

//filter out duplicate start dates
$filtered_array = array();
foreach($merged_array as $k=>$v){
    if(!isset($filtered_array[ $v['s'] ] )){
        $filtered_array[ $v['s'] ] = $v;
    }

    //if the end date is before the currently saved end date (for this start date) then use it
    if( strtotime($v['e']) < strtotime($filtered_array[ $v['s'] ]['e']) ){
        $filtered_array[ $v['s'] ] = $v;
    }
}

//reset the array to zero based
$filtered_array = array_values($filtered_array);

//sort the array by start date
$tmp = array();
foreach($filtered_array as $k=>$v){
    $tmp[$k] = $v['s'];
}

array_multisort($tmp,SORT_ASC,$filtered_array);

//end date overlap checking
foreach($filtered_array as $k=>$v){
    //if the end date is after (or equal to) the "next" start date, then make that end date the "yesterday" of the next start date
    if( isset($filtered_array[$k+1]['s']) && strtotime($v['e']) >= strtotime($filtered_array[$k+1]['s'])  ){
        $yesterday = strtotime($filtered_array[$k+1]['s']) - 1;
        $yesterday = date("Y-m-d",$yesterday);
        $filtered_array[$k]['e'] = $yesterday;
    }
}

echo '<pre>',print_r($filtered_array),'</pre>';

/*
Array
(
    [0] => Array
        (
            [s] => 2014-04-05
            [e] => 2014-05-02
        )

    [1] => Array
        (
            [s] => 2014-05-03
            [e] => 2014-05-31
        )

    [2] => Array
        (
            [s] => 2014-06-01
            [e] => 2014-06-27
        )

    [3] => Array
        (
            [s] => 2014-06-28
            [e] => 2014-10-19
        )

)
*/

准备中

$arr1 = array(
  array('start'=>'2014-04-05', 'end'=> '2014-06-27'),
  array('start'=>'2014-06-28', 'end'=> '2014-10-19'),
);

$arr2 = array(
  array('start'=>'2014-04-05', 'end'=> '2014-05-02'),
  array('start'=>'2014-05-03', 'end'=> '2014-05-31'),
  array('start'=>'2014-06-01', 'end'=> '2014-10-21')
);

// merge arrays
$all = array_merge($arr1,$arr2);

// divide start-dates and end-dates into two arrays
$starts = array();
$ends = array();
foreach($all as $date){
    $starts[] = $date['start'];
    $ends[] = $date['end'];
}

// Remove duplicates and "sort ASC"
$starts = array_unique($starts);
natsort($starts);

$ends = array_unique($ends);
natsort($ends);

echo '<pre>';
var_dump($starts,$ends);
echo '</pre>';

输出

array(4) {
    [0]=>
  string(10) "2014-04-05"
    [3]=>
  string(10) "2014-05-03"
    [4]=>
  string(10) "2014-06-01"
    [1]=>
  string(10) "2014-06-28"
}
array(5) {
    [2]=>
  string(10) "2014-05-02"
    [3]=>
  string(10) "2014-05-31"
    [0]=>
  string(10) "2014-06-27"
    [1]=>
  string(10) "2014-10-19"
    [4]=>
  string(10) "2014-10-21"
}

好的。现在我们需要循环数组 $starts:对于每个 start 找到最接近的 end 然后更多 start。这样做:

$ranges = array();

foreach($starts as $start){
    $start_time = strtotime($start);

    foreach($ends as $end){
        $end_time = strtotime($end);
        if ($start_time>$end_time) continue;
        else{
            $ranges[$end] = $start;
            break;
        }
    }
}

// "combine" 
$result = array();    
foreach($ranges as $end=>$start) {
    $result[] = array('start' => $start, 'end' => $end);
}

// print final result
foreach($result as $item){
    echo $item['start'].'  To  '.$item['end'].'<br/>';
}

输出:

2014-04-05 To 2014-05-02
2014-05-03 To 2014-05-31
2014-06-01 To 2014-06-27
2014-06-28 To 2014-10-19

你需要什么。

备注 关于循环中的这一行:

 $ranges[$end] = $start;

我们可以有这样的情况:

2014-04-03 To 2014-05-02
2014-04-04 To 2014-05-02
2014-04-05 To 2014-05-02

但这是错误的。只需要最后一个范围 2014-04-05 To 2014-05-02。和行:

 $ranges[$end] = $start;

用相同的键重写值=> 最终将正确 2014-04-05 设置为键 2014-05-02

用法:$output = mergeRanges($input);

此方法最初设计用于合并任何类型的数字范围,包括时间戳并支持任何类型的重叠。它在输入中获取一个对象数组,其中包含可自定义的 "from" 和 "to" 键。


/**
 * @param        $ranges
 * @param string $keyFrom
 * @param string $keyTo
 *
 * @return array
 */
function mergeRanges($ranges, $keyFrom = 'from', $keyTo = 'to')
{
    // Split from / to values.
    $arrayFrom = [];
    $arrayTo   = [];
    foreach ($ranges as $date)
    {
        $arrayFrom[] = $date->$keyFrom;
        $arrayTo[]   = $date->$keyTo;
    }

    // Sort ASC.
    natsort($arrayFrom);
    natsort($arrayTo);

    $ranges = [];
    // Iterate over start dates.
    foreach ($arrayFrom as $indexFrom => $from)
    {
        // Get previous entry.
        $previousEntry = end($ranges);
        // Find associated default "to" value to "from" one.
        $to = $arrayTo[$indexFrom];

        // If we have a previous entry and "to" is greater than
        // current "from" value.
        if (isset($previousEntry->to) && $from < $previousEntry->to + 1)
        {
            // Do nothing if this range is completely covered
            // by the previous one.
            if ($to > $previousEntry->to)
            {
                // We just change te "to" value of previous range,
                // so we don't create a new entry.
                $previousEntry->to = $to;
            }
        }
        else
        {
            // Create a new range entry.
            $ranges[] = (object) [
                $keyFrom => $from,
                $keyTo   => $to,
            ];
        }
    }

    return $ranges;
}

示例:

$input = [
    // One day.
    (object) [
        'title' => 'One day.',
        'from'  => 1560816000,
        'to'    => 1560902399,
    ],
    // Same day, inner period
    (object) [
        'title' => 'Same day, inner period',
        'from'  => 1560816000 + 1000,
        'to'    => 1560902399 - 1000,
    ],
    // Just before midnight
    (object) [
        'title' => 'Just before midnight',
        'from'  => 1560816000 - 1000,
        'to'    => 1560816000 + 1000,
    ],
    // Just after midnight
    (object) [
        'title' => 'Just after midnight',
        'from'  => 1560902399 - 1000,
        'to'    => 1560902399 + 1000,
    ],
    // Other period before
    (object) [
        'title' => 'Other period before',
        'from'  => 1560902399 - 100000,
        'to'    => 1560902399 - 100000 + 5000,
    ],
    // Other period after
    (object) [
        'title' => 'Other period after',
        'from'  => 1560816000 + 100000,
        'to'    => 1560902399 + 100000 + 5000,
    ],
];

结果:

Array
(
    [0] => Array
        (
            [from] => 2019-06-17 22:13:19
            [to] => 2019-06-17 23:36:39
        )

    [1] => Array
        (
            [from] => 2019-06-18 01:43:20
            [to] => 2019-06-19 02:16:39
        )

    [2] => Array
        (
            [from] => 2019-06-19 05:46:40
            [to] => 2019-06-20 07:09:59
        )

)