简体   繁体   English

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

[英]PHP combine two seperate conflicting date ranges into unique pairs

Set one: 设置一个:

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

Set two: 设置二:

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

What I need this to output is: 我需要输出的是:

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

I attempted to use a function to check for overlaps as such: 我试图使用一个函数来检查重叠:

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

And used a for loop to check for the overlap: 并使用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;
        }
    }
}

The issue that I'm having is when I break a date range out, it doesn't check the new range that was created when looping. 我遇到的问题是当我打破日期范围时,它不会检查循环时创建的新范围。 I do not have the ability to use SQL with this so I must come up with a programmatic solution. 我没有能力使用SQL,所以我必须提出一个程序化的解决方案。 I've tried several different methods including some foreach loops. 我尝试了几种不同的方法,包括一些foreach循环。

The data is received in a date format as show in an array like such: 数据以日期格式接收,如下所示:

$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'));

The second pair would be a separate array, since it may have the same keys. 第二对将是一个单独的数组,因为它可能具有相同的键。

Any guidance or help with this is greatly appreciated. 非常感谢任何指导或帮助。 Date ranges with PHP has very limited resource online. PHP的日期范围在线资源非常有限。

Here is my solution: 这是我的解决方案:

<?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
        )

)
*/

Preparing 准备

$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>';

output 产量

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"
}

Ok. 好。 Now we need loop array $starts : for each start find closest end that more then start . 现在我们需要循环数组$starts :对于每个start找到最接近的end然后start Do it: 这样做:

$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/>';
}

output : 输出

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

What you need. 你需要什么

Note About this line in loops: 注意关于循环中的这一行:

 $ranges[$end] = $start;

We can have this situation: 我们可以遇到这种情况:

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

But it's wrong. 但这是错的。 Need only last range 2014-04-05 To 2014-05-02 . 仅需最后一班2014-04-05 To 2014-05-02 And line: 并且行:

 $ranges[$end] = $start;

override value with same key=> finally will be set proper 2014-04-05 to key 2014-05-02 . 使用相同的键覆盖值=>最后将设置正确2014-04-05至关键2014-05-02

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

This method is originally designed to merge any kind on numeric ranges, timestamps included and supports any kind of overlaps. 此方法最初设计为合并任何类型的数字范围,包括时间戳并支持任何类型的重叠。 It takes an array of objects in input, containing "from" and "to" keys which is customizable. 它在输入中包含一组对象,包含可自定义的“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;
}

Example : 示例:

$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,
    ],
];

Result : 结果:

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
        )

)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM