Calendar, offset overlapping events

2019-05-23 23:48发布

问题:

I am trying to write a calendar in PHP. In week view, I want my events to be listed like iCal, where simultaneous events reduces their width to half size.

I have an extremely hard time figuring this one out though, so I hope you can help me. What I want is that if one event is overlapping another, it should set [split] => true on both event arrays - or something in that direction (read: I am unsure whether this is the most efficient solution). Then I can check for split == true in the foreach loop which prints out the events.

Here is an example array containing two simultaneous events:

$events = array(
  array(
    "id" => 21,
    "start" => 1242219600,
    "end" => 1242237600,
    "title" => "foo",
    "split" => false
  ),
  array(
    "id" => 22,
    "start" => 1242223200,
    "end" => 1242234000,
    "title" => "foo",
    "split" => false
  )
);

$events = someFunctionToOffsetEvents($events);

How would you solve this one?

回答1:

I've had to deal with date collision issues alot lately, and the best I've been able to come up with is:

date1.start < date2.end and date1.end > date2.start = collision

This simple formula will account for all of the following situations:

--same for all situations:
date1.start = 1/1/2009
date1.end = 1/10/2009

--Start date is in first dates range:
date2.start = 1/9/2009
date2.end = 2/10/2009

--End date is in first dates range:
date2.start = 12/10/2008
date2.end = 1/3/2009

--Start & End date is inside first dates range:
date2.start = 1/2/2009
date2.end = 1/3/2009

--First date is inside of second dates range:
date2.start = 12/1/2008
date2.end = 2/1/2009
$date1 = array('start' => '2009-01-05', 'end' => '2009-01-10');
$date2 = array('start' => '2009-01-01', 'end' => '2009-01-04'); // end inside one
$date3 = array('start' => '2009-01-04', 'end' => '2009-01-15'); // start inside one
$date4 = array('start' => '2009-01-01', 'end' => '2009-01-15'); // one inside me
$date5 = array('start' => '2009-01-04', 'end' => '2009-01-05'); // inside one

function datesCollide($date1, $date2)
{
    $start1TS = strtotime($date1['start']);
    $end1TS = strtotime($date1['end']);
    $start2TS = strtotime($date2['start']);
    $end2TS = strtotime($date2['end']);

    if ($start1TS <= $end2TS && $end1TS >= $start2)
    {
        return true;
    }

    return false;
}

Based on your comment this is probably the solution you are looking for: Note that this solution isn't very optimized, and should only be used for figuring out a better solution. Never trust code from the web.

$events = array(
  array(
        "id" => 21,
        "start" => 1242219600,
        "end" => 1242237600,
        "title" => "foo",
        "split" => false
  ),
  array(
        "id" => 22,
        "start" => 1242223200,
        "end" => 1242234000,
        "title" => "foo",
        "split" => false
  )
);

foreach ($events as $key => $event)
{
    $events[$key]->split = dateCollisionCheck($event, $events);
}

function dateCollisionCheck(&$event, &$eventList)
{
    foreach ($eventList as $checkEvent)
    {
        if ($event->id != $checkEvent->id)
        {
            if ($event->start <= $checkEvent->end && $event->end >= $checkEvent->start)
            {
                return true; // return as soon as we know there is a collision
            }
        }
    }

    return false;
}

*Code has not been tested



回答2:

I posted the question about what if three events overlap, which leads to a better solution. Don't put your GUI and your data in the same structure. Save your data, and then figure out how to display it.



回答3:

I would revert your problem. Map the events on an array representing the week you want to display:

$weekdayEvents=array();
foreach ($events as $event) {
   $day=toThisWeek($event['start'], $firstweekday),
   $lastDay=toThisWeek($event['end'], $firstweekday); 
   while ($day<=$lastDay) {
     $weekdayEvents[$day++][]=$event['id'];
   }
}
// now you have an array of array filled with ids
$eventCollisions=array();
foreach($weekdaysEvents as $activeDay) {
  $eventsOnDay = count($activeDay);
  foreach($activeDay as $eventID) {
    if ($eventCollisions[$eventID]<$eventsOnDay) {
      $eventCollisions[$eventID]=$eventsOnDay;
    }
  }
}

}

where toThisWeek() is a function like

function toThisWeek($dayTimestamp, $firstDayOfWeekTimestamp) {
  $dayOfWeek = unixtojd($firsDayOfWeekTimestamp) - unixtojd($dayTimestamp);
  if($dayOfWeek<0) return 0;
  if($dayOfWeek>6) return 6; 
  return $dayOfWeek;      
}

Now in $eventCollisions you have the max number of collision occur on an event for every events occurring during the week you have to display. Now you can use the values in this array to fill the field split in the $events subarray:

foreach ($events as $event) {
  $event['split']=$eventCollisions[$event['id']]-1;
}

Or just use the values in $eventCollisions "ondemand"



回答4:

Here is the working, modified code based on Rob's answer:

// this is in my getEvents function
if(is_array($events)) {
  foreach ($events as $key => $event) {
    $events[$key]['collides'] = $this->dateCollisionCheck($event, $events);
  }
}

//this is another function added to calendar.class.php
function dateCollisionCheck(&$event, &$eventList) {
  foreach ($eventList as $checkEvent) {
    if ($event['id'] != $checkEvent['id']) {
      if ($event['start'] <= $checkEvent['end'] && $event['end'] >= $checkEvent['start']) {
        return true;
      }
    }
  }

  return false;
}