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的日期范围在线资源非常有限。

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

此方法最初设计用于合并任何类型的数字范围、包括的时间戳,并支持任何类型的重叠。它在输入中接受一个对象数组,其中包含可自定义的"from"answers"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
        )
)

这是我的解决方案:

<?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,找到比start更接近的end。做到:

$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