diff --git a/src/applications/calendar/application/PhabricatorCalendarApplication.php b/src/applications/calendar/application/PhabricatorCalendarApplication.php index 7a1ca000e5..aeeaa68d2b 100644 --- a/src/applications/calendar/application/PhabricatorCalendarApplication.php +++ b/src/applications/calendar/application/PhabricatorCalendarApplication.php @@ -1,89 +1,90 @@ [1-9]\d*)' => 'PhabricatorCalendarEventViewController', '/calendar/' => array( - '(?:query/(?P[^/]+)/(?:(?P\d+)/(?P\d+)/)?)?' + '(?:query/(?P[^/]+)/(?:(?P\d+)/'. + '(?P\d+)/)?(?:(?P\d+)/)?)?' => 'PhabricatorCalendarEventListController', 'event/' => array( 'create/' => 'PhabricatorCalendarEventEditController', 'edit/(?P[1-9]\d*)/' => 'PhabricatorCalendarEventEditController', 'cancel/(?P[1-9]\d*)/' => 'PhabricatorCalendarEventCancelController', '(?Pjoin|decline|accept)/(?P[1-9]\d*)/' => 'PhabricatorCalendarEventJoinController', 'comment/(?P[1-9]\d*)/' => 'PhabricatorCalendarEventCommentController', ), ), ); } public function getQuickCreateItems(PhabricatorUser $viewer) { $items = array(); $item = id(new PHUIListItemView()) ->setName(pht('Calendar Event')) ->setIcon('fa-calendar') ->setHref($this->getBaseURI().'event/create/'); $items[] = $item; return $items; } public function getMailCommandObjects() { return array( 'event' => array( 'name' => pht('Email Commands: Events'), 'header' => pht('Interacting with Calendar Events'), 'object' => new PhabricatorCalendarEvent(), 'summary' => pht( 'This page documents the commands you can use to interact with '. 'events in Calendar. These commands work when creating new tasks '. 'via email and when replying to existing tasks.'), ), ); } } diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventListController.php b/src/applications/calendar/controller/PhabricatorCalendarEventListController.php index 30a908fb8e..519841d1f3 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventListController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventListController.php @@ -1,41 +1,42 @@ getURIData('year'); $month = $request->getURIData('month'); + $day = $request->getURIData('day'); $engine = new PhabricatorCalendarEventSearchEngine(); if ($month && $year) { - $engine->setCalendarYearAndMonth($year, $month); + $engine->setCalendarYearAndMonthAndDay($year, $month, $day); } $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($request->getURIData('queryKey')) ->setSearchEngine($engine) ->setNavigation($this->buildSideNav()); return $this->delegateToController($controller); } public function buildSideNav() { $user = $this->getRequest()->getUser(); $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); id(new PhabricatorCalendarEventSearchEngine()) ->setViewer($user) ->addNavigationItems($nav->getMenu()); $nav->selectFilter(null); return $nav; } } diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php index e4a9067a5b..05b1d95f51 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -1,476 +1,459 @@ setParameter( 'rangeStart', $this->readDateFromRequest($request, 'rangeStart')); $saved->setParameter( 'rangeEnd', $this->readDateFromRequest($request, 'rangeEnd')); $saved->setParameter( 'upcoming', $this->readBoolFromRequest($request, 'upcoming')); $saved->setParameter( 'invitedPHIDs', $this->readUsersFromRequest($request, 'invited')); $saved->setParameter( 'creatorPHIDs', $this->readUsersFromRequest($request, 'creators')); $saved->setParameter( 'isCancelled', $request->getStr('isCancelled')); $saved->setParameter( 'display', $request->getStr('display')); return $saved; } public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { $query = id(new PhabricatorCalendarEventQuery()); $viewer = $this->requireViewer(); $min_range = $this->getDateFrom($saved)->getEpoch(); $max_range = $this->getDateTo($saved)->getEpoch(); if ($saved->getParameter('display') == 'month') { - list($start_month, $start_year) = $this->getDisplayMonthAndYear($saved); + list($start_year, $start_month) = + $this->getDisplayYearAndMonthAndDay($saved); $start_day = 1; $end_year = ($start_month == 12) ? $start_year + 1 : $start_year; $end_month = ($start_month == 12) ? 1 : $start_month + 1; $end_day = 1; $calendar_start = AphrontFormDateControlValue::newFromParts( $viewer, $start_year, $start_month, $start_day)->getEpoch(); $calendar_end = AphrontFormDateControlValue::newFromParts( $viewer, $end_year, $end_month, $end_day)->getEpoch(); if (!$min_range || ($min_range < $calendar_start)) { $min_range = $calendar_start; } if (!$max_range || ($max_range > $calendar_end)) { $max_range = $calendar_end; } } if ($saved->getParameter('upcoming')) { if ($min_range) { $min_range = max(time(), $min_range); } else { $min_range = time(); } } if ($min_range || $max_range) { $query->withDateRange($min_range, $max_range); } $invited_phids = $saved->getParameter('invitedPHIDs'); if ($invited_phids) { $query->withInvitedPHIDs($invited_phids); } $creator_phids = $saved->getParameter('creatorPHIDs'); if ($creator_phids) { $query->withCreatorPHIDs($creator_phids); } $is_cancelled = $saved->getParameter('isCancelled'); switch ($is_cancelled) { case 'active': $query->withIsCancelled(false); break; case 'cancelled': $query->withIsCancelled(true); break; } return $query; } public function buildSearchForm( AphrontFormView $form, PhabricatorSavedQuery $saved) { $range_start = $this->getDateFrom($saved); $e_start = null; $range_end = $this->getDateTo($saved); $e_end = null; if (!$range_start->isValid()) { $this->addError(pht('Start date is not valid.')); $e_start = pht('Invalid'); } if (!$range_end->isValid()) { $this->addError(pht('End date is not valid.')); $e_end = pht('Invalid'); } $start_epoch = $range_start->getEpoch(); $end_epoch = $range_end->getEpoch(); if ($start_epoch && $end_epoch && ($start_epoch > $end_epoch)) { $this->addError(pht('End date must be after start date.')); $e_start = pht('Invalid'); $e_end = pht('Invalid'); } $upcoming = $saved->getParameter('upcoming'); $is_cancelled = $saved->getParameter('isCancelled', 'active'); $display = $saved->getParameter('display', 'month'); $invited_phids = $saved->getParameter('invitedPHIDs', array()); $creator_phids = $saved->getParameter('creatorPHIDs', array()); $resolution_types = array( 'active' => pht('Active Events Only'), 'cancelled' => pht('Cancelled Events Only'), 'both' => pht('Both Cancelled and Active Events'), ); $display_options = array( 'month' => pht('Month View'), 'day' => pht('Day View (beta)'), 'list' => pht('List View'), ); $form ->appendControl( id(new AphrontFormTokenizerControl()) ->setDatasource(new PhabricatorPeopleDatasource()) ->setName('creators') ->setLabel(pht('Created By')) ->setValue($creator_phids)) ->appendControl( id(new AphrontFormTokenizerControl()) ->setDatasource(new PhabricatorPeopleDatasource()) ->setName('invited') ->setLabel(pht('Invited')) ->setValue($invited_phids)) ->appendChild( id(new AphrontFormDateControl()) ->setLabel(pht('Occurs After')) ->setUser($this->requireViewer()) ->setName('rangeStart') ->setError($e_start) ->setValue($range_start)) ->appendChild( id(new AphrontFormDateControl()) ->setLabel(pht('Occurs Before')) ->setUser($this->requireViewer()) ->setName('rangeEnd') ->setError($e_end) ->setValue($range_end)) ->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( 'upcoming', 1, pht('Show only upcoming events.'), $upcoming)) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Cancelled Events')) ->setName('isCancelled') ->setValue($is_cancelled) ->setOptions($resolution_types)) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Display Options')) ->setName('display') ->setValue($display) ->setOptions($display_options)); } protected function getURI($path) { return '/calendar/'.$path; } protected function getBuiltinQueryNames() { $names = array( 'month' => pht('Month View'), 'upcoming' => pht('Upcoming Events'), 'all' => pht('All Events'), ); return $names; } - public function setCalendarYearAndMonth($year, $month) { + public function setCalendarYearAndMonthAndDay($year, $month, $day = null) { $this->calendarYear = $year; $this->calendarMonth = $month; + $this->calendarDay = $day; return $this; } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); switch ($query_key) { case 'month': return $query->setParameter('display', 'month'); case 'upcoming': return $query->setParameter('upcoming', true); case 'all': return $query; } return parent::buildSavedQueryFromBuiltin($query_key); } protected function getRequiredHandlePHIDsForResultList( array $objects, PhabricatorSavedQuery $query) { $phids = array(); foreach ($objects as $event) { $phids[$event->getUserPHID()] = 1; } return array_keys($phids); } protected function renderResultList( array $events, PhabricatorSavedQuery $query, array $handles) { if ($query->getParameter('display') == 'month') { return $this->buildCalendarView($events, $query, $handles); } else if ($query->getParameter('display') == 'day') { return $this->buildCalendarDayView($events, $query, $handles); } assert_instances_of($events, 'PhabricatorCalendarEvent'); $viewer = $this->requireViewer(); $list = new PHUIObjectItemListView(); foreach ($events as $event) { $href = '/E'.$event->getID(); $from = phabricator_datetime($event->getDateFrom(), $viewer); $to = phabricator_datetime($event->getDateTo(), $viewer); $creator_handle = $handles[$event->getUserPHID()]; $name = (strlen($event->getName())) ? $event->getName() : $event->getTerseSummary($viewer); $color = ($event->getStatus() == PhabricatorCalendarEvent::STATUS_AWAY) ? 'red' : 'yellow'; $item = id(new PHUIObjectItemView()) ->setHeader($name) ->setHref($href) ->setBarColor($color) ->addByline(pht('Creator: %s', $creator_handle->renderLink())) ->addAttribute(pht('From %s to %s', $from, $to)) ->addAttribute(id(new PhutilUTF8StringTruncator()) ->setMaximumGlyphs(64) ->truncateString($event->getDescription())); $list->addItem($item); } return $list; } private function buildCalendarView( array $statuses, PhabricatorSavedQuery $query, array $handles) { $viewer = $this->requireViewer(); $now = time(); - list($start_month, $start_year) = $this->getDisplayMonthAndYear($query); + list($start_year, $start_month) = + $this->getDisplayYearAndMonthAndDay($query); $now_year = phabricator_format_local_time($now, $viewer, 'Y'); $now_month = phabricator_format_local_time($now, $viewer, 'm'); $now_day = phabricator_format_local_time($now, $viewer, 'j'); if ($start_month == $now_month && $start_year == $now_year) { $month_view = new PHUICalendarMonthView( $start_month, $start_year, $now_day); } else { $month_view = new PHUICalendarMonthView( $start_month, $start_year); } $month_view->setUser($viewer); $phids = mpull($statuses, 'getUserPHID'); /* Assign Colors */ $unique = array_unique($phids); $allblue = false; $calcolors = CalendarColors::getColors(); if (count($unique) > count($calcolors)) { $allblue = true; } $i = 0; $eventcolor = array(); foreach ($unique as $phid) { if ($allblue) { $eventcolor[$phid] = CalendarColors::COLOR_SKY; } else { $eventcolor[$phid] = $calcolors[$i]; } $i++; } foreach ($statuses as $status) { $event = new AphrontCalendarEventView(); $event->setEpochRange($status->getDateFrom(), $status->getDateTo()); $name_text = $handles[$status->getUserPHID()]->getName(); $status_text = $status->getHumanStatus(); $event->setUserPHID($status->getUserPHID()); $event->setDescription(pht('%s (%s)', $name_text, $status_text)); $event->setName($status_text); $event->setEventID($status->getID()); $event->setColor($eventcolor[$status->getUserPHID()]); $month_view->addEvent($event); } $month_view->setBrowseURI( $this->getURI('query/'.$query->getQueryKey().'/')); return $month_view; } private function buildCalendarDayView( array $statuses, PhabricatorSavedQuery $query, array $handles) { $viewer = $this->requireViewer(); - list($start_month, $start_year, $start_day) = - $this->getDisplayMonthAndYearAndDay($query); + list($start_year, $start_month, $start_day) = + $this->getDisplayYearAndMonthAndDay($query); $day_view = new PHUICalendarDayView( - $start_month, $start_year, + $start_month, $start_day); $day_view->setUser($viewer); $phids = mpull($statuses, 'getUserPHID'); foreach ($statuses as $status) { $event = new AphrontCalendarDayEventView(); $event->setEventID($status->getID()); $event->setEpochRange($status->getDateFrom(), $status->getDateTo()); $event->setName($status->getName()); $event->setURI('/'.$status->getMonogram()); $day_view->addEvent($event); } + $day_view->setBrowseURI( + $this->getURI('query/'.$query->getQueryKey().'/')); + return $day_view; } - private function getDisplayMonthAndYear( + private function getDisplayYearAndMonthAndDay( PhabricatorSavedQuery $query) { $viewer = $this->requireViewer(); - - // get month/year from url if ($this->calendarYear && $this->calendarMonth) { $start_year = $this->calendarYear; $start_month = $this->calendarMonth; - } else { - $epoch = $this->getDateFrom($query)->getEpoch(); - if (!$epoch) { - $epoch = $this->getDateTo($query)->getEpoch(); - if (!$epoch) { - $epoch = time(); - } - } - $start_year = phabricator_format_local_time($epoch, $viewer, 'Y'); - $start_month = phabricator_format_local_time($epoch, $viewer, 'm'); - } - - return array($start_month, $start_year); - } - - private function getDisplayMonthAndYearAndDay( - PhabricatorSavedQuery $query) { - $viewer = $this->requireViewer(); - if ($this->calendarYear && $this->calendarMonth && $this->calendarDay) { - $start_year = $this->calendarYear; - $start_month = $this->calendarMonth; - $start_day = $this->calendarDay; + $start_day = $this->calendarDay ? $this->calendarDay : 1; } else { $epoch = $this->getDateFrom($query)->getEpoch(); if (!$epoch) { $epoch = $this->getDateTo($query)->getEpoch(); if (!$epoch) { $epoch = time(); } } $start_year = phabricator_format_local_time($epoch, $viewer, 'Y'); $start_month = phabricator_format_local_time($epoch, $viewer, 'm'); $start_day = phabricator_format_local_time($epoch, $viewer, 'd'); } return array($start_year, $start_month, $start_day); } public function getPageSize(PhabricatorSavedQuery $saved) { return $saved->getParameter('limit', 1000); } private function getDateFrom(PhabricatorSavedQuery $saved) { return $this->getDate($saved, 'rangeStart'); } private function getDateTo(PhabricatorSavedQuery $saved) { return $this->getDate($saved, 'rangeEnd'); } private function getDate(PhabricatorSavedQuery $saved, $key) { $viewer = $this->requireViewer(); $wild = $saved->getParameter($key); if ($wild) { $value = AphrontFormDateControlValue::newFromWild($viewer, $wild); } else { $value = AphrontFormDateControlValue::newFromEpoch( $viewer, PhabricatorTime::getNow()); $value->setEnabled(false); } $value->setOptional(true); return $value; } } diff --git a/src/view/phui/calendar/PHUICalendarDayView.php b/src/view/phui/calendar/PHUICalendarDayView.php index 61a0b09c7a..69bdb69239 100644 --- a/src/view/phui/calendar/PHUICalendarDayView.php +++ b/src/view/phui/calendar/PHUICalendarDayView.php @@ -1,245 +1,390 @@ events[] = $event; return $this; } + public function setBrowseURI($browse_uri) { + $this->browseURI = $browse_uri; + return $this; + } + private function getBrowseURI() { + return $this->browseURI; + } + public function __construct($year, $month, $day = null) { $this->day = $day; $this->month = $month; $this->year = $year; } public function render() { require_celerity_resource('phui-calendar-day-css'); - $day_box = new PHUIObjectBoxView(); - $day_of_week = $this->getDayOfWeek(); - $header_text = $this->getDateTime()->format('F j, Y'); - $header_text = $day_of_week.', '.$header_text; - $day_box->setHeaderText($header_text); $hours = $this->getHoursOfDay(); $hourly_events = array(); $rows = array(); // sort events into buckets by their start time // pretend no events overlap foreach ($hours as $hour) { $events = array(); $hour_start = $hour->format('U'); $hour_end = id(clone $hour)->modify('+1 hour')->format('U'); foreach ($this->events as $event) { if ($event->getEpochStart() >= $hour_start && $event->getEpochStart() < $hour_end) { $events[] = $event; } } $count_events = count($events); $n = 0; foreach ($events as $event) { $event_start = $event->getEpochStart(); $event_end = $event->getEpochEnd(); $top = ((($event_start - $hour_start) / ($hour_end - $hour_start)) * 100).'%'; $height = ((($event_end - $event_start) / ($hour_end - $hour_start)) * 100).'%'; $hourly_events[$event->getEventID()] = array( 'hour' => $hour, 'event' => $event, 'offset' => '0', 'width' => '100%', 'top' => $top, 'height' => $height, ); $n++; } } $clusters = $this->findClusters(); foreach ($clusters as $cluster) { $hourly_events = $this->updateEventsFromCluster( $cluster, $hourly_events); } // actually construct table foreach ($hours as $hour) { $drawn_hourly_events = array(); $cell_time = phutil_tag( 'td', array('class' => 'phui-calendar-day-hour'), $hour->format('g A')); foreach ($hourly_events as $hourly_event) { if ($hourly_event['hour'] == $hour) { $drawn_hourly_events[] = $this->drawEvent( $hourly_event['event'], $hourly_event['offset'], $hourly_event['width'], $hourly_event['top'], $hourly_event['height']); } } $cell_event = phutil_tag( 'td', array('class' => 'phui-calendar-day-events'), $drawn_hourly_events); $row = phutil_tag( 'tr', array(), array($cell_time, $cell_event)); $rows[] = $row; } $table = phutil_tag( 'table', array('class' => 'phui-calendar-day-view'), array( '', $rows, )); - $day_box->appendChild($table); + $header = $this->renderDayViewHeader(); + + $day_box = (new PHUIObjectBoxView()) + ->setHeader($header) + ->appendChild($table); + return $day_box; + } + private function renderDayViewHeader() { + $button_bar = null; + + // check for a browseURI, which means we need "fancy" prev / next UI + $uri = $this->getBrowseURI(); + if ($uri) { + list($prev_year, $prev_month, $prev_day) = $this->getPrevDay(); + $prev_uri = $uri.$prev_year.'/'.$prev_month.'/'.$prev_day.'/'; + + list($next_year, $next_month, $next_day) = $this->getNextDay(); + $next_uri = $uri.$next_year.'/'.$next_month.'/'.$next_day.'/'; + + $button_bar = new PHUIButtonBarView(); + + $left_icon = id(new PHUIIconView()) + ->setIconFont('fa-chevron-left bluegrey'); + $left = id(new PHUIButtonView()) + ->setTag('a') + ->setColor(PHUIButtonView::GREY) + ->setHref($prev_uri) + ->setTitle(pht('Previous Day')) + ->setIcon($left_icon); + + $right_icon = id(new PHUIIconView()) + ->setIconFont('fa-chevron-right bluegrey'); + $right = id(new PHUIButtonView()) + ->setTag('a') + ->setColor(PHUIButtonView::GREY) + ->setHref($next_uri) + ->setTitle(pht('Next Day')) + ->setIcon($right_icon); + + $button_bar->addButton($left); + $button_bar->addButton($right); + + } + + $day_of_week = $this->getDayOfWeek(); + $header_text = $this->getDateTime()->format('F j, Y'); + $header_text = $day_of_week.', '.$header_text; + + $header = id(new PHUIHeaderView()) + ->setHeader($header_text); + + if ($button_bar) { + $header->setButtonBar($button_bar); + } + + return $header; } private function updateEventsFromCluster($cluster, $hourly_events) { $cluster_size = count($cluster); $n = 0; foreach ($cluster as $cluster_member) { $event_id = $cluster_member->getEventID(); $offset = (($n / $cluster_size) * 100).'%'; $width = ((1 / $cluster_size) * 100).'%'; if (isset($hourly_events[$event_id])) { $hourly_events[$event_id]['offset'] = $offset; $hourly_events[$event_id]['width'] = $width; } $n++; } return $hourly_events; } private function drawEvent( AphrontCalendarDayEventView $event, $offset, $width, $top, $height) { $name = phutil_tag( 'a', array( 'class' => 'phui-calendar-day-event-link', 'href' => $event->getURI(), ), $event->getName()); $div = phutil_tag( 'div', array( 'class' => 'phui-calendar-day-event', 'style' => 'left: '.$offset .'; width: '.$width .'; top: '.$top .'; height: '.$height .';', ), $name); return $div; } private function getDayOfWeek() { $date = $this->getDateTime(); $day_of_week = $date->format('l'); return $day_of_week; } // returns DateTime of each hour in the day private function getHoursOfDay() { $included_datetimes = array(); $day_datetime = $this->getDateTime(); $day_epoch = $day_datetime->format('U'); $day_datetime->modify('+1 day'); $next_day_epoch = $day_datetime->format('U'); $included_time = $day_epoch; $included_datetime = $this->getDateTime(); while ($included_time < $next_day_epoch) { $included_datetimes[] = clone $included_datetime; $included_datetime->modify('+1 hour'); $included_time = $included_datetime->format('U'); } return $included_datetimes; } + private function getNumberOfDaysInMonth($month, $year) { + $user = $this->user; + $timezone = new DateTimeZone($user->getTimezoneIdentifier()); + + list($next_year, $next_month) = $this->getNextYearAndMonth($month, $year); + + $end_date = new DateTime("{$next_year}-{$next_month}-01", $timezone); + $end_epoch = $end_date->format('U'); + + $days = 0; + for ($day = 1; $day <= 31; $day++) { + $day_date = new DateTime("{$year}-{$month}-{$day}", $timezone); + $day_epoch = $day_date->format('U'); + if ($day_epoch >= $end_epoch) { + break; + } else { + $days++; + } + } + + return $days; + } + + private function getPrevDay() { + $day = $this->day; + $month = $this->month; + $year = $this->year; + + $prev_year = $year; + $prev_month = $month; + $prev_day = $day - 1; + if ($prev_day == 0) { + $prev_month--; + if ($prev_month == 0) { + $prev_year--; + $prev_month = 12; + } + $prev_day = $this->getNumberOfDaysInMonth($prev_month, $prev_year); + } + + return array($prev_year, $prev_month, $prev_day); + } + + private function getNextDay() { + $day = $this->day; + $month = $this->month; + $year = $this->year; + + $next_year = $year; + $next_month = $month; + $next_day = $day + 1; + $days_in_month = $this->getNumberOfDaysInMonth($month, $year); + if ($next_day > $days_in_month) { + $next_day = 1; + $next_month++; + } + if ($next_month == 13) { + $next_year++; + $next_month = 1; + } + + return array($next_year, $next_month, $next_day); + } + + private function getNextYearAndMonth($month, $year) { + $next_year = $year; + $next_month = $month + 1; + if ($next_month == 13) { + $next_year = $year + 1; + $next_month = 1; + } + + return array($next_year, $next_month); + } + + private function getPrevYearAndMonth($month, $year) { + $prev_year = $year; + $prev_month = $month - 1; + if ($prev_month == 0) { + $prev_year = $year - 1; + $prev_month = 12; + } + + return array($prev_year, $prev_month); + } + private function getDateTime() { $user = $this->user; $timezone = new DateTimeZone($user->getTimezoneIdentifier()); $day = $this->day; $month = $this->month; $year = $this->year; $date = new DateTime("{$year}-{$month}-{$day} ", $timezone); return $date; } private function findClusters() { $events = msort($this->events, 'getEpochStart'); $clusters = array(); foreach ($events as $event) { $destination_cluster_key = null; $event_start = $event->getEpochStart(); $event_end = $event->getEpochEnd(); foreach ($clusters as $key => $cluster) { foreach ($cluster as $clustered_event) { $compare_event_start = $clustered_event->getEpochStart(); $compare_event_end = $clustered_event->getEpochEnd(); if ($event_start < $compare_event_end && $event_end > $compare_event_start) { $destination_cluster_key = $key; break; } } } if ($destination_cluster_key !== null) { $clusters[$destination_cluster_key][] = $event; } else { $next_cluster = array(); $next_cluster[] = $event; $clusters[] = $next_cluster; } } return $clusters; } }