简体   繁体   中英

PHP get array of dates, splitted from a long date range

Referring to my original post ( PHP split data range to get only chunks free ), i've done some implementation, to get better result but still not correct in some scenario.

My goal is to split a original range date to get only free chunks not occupated by activities.

This is my actual script in php:

function date_compare($a, $b)
{
    $t1 = strtotime($a['start']);
    $t2 = strtotime($b['start']);
    return $t1 - $t2;
} 

$fullrange = array(   // <----------------------------------- one month range to split
   "start" => "2019-12-01 00:00:00",
   "end" => "2019-12-31 23:59:59"
);

$result[] = $fullrange;

$array_activities = array( // <--------------------------- activities (busy ranges)
  0 => array(
      "start" => "2019-12-08 09:00:00",
      "end" => "2019-12-08 10:00:00"

  ),
  1 => array(
      "start" => "2019-12-07 09:00:00",
      "end" => "2019-12-07 17:40:00"
  ),
  2 => array(
      "start" => "2019-12-10 10:00:00",
      "end" => "2019-12-15 17:00:00"
  ),
  3 => array(
      "start" => "2019-12-11 08:00:00",
      "end" => "2019-12-17 21:00:00"
  ),
  4 => array(
      "start" => "2019-12-08 08:57:05",
      "end" => "2019-12-08 19:00:00"
  ),
  5 => array(
      "start" => "2019-12-04 10:00:00",
      "end" => "2019-12-05 17:00:00"
  ),
    6 => array(
      "start" => "2019-12-20 10:00:00",
      "end" => "2019-12-31 23:59:59"
  ),
    7 => array(
      "start" => "2019-12-16 10:00:00",
      "end" => "2019-12-31 23:59:59"
  )

);


// reorder array of activities by date start  
usort($array_activities, 'date_compare');

foreach ( $array_activities as $index_1 => $array_activity ) {

foreach ($result as $index_2 => $r) {

    if ( $r['start'] < $array_activity['start'] && $array_activity['start'] < $r['end'] ) {

        $temp = new Datetime($array_activity['start']);
        $temp->modify("-1 second");

        $result[$index_2]['end'] = $temp->format("Y-m-d H:i:s");

        if ( $r['start'] < $array_activity['end'] && $array_activity['end'] < $r['end'] ) {

        $result[] = array("start" => $array_activity['start'], "end" => $r['end']);

        }

    }

}

foreach ($result as $index_2 => $r) {

    if ( $r['start'] < $array_activity['end'] && $array_activity['end'] < $r['end'] ) {

        $temp = new Datetime($array_activity['end']);
        $temp->modify("+1 second");

        $result[$index_2]['start'] = $temp->format("Y-m-d H:i:s");
        $result[$index_2]['end'] = $r['end'];


    }

}

echo '<pre>';
var_dump($result);
echo '</pre>';

}

Expected result:

array(5) {
  [0]=>
  array(2) {
    ["start"]=>
    string(19) "2019-12-01 00:00:00"
    ["end"]=>
    string(19) "2019-12-04 09:59:59"
  }
  [1]=>
  array(2) {
    ["start"]=>
    string(19) "2019-12-05 17:00:01"
    ["end"]=>
    string(19) "2019-12-07 08:59:59"
  }
  [2]=>
  array(2) {
    ["start"]=>
    string(19) "2019-12-07 17:40:01"
    ["end"]=>
    string(19) "2019-12-08 08:57:04"
  }
  [3]=>
  array(2) {
    ["start"]=>
    string(19) "2019-12-08 19:00:01"
    ["end"]=>
    string(19) "2019-12-10 09:59:59"
  }
}

Result get ( WRONG):

array(5) {
  [0]=>
  array(2) {
    ["start"]=>
    string(19) "2019-12-01 00:00:00"
    ["end"]=>
    string(19) "2019-12-04 09:59:59"
  }
  [1]=>
  array(2) {
    ["start"]=>
    string(19) "2019-12-05 17:00:01"
    ["end"]=>
    string(19) "2019-12-07 08:59:59"
  }
  [2]=>
  array(2) {
    ["start"]=>
    string(19) "2019-12-07 17:40:01"
    ["end"]=>
    string(19) "2019-12-08 08:57:04"
  }
  [3]=>
  array(2) {
    ["start"]=>
    string(19) "2019-12-08 19:00:01"
    ["end"]=>
    string(19) "2019-12-10 09:59:59"
  }
  [4]=>
  array(2) {
    ["start"]=>
    string(19) "2019-12-17 21:00:01"
    ["end"]=>
    string(19) "2019-12-20 09:59:59"
  }
}

This probably works on all scenario:

foreach ( $array_activities as $index_1 => $array_activity ) {

    foreach ($result as $index_2 => $r) {

        if ( $r['start'] < $array_activity['start'] && $array_activity['start'] < $r['end'] ) {

            $temp = new Datetime($array_activity['start']);
            $temp->modify("-1 second");

            $result[$index_2]['end'] = $temp->format("Y-m-d H:i:s");

            if ( $r['start'] < $array_activity['end'] && $array_activity['end'] < $r['end'] ) {

            $result[] = array("start" => $array_activity['start'], "end" => $r['end']);

            }

        } else {

            if ( $array_activity['start'] <= $r['start'] && $array_activity['end'] >= $r['end'] ) {
                unset($result[$index_2]);   
            }

        }

    }


    foreach ($result as $index_2 => $r) {

        if ( $r['start'] < $array_activity['end'] && $array_activity['end'] < $r['end'] ) {

            $temp = new Datetime($array_activity['end']);
            $temp->modify("+1 second");

            $result[$index_2]['start'] = $temp->format("Y-m-d H:i:s");
            $result[$index_2]['end'] = $r['end'];


        }

    }



    echo '<pre>';
var_dump($result);
echo '</pre>';


}

The following function should do it (if I didn't miss anything). For each activity, it loops over all free ranges, checks if the activity is overlapping and adjusts / create new ranges accordingly.

Note that ideally, your "period" entries should be objects (with start and end as DateTime properties), so as to provide a stronger signature to the function / reduce the amount of superfluous code.

function getFreeTimeRanges(array $fullRange, array $activities): array
{
  $freeRanges = [[
    'start' => new \DateTime($fullRange['start']), 
    'end' => new \DateTime($fullRange['end'])
  ]];

  foreach ($activities as $activity) {
    $activityStart = new \DateTime($activity['start']);
    $activityEnd = new \DateTime($activity['end']);

    foreach ($freeRanges as &$range) {
      $activityIsOverlapping = $activityStart < $range['end'] 
          && $activityEnd > $range['start'];

      if ($activityIsOverlapping) {
        $activityStartsLater = $activityStart > $range['start'];
        $activityEndsBefore = $activityEnd < $range['end'];

        if ($activityStartsLater) {
          if ($activityEndsBefore) {
            $freeRanges[] = [
              'start' => $activityEnd->modify('+1 second'), 
              'end' => $range['end']
            ];
          }
          $range['end'] = $activityStart->modify('-1 second');
        }
        elseif ($activityEndsBefore) {
          $range['start'] = $activityEnd->modify('+1 second');
        }
      }
    }
  }

  uasort($freeRanges, static function (array $range1, array $range2) {
    return $range1['start'] <=> $range2['start'];
  });

  return array_map(static function ($range) {
    return [
      'start' => $range['start']->format('Y-m-d H:i:s'), 
      'end' => $range['end']->format('Y-m-d H:i:s')
    ];
  }, $freeRanges);
}

$fullRange = ['start' => '2019-12-01 00:00:00', 'end' => '2019-12-31 23:59:59'];
$activities = [
  0 => ['start' => '2019-12-08 09:00:00', 'end' => '2019-12-08 10:00:00'],
  1 => ['start' => '2019-12-07 09:00:00', 'end' => '2019-12-07 17:40:00'],
  2 => ['start' => '2019-12-10 10:00:00', 'end' => '2019-12-15 17:00:00'],
  3 => ['start' => '2019-12-11 08:00:00', 'end' => '2019-12-17 21:00:00'],
  4 => ['start' => '2019-12-08 08:57:05', 'end' => '2019-12-08 19:00:00'],
  5 => ['start' => '2019-12-04 10:00:00', 'end' => '2019-12-05 17:00:00'],
  6 => ['start' => '2019-12-20 10:00:00', 'end' => '2019-12-31 23:59:59'],
  7 => ['start' => '2019-12-16 10:00:00', 'end' => '2019-12-31 23:59:59']
];

print_r(getFreeTimeRanges($fullRange, $activities));

Demo: https://3v4l.org/p8Kvl

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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