Tested code on PHP 5.3.18. later version where everything is functions and includes a sort and reduction to a simple output array
Download the source code for the updated version with the sort function.
demonstration of the code shown below at viper-7
Required: Given two events for the same day, one specific to the date and one the repeats on the day, then the event with specific date must be used.
Check the time of the events for any day so that only non-overlapping events are accepted and preference is given to 'run-once' events.
Assumptions: the input is for one week. The order of the events in the input is not important.
The output array will be dependent on the order of the input. i.e. it is not certain to be in day order.
The time of the events must be taken into account when adding events.
The algorithm:
There are lots of comments in the code explaining the actual checks performed. It is not as simple as i thought initially.
Run Once Event: This must not overlap with any other event for the day and is added over any similar 'repeat' event for the day.
repeated events: only add to output if output day / time is empty. These will get overwritten by 'specific date' events for the same day / time.
Code:
/*
* Hold a list of events by day.
* Each 'day' will be a list of non-overlapping events.
*/
$weekEvents = array(); // output events here
// process each event which is 'day' and 'time'...
foreach($events as $newEvent) {
$newEventIsRunOnce = false; // make testing easier to read later.
// add a 'day' entry to run_once events
if (!empty($newEvent['date'])) {
$dt = DateTime::createFromFormat('d M Y', $newEvent['date']);
$day = $dt->format('l');
$newEvent = array_merge($newEvent, array('day' => $day));
$newEventIsRunOnce = true;
}
// now see if it can be added to the event list...
if (!isset($weekEvents[$newEvent['day']])) { // nothing for today so add
$weekEvents[$newEvent['day']][] = $newEvent;
continue; // do the next event...
}
// now check whether the 'newEvent' overlaps with any events for today in the list.
// 1) may need to replace the entry with the curEntry with the new one
// 2) it may overlap more than one entry!
$overlapCount = 0;
$overlapCurEntryIdx = -1;
$overlapCurIsRunOnce = false; // makes testing easier to read later
foreach ($weekEvents[$newEvent['day']] as $idx => $curEvent) {
if (timeRangeOverlap($curEvent['start'], $curEvent['end'],
$newEvent['start'], $newEvent['end'])) {
$overlapCount++;
$overlapCurEntryIdx = $idx;
$overlapCurIsRunOnce = !empty($curEvent['date']);
}
}
// now check to see if overlaps any
if ($overlapCount === 0) { // ok to add
$weekEvents[$newEvent['day']][] = $newEvent;
continue; // do the next event...
}
// now check to see if overlaps and what type it overlaps with...
if ($overlapCount === 1) { // only overlaps one event
if (!$overlapCurIsRunOnce && $newEventIsRunOnce) {
$weekEvents[$newEvent['day']][$overlapCurEntryIdx] = $newEvent;
}
continue; // do the next event...
}
}
echo '<pre>';
print_r($weekEvents);
Time Range Overlap Function:
/*
* need to calculate whether two time ranges overlap.
* A time will always be entered as hh:MM in 24 hour format.
*
* This is not the most efficient routine but is easy to check that it works
*/
function timeRangeOverlap($r1Start, $r1End, $r2Start, $r2End)
{
// convert all to times that can be compared easily.
$r1Start = strtotime($r1Start); $r1End = strtotime($r1End);
$r2Start = strtotime($r2Start); $r2End = strtotime($r2End);
// order them by earliest start time so i can easily see that it works.
// $r1 will always contain the earliest start time
if ($r1Start <= $r2Start) {
$r1 = array('s' => $r1Start, 'e' => $r1End);
$r2 = array('s' => $r2Start, 'e' => $r2End);
}
else {
$r1 = array('s' => $r2Start, 'e' => $r2End);
$r2 = array('s' => $r1Start, 'e' => $r1End);
}
// ensure they do not overlap
// in words: r1 ends before r2 starts or r1 starts after r2 ends
if ($r1['e'] <= $r2['s'] || $r1['s'] >= $r2['e']) {
return false;
}
return true;
}
Test data:
$events = Array(
Array('start' => '20:00', 'end' => '21:00', 'title' => 'event1: run_once',
'date' => '22 Jun 2014'),
Array('start' => '20:00', 'end' => '22:00', 'title' => 'event1: repeat',
'day' => 'Sunday'),
Array('start' => '10:00', 'end' => '12:00', 'title' => 'event2 : repeat',
'day' => 'Sunday'),
Array('start' => '9:00', 'end' => '11:00', 'title' => 'event2 : run_once',
'date' => '22 Jun 2014'),
Array('start' => '15:00', 'end' => '17:00', 'title' => 'event3 : repeat',
'day' => 'Sunday'),
);