0

I have a mixed array made up of events. Example:

 Array
(
    [0] => Array
        (
            [start] => 20:00
            [end] => 21:00
            [title] => test event with a date
            [date] => 22 Jun 2014
        )

    [1] => Array
        (
            [start] => 20:00
            [end] => 22:00
            [title] => test event without a date
            [day] => Sunday
        )

)

Most events are daily (e.g. Sunday). However, some are occasional and have a full date.

If an event with a full date falls on the same day and time as a daily event (as in the example above), I would like the daily event not to show up when I step through the array in a foreach.

Is there a way to check if both the date and time exist in an item somewhere else in the array before outputting?

This is what I have so far:

// loop through each day of week
    foreach ($thisweek as $day => $v) {
        echo '<h3>' . $day . ' ' . $v . '</h3>';
        echo '<ul>';
        // loop through a bunch of hours
        foreach ($eventhours as $eventhour) {
            // loop through events
            foreach ($mergedevents as $event) {

                // if event date is on this day / hour
                if ($event['date'] == $v && $event['start'] == $eventhour) {
                        echo '<li><strong>' . $eventhour . ' - ' . $event['title'] . '</strong></li>';
                } 

                // if daily event has this day / hour
                if ($event['day'] == $day && $event['start'] == $eventhour) {
                        echo '<li>' . $eventhour . ' - ' . $event['title'] . '</li>';
                };

            }
        }
        echo '</ul>';
    }

This works fine, but both occasional and daily events show up regardless. I guess I need to wrap the if($event['day'] == $day) part in another if that looks through the whole array. Is that possible some how?

10
  • if you have an event (in your case), such that two events coincide in sunday, which item has more precedence? the one with the full date or the item with a day? Commented Jun 25, 2014 at 23:57
  • The one with the full date. Commented Jun 25, 2014 at 23:59
  • Later on today, I will add a sort to the output array to turn it into a simple list of events ordered by start time within day, if required. Commented Jun 26, 2014 at 11:39
  • That would be fantastic if you could, Ryan. Many thanks for your help! Commented Jun 26, 2014 at 12:44
  • 1
    No need - glad you are finding it useful. :) I only answer questions that i am interested in. I will change the 'demonstration' first so you can see all the code working. Demonstration of sorting and all the code as functions Commented Jun 26, 2014 at 20:24

2 Answers 2

1

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'),
);
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks, Ryan. This gets me closer. However, I only want to remove repeat (day) events if they fall on the same day AND TIME as the non-repeat (date) event. Is that also possible using this approach?
Sorry, Will fix that... will post an updated answer shortly. After i have had some coffee. ;-/
This is fantastic. Thanks very much for you help. I've learned tonnes just checking through what you have done here. Many thanks again.
0

If I understand correctly, you want to detect collisions between repeated events and non-repeated events and only display the non-repeated event if it overlaps a repeated event. It doesn't look like your code is performing any checking.

You should use three arrays. The first contains all the repeated events, the second contains all the non-repeated events for the week at hand. Then you can create a list of 'displayable' events for the week at hand.

Assuming your two arrays are ordered by starting time, here is a good way to create the displayable array:

  • Compare the starting times of the first element in both arrays, and let E represent the event with the earliest start time.
  • If E is a non-repeated event, add it to the display list.
  • If E is a repeated event, starting with the first element in the non-repeated list, check all the non-repeated events whose start times are before E's ending time. If there are no overlaps, add E to the display list.
  • Take the next earliest event and repeat.

Once you have your displayable event array, your posted code should work fine.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.