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.