PHP combine two seperate conflicting date ranges into unique pairs

后端 未结 3 402
陌清茗
陌清茗 2021-01-20 16:05

Set one:

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

Set two:

  1. 2014-04-05 To 2014-05-02
  2. 2014-05-03 To 2014
相关标签:
3条回答
  • 2021-01-20 16:36

    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
            )
    
    )
    */
    
    0 讨论(0)
  • 2021-01-20 16:45

    Usage : $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.

    
    /**
     * @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
            )
    
    )
    
    
    0 讨论(0)
  • 2021-01-20 16:53

    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. 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. And line:

     $ranges[$end] = $start;
    

    override value with same key=> finally will be set proper 2014-04-05 to key 2014-05-02.

    0 讨论(0)
提交回复
热议问题