Compares this to another date object, including times, to see
which one is greater (later). Assumes that the dates are in the
same timezone.
public compareDateTime ( mixed $other ) : integer | ||
$other | mixed | The date to compare to. |
return | integer | == 0 if they are equal >= 1 if $this is greater (later) <= -1 if $other is greater (later) |
/** * Retrieve the busy times from this event within the given timeframe. This * is trivial for non-recurring events but recurring events need to be * expanded. * * @param Horde_Date $startDate The start point. * @param Horde_Date $endDate The end point. * * @return array The list of busy times (only the start times of the event). */ public function getBusyTimes(Horde_Date $startDate, Horde_Date $endDate) { if (!$this->recurs()) { if ($startDate->compareDateTime($this->_start) > 0 || $endDate->compareDateTime($this->_start) < 0) { return array(); } return array($this->_start->timestamp()); } else { $result = array(); $next = $this->_recurrence->nextRecurrence($startDate); while ($next) { if ($endDate->compareDateTime($next) < 0) { break; } if (!$this->_recurrence->hasException($next->year, $next->month, $next->mday)) { $result[] = $next->timestamp(); } $next->mday++; $next = $this->_recurrence->nextRecurrence($next); } return $result; } }
/** * Is a Horde_Date within this span? * * @param Horde_Date $date */ public function includes($date) { return $this->begin->compareDateTime($date) <= 0 && $this->end->compareDateTime($date) >= 0; }
public function readForm() { global $prefs, $session; // Event owner. $targetcalendar = Horde_Util::getFormData('targetcalendar'); if (strpos($targetcalendar, '\\')) { list(, $this->creator) = explode('\\', $targetcalendar, 2); } elseif (!isset($this->_id)) { $this->creator = $GLOBALS['registry']->getAuth(); } // Basic fields. $this->title = Horde_Util::getFormData('title', $this->title); $this->description = Horde_Util::getFormData('description', $this->description); $this->location = Horde_Util::getFormData('location', $this->location); $this->timezone = Horde_Util::getFormData('timezone', $this->timezone); $this->private = (bool) Horde_Util::getFormData('private'); // URL. $url = Horde_Util::getFormData('eventurl', $this->url); if (strlen($url)) { // Analyze and re-construct. $url = @parse_url($url); if ($url) { if (function_exists('http_build_url')) { if (empty($url['path'])) { $url['path'] = '/'; } $url = http_build_url($url); } else { $new_url = ''; if (isset($url['scheme'])) { $new_url .= $url['scheme'] . '://'; } if (isset($url['user'])) { $new_url .= $url['user']; if (isset($url['pass'])) { $new_url .= ':' . $url['pass']; } $new_url .= '@'; } if (isset($url['host'])) { // Convert IDN hosts to ASCII. if (function_exists('idn_to_ascii')) { $url['host'] = @idn_to_ascii($url['host']); } elseif (Horde_Mime::is8bit($url['host'])) { //throw new Kronolith_Exception(_("Invalid character in URL.")); $url['host'] = ''; } $new_url .= $url['host']; } if (isset($url['path'])) { $new_url .= $url['path']; } if (isset($url['query'])) { $new_url .= '?' . $url['query']; } if (isset($url['fragment'])) { $new_url .= '#' . $url['fragment']; } $url = $new_url; } } } $this->url = $url; // Status. $this->status = Horde_Util::getFormData('status', $this->status); // Attendees. $attendees = $session->get('kronolith', 'attendees', Horde_Session::TYPE_ARRAY); if (!is_null($newattendees = Horde_Util::getFormData('attendees'))) { $newattendees = Kronolith::parseAttendees(trim($newattendees)); foreach ($newattendees as $email => $attendee) { if (!isset($attendees[$email])) { $attendees[$email] = $attendee; } } foreach (array_keys($attendees) as $email) { if (!isset($newattendees[$email])) { unset($attendees[$email]); } } } $this->attendees = $attendees; // Event start. $allDay = Horde_Util::getFormData('whole_day'); if ($start_date = Horde_Util::getFormData('start_date')) { // From ajax interface. $this->start = Kronolith::parseDate($start_date . ' ' . Horde_Util::getFormData('start_time'), true, $this->timezone); if ($allDay) { $this->start->hour = $this->start->min = $this->start->sec = 0; } } else { // From traditional interface. $start = Horde_Util::getFormData('start'); $start_year = $start['year']; $start_month = $start['month']; $start_day = $start['day']; $start_hour = Horde_Util::getFormData('start_hour'); $start_min = Horde_Util::getFormData('start_min'); $am_pm = Horde_Util::getFormData('am_pm'); if (!$prefs->getValue('twentyFour')) { if ($am_pm == 'PM') { if ($start_hour != 12) { $start_hour += 12; } } elseif ($start_hour == 12) { $start_hour = 0; } } if (Horde_Util::getFormData('end_or_dur') == 1) { if ($allDay) { $start_hour = 0; $start_min = 0; $dur_day = 0; $dur_hour = 24; $dur_min = 0; } else { $dur_day = (int) Horde_Util::getFormData('dur_day'); $dur_hour = (int) Horde_Util::getFormData('dur_hour'); $dur_min = (int) Horde_Util::getFormData('dur_min'); } } $this->start = new Horde_Date(array('hour' => $start_hour, 'min' => $start_min, 'month' => $start_month, 'mday' => $start_day, 'year' => $start_year), $this->timezone); } // Event end. if ($end_date = Horde_Util::getFormData('end_date')) { // From ajax interface. $this->end = Kronolith::parseDate($end_date . ' ' . Horde_Util::getFormData('end_time'), true, $this->timezone); if ($allDay) { $this->end->hour = $this->end->min = $this->end->sec = 0; $this->end->mday++; } } elseif (Horde_Util::getFormData('end_or_dur') == 1) { // Event duration from traditional interface. $this->end = new Horde_Date(array('hour' => $start_hour + $dur_hour, 'min' => $start_min + $dur_min, 'month' => $start_month, 'mday' => $start_day + $dur_day, 'year' => $start_year)); } else { // From traditional interface. $end = Horde_Util::getFormData('end'); $end_year = $end['year']; $end_month = $end['month']; $end_day = $end['day']; $end_hour = Horde_Util::getFormData('end_hour'); $end_min = Horde_Util::getFormData('end_min'); $end_am_pm = Horde_Util::getFormData('end_am_pm'); if (!$prefs->getValue('twentyFour')) { if ($end_am_pm == 'PM') { if ($end_hour != 12) { $end_hour += 12; } } elseif ($end_hour == 12) { $end_hour = 0; } } $this->end = new Horde_Date(array('hour' => $end_hour, 'min' => $end_min, 'month' => $end_month, 'mday' => $end_day, 'year' => $end_year), $this->timezone); if ($this->end->compareDateTime($this->start) < 0) { $this->end = new Horde_Date($this->start); } } $this->allday = false; // Alarm. if (!is_null($alarm = Horde_Util::getFormData('alarm'))) { if ($alarm) { $value = Horde_Util::getFormData('alarm_value'); $unit = Horde_Util::getFormData('alarm_unit'); if ($value == 0) { $value = $unit = 1; } $this->alarm = $value * $unit; // Notification. if (Horde_Util::getFormData('alarm_change_method')) { $types = Horde_Util::getFormData('event_alarms'); $methods = array(); if (!empty($types)) { foreach ($types as $type) { $methods[$type] = array(); switch ($type) { case 'notify': $methods[$type]['sound'] = Horde_Util::getFormData('event_alarms_sound'); break; case 'mail': $methods[$type]['email'] = Horde_Util::getFormData('event_alarms_email'); break; case 'popup': break; } } } $this->methods = $methods; } else { $this->methods = array(); } } else { $this->alarm = 0; $this->methods = array(); } } // Recurrence. $this->recurrence = $this->readRecurrenceForm($this->start, $this->timezone, $this->recurrence); // Convert to local timezone. $this->setTimezone(false); // Resources $existingResources = $this->_resources; if (Horde_Util::getFormData('isajax', false)) { $resources = array(); } else { $resources = $session->get('kronolith', 'resources', Horde_Session::TYPE_ARRAY); } $newresources = Horde_Util::getFormData('resources'); if (!empty($newresources)) { foreach (explode(',', $newresources) as $id) { try { $resource = Kronolith::getDriver('Resource')->getResource($id); } catch (Kronolith_Exception $e) { $GLOBALS['notification']->push($e->getMessage(), 'horde.error'); continue; } if (!$resource instanceof Kronolith_Resource_Group || $resource->isFree($this)) { $resources[$resource->getId()] = array('attendance' => Kronolith::PART_REQUIRED, 'response' => Kronolith::RESPONSE_NONE, 'name' => $resource->get('name')); } else { $GLOBALS['notification']->push(_("No resources from this group were available"), 'horde.error'); } } } $this->_resources = $resources; // Check if we need to remove any resources $merged = $existingResources + $this->_resources; $delete = array_diff(array_keys($existingResources), array_keys($this->_resources)); foreach ($delete as $key) { // Resource might be declined, in which case it won't have the event // on it's calendar. if ($merged[$key]['response'] != Kronolith::RESPONSE_DECLINED) { try { Kronolith::getDriver('Resource')->getResource($key)->removeEvent($this); } catch (Kronolith_Exception $e) { $GLOBALS['notification']->push('foo', 'horde.error'); } } } // Tags. $this->tags = Horde_Util::getFormData('tags', $this->tags); // Geolocation if (Horde_Util::getFormData('lat') && Horde_Util::getFormData('lon')) { $this->geoLocation = array('lat' => Horde_Util::getFormData('lat'), 'lon' => Horde_Util::getFormData('lon'), 'zoom' => Horde_Util::getFormData('zoom')); } $this->initialized = true; }
/** * @throws Kronolith_Exception */ public function listAlarms($date, $fullevent = false) { $allevents = $this->listEvents($date, null, array('has_alarm' => true)); $events = array(); foreach (array_keys($allevents) as $eventId) { $event = $this->getEvent($eventId); if (!$event->recurs()) { $start = new Horde_Date($event->start); $start->min -= $event->alarm; if ($start->compareDateTime($date) <= 0 && $date->compareDateTime($event->end) <= -1) { $events[] = $fullevent ? $event : $eventId; } } else { if ($next = $event->recurrence->nextRecurrence($date)) { if ($event->recurrence->hasException($next->year, $next->month, $next->mday)) { continue; } $start = new Horde_Date($next); $start->min -= $event->alarm; $end = new Horde_Date(array('year' => $next->year, 'month' => $next->month, 'mday' => $next->mday, 'hour' => $event->end->hour, 'min' => $event->end->min, 'sec' => $event->end->sec)); if ($start->compareDateTime($date) <= 0 && $date->compareDateTime($end) <= -1) { if ($fullevent) { $event->start = $start; $event->end = $end; $events[] = $event; } else { $events[] = $eventId; } } } } } return is_array($events) ? $events : array(); }
/** * * @param Horde_Date $date The date to list alarms for * @param boolean $fullevent Return the full event objects? * * @return array An array of event ids, or Kronolith_Event objects * @throws Kronolith_Exception */ public function listAlarms($date, $fullevent = false) { $allevents = $this->listEvents($date, null, array('has_alarm' => true)); $events = array(); foreach ($allevents as $dayevents) { foreach ($dayevents as $event) { if (!$event->recurs()) { $start = new Horde_Date($event->start); $start->min -= $event->alarm; if ($start->compareDateTime($date) <= 0 && $date->compareDateTime($event->end) <= -1) { $events[] = $fullevent ? $event : $event->id; } } else { // Need to start at the beginning of the day to catch the // case where we might be within the event's timespan // when we call this, hence nextRecurrence() would miss the // current event. $start = clone $date; $start->min = 0; $start->hour = 0; $start->sec = 0; if ($next = $event->recurrence->nextRecurrence($start)) { if ($event->recurrence->hasException($next->year, $next->month, $next->mday)) { continue; } $start = new Horde_Date($next); $start->min -= $event->alarm; $diff = $event->start->diff($event->end); $end = new Horde_Date(array('year' => $next->year, 'month' => $next->month, 'mday' => $next->mday + $diff, 'hour' => $event->end->hour, 'min' => $event->end->min, 'sec' => $event->end->sec)); if ($start->compareDateTime($date) <= 0 && $date->compareDateTime($end) <= -1) { if ($fullevent) { $event->start = $next; $event->end = $end; $events[] = $event; } else { $events[] = $event->id; } } } } } } return $events; }
/** * Adds an event to all the days it covers. * * @param array $result The current result list. * @param Kronolith_Event $event An event object. * @param Horde_Date $eventStart The event's start at the actual * recurrence. * @param Horde_Date $eventEnd The event's end at the actual recurrence. * @param boolean $json Store the results of the events' toJson() * method? */ public static function addCoverDates(&$results, $event, $eventStart, $eventEnd, $json) { $loopDate = new Horde_Date($eventStart->year, $eventStart->month, $eventStart->mday); $allDay = $event->isAllDay(); while ($loopDate->compareDateTime($eventEnd) <= 0) { if (!$allDay || $loopDate->compareDateTime($eventEnd) != 0) { $addEvent = clone $event; $addEvent->start = $eventStart; $addEvent->end = $eventEnd; if ($loopDate->compareDate($eventStart) != 0) { $addEvent->first = false; } if ($loopDate->compareDate($eventEnd) != 0) { $addEvent->last = false; } if ($addEvent->recurs() && $addEvent->recurrence->hasCompletion($loopDate->year, $loopDate->month, $loopDate->mday)) { $addEvent->status = Kronolith::STATUS_CANCELLED; } $results[$loopDate->dateString()][$addEvent->id] = $json ? $addEvent->toJson($allDay) : $addEvent; } $loopDate->mday++; } }
/** * Reads form/post data and updates this event's properties. * * @param Kronolith_Event|null $existing If this is an exception event * this is taken as the base event. * @since 4.2.6 * */ public function readForm(Kronolith_Event $existing = null) { global $prefs, $session; // Event owner. $targetcalendar = Horde_Util::getFormData('targetcalendar'); if (strpos($targetcalendar, '\\')) { list(, $this->creator) = explode('\\', $targetcalendar, 2); } elseif (!isset($this->_id)) { $this->creator = $GLOBALS['registry']->getAuth(); } // Basic fields. $this->title = Horde_Util::getFormData('title', $this->title); $this->description = Horde_Util::getFormData('description', $this->description); $this->location = Horde_Util::getFormData('location', $this->location); $this->timezone = Horde_Util::getFormData('timezone', $this->timezone); $this->private = (bool) Horde_Util::getFormData('private'); // URL. $url = Horde_Util::getFormData('eventurl', $this->url); if (strlen($url)) { // Analyze and re-construct. $url = @parse_url($url); if ($url) { if (function_exists('http_build_url')) { if (empty($url['path'])) { $url['path'] = '/'; } $url = http_build_url($url); } else { $new_url = ''; if (isset($url['scheme'])) { $new_url .= $url['scheme'] . '://'; } if (isset($url['user'])) { $new_url .= $url['user']; if (isset($url['pass'])) { $new_url .= ':' . $url['pass']; } $new_url .= '@'; } if (isset($url['host'])) { // Convert IDN hosts to ASCII. if (function_exists('idn_to_ascii')) { $url['host'] = @idn_to_ascii($url['host']); } elseif (Horde_Mime::is8bit($url['host'])) { //throw new Kronolith_Exception(_("Invalid character in URL.")); $url['host'] = ''; } $new_url .= $url['host']; } if (isset($url['path'])) { $new_url .= $url['path']; } if (isset($url['query'])) { $new_url .= '?' . $url['query']; } if (isset($url['fragment'])) { $new_url .= '#' . $url['fragment']; } $url = $new_url; } } } $this->url = $url; // Status. $this->status = Horde_Util::getFormData('status', $this->status); // Attendees. $attendees = $session->get('kronolith', 'attendees', Horde_Session::TYPE_ARRAY); if (!is_null($newattendees = Horde_Util::getFormData('attendees'))) { $newattendees = Kronolith::parseAttendees(trim($newattendees)); foreach ($newattendees as $email => $attendee) { if (!isset($attendees[$email])) { $attendees[$email] = $attendee; } } foreach (array_keys($attendees) as $email) { if (!isset($newattendees[$email])) { unset($attendees[$email]); } } } $this->attendees = $attendees; // Event start. $allDay = Horde_Util::getFormData('whole_day'); if ($start_date = Horde_Util::getFormData('start_date')) { // From ajax interface. $this->start = Kronolith::parseDate($start_date . ' ' . Horde_Util::getFormData('start_time'), true, $this->timezone); if ($allDay) { $this->start->hour = $this->start->min = $this->start->sec = 0; } } else { // From traditional interface. $start = Horde_Util::getFormData('start'); $start_year = $start['year']; $start_month = $start['month']; $start_day = $start['day']; $start_hour = Horde_Util::getFormData('start_hour'); $start_min = Horde_Util::getFormData('start_min'); $am_pm = Horde_Util::getFormData('am_pm'); if (!$prefs->getValue('twentyFour')) { if ($am_pm == 'PM') { if ($start_hour != 12) { $start_hour += 12; } } elseif ($start_hour == 12) { $start_hour = 0; } } if (Horde_Util::getFormData('end_or_dur') == 1) { if ($allDay) { $start_hour = 0; $start_min = 0; $dur_day = 0; $dur_hour = 24; $dur_min = 0; } else { $dur_day = (int) Horde_Util::getFormData('dur_day'); $dur_hour = (int) Horde_Util::getFormData('dur_hour'); $dur_min = (int) Horde_Util::getFormData('dur_min'); } } $this->start = new Horde_Date(array('hour' => $start_hour, 'min' => $start_min, 'month' => $start_month, 'mday' => $start_day, 'year' => $start_year), $this->timezone); } // Event end. if ($end_date = Horde_Util::getFormData('end_date')) { // From ajax interface. $this->end = Kronolith::parseDate($end_date . ' ' . Horde_Util::getFormData('end_time'), true, $this->timezone); if ($allDay) { $this->end->hour = $this->end->min = $this->end->sec = 0; $this->end->mday++; } } elseif (Horde_Util::getFormData('end_or_dur') == 1) { // Event duration from traditional interface. $this->end = new Horde_Date(array('hour' => $start_hour + $dur_hour, 'min' => $start_min + $dur_min, 'month' => $start_month, 'mday' => $start_day + $dur_day, 'year' => $start_year)); } else { // From traditional interface. $end = Horde_Util::getFormData('end'); $end_year = $end['year']; $end_month = $end['month']; $end_day = $end['day']; $end_hour = Horde_Util::getFormData('end_hour'); $end_min = Horde_Util::getFormData('end_min'); $end_am_pm = Horde_Util::getFormData('end_am_pm'); if (!$prefs->getValue('twentyFour')) { if ($end_am_pm == 'PM') { if ($end_hour != 12) { $end_hour += 12; } } elseif ($end_hour == 12) { $end_hour = 0; } } $this->end = new Horde_Date(array('hour' => $end_hour, 'min' => $end_min, 'month' => $end_month, 'mday' => $end_day, 'year' => $end_year), $this->timezone); if ($this->end->compareDateTime($this->start) < 0) { $this->end = new Horde_Date($this->start); } } $this->allday = false; // Alarm. if (!is_null($alarm = Horde_Util::getFormData('alarm'))) { if ($alarm) { $value = Horde_Util::getFormData('alarm_value'); $unit = Horde_Util::getFormData('alarm_unit'); if ($value == 0) { $value = $unit = 1; } $this->alarm = $value * $unit; // Notification. if (Horde_Util::getFormData('alarm_change_method')) { $types = Horde_Util::getFormData('event_alarms'); $methods = array(); if (!empty($types)) { foreach ($types as $type) { $methods[$type] = array(); switch ($type) { case 'notify': $methods[$type]['sound'] = Horde_Util::getFormData('event_alarms_sound'); break; case 'mail': $methods[$type]['email'] = Horde_Util::getFormData('event_alarms_email'); break; case 'popup': break; } } } $this->methods = $methods; } else { $this->methods = array(); } } else { $this->alarm = 0; $this->methods = array(); } } // Recurrence. $this->recurrence = $this->readRecurrenceForm($this->start, $this->timezone, $this->recurrence); // Convert to local timezone. $this->setTimezone(false); $this->_handleResources($existing); // Tags. $this->tags = Horde_Util::getFormData('tags', $this->tags); // Geolocation if (Horde_Util::getFormData('lat') && Horde_Util::getFormData('lon')) { $this->geoLocation = array('lat' => Horde_Util::getFormData('lat'), 'lon' => Horde_Util::getFormData('lon'), 'zoom' => Horde_Util::getFormData('zoom')); } $this->initialized = true; }
/** * Finds the next recurrence of this event that's after $afterDate. * * @param Horde_Date|string $after Return events after this date. * * @return Horde_Date|boolean The date of the next recurrence or false * if the event does not recur after * $afterDate. */ public function nextRecurrence($after) { if (!$after instanceof Horde_Date) { $after = new Horde_Date($after); } else { $after = clone $after; } // Make sure $after and $this->start are in the same TZ $after->setTimezone($this->start->timezone); if ($this->start->compareDateTime($after) >= 0) { return clone $this->start; } if ($this->recurInterval == 0 && empty($this->rdates)) { return false; } switch ($this->getRecurType()) { case self::RECUR_DAILY: $diff = $this->start->diff($after); $recur = ceil($diff / $this->recurInterval); if ($this->recurCount && $recur >= $this->recurCount) { return false; } $recur *= $this->recurInterval; $next = $this->start->add(array('day' => $recur)); if ((!$this->hasRecurEnd() || $next->compareDateTime($this->recurEnd) <= 0) && $next->compareDateTime($after) >= 0) { return $next; } break; case self::RECUR_WEEKLY: if (empty($this->recurData)) { return false; } $start_week = Horde_Date_Utils::firstDayOfWeek($this->start->format('W'), $this->start->year); $start_week->timezone = $this->start->timezone; $start_week->hour = $this->start->hour; $start_week->min = $this->start->min; $start_week->sec = $this->start->sec; // Make sure we are not at the ISO-8601 first week of year while // still in month 12...OR in the ISO-8601 last week of year while // in month 1 and adjust the year accordingly. $week = $after->format('W'); if ($week == 1 && $after->month == 12) { $theYear = $after->year + 1; } elseif ($week >= 52 && $after->month == 1) { $theYear = $after->year - 1; } else { $theYear = $after->year; } $after_week = Horde_Date_Utils::firstDayOfWeek($week, $theYear); $after_week->timezone = $this->start->timezone; $after_week_end = clone $after_week; $after_week_end->mday += 7; $diff = $start_week->diff($after_week); $interval = $this->recurInterval * 7; $repeats = floor($diff / $interval); if ($diff % $interval < 7) { $recur = $diff; } else { /** * If the after_week is not in the first week interval the * search needs to skip ahead a complete interval. The way it is * calculated here means that an event that occurs every second * week on Monday and Wednesday with the event actually starting * on Tuesday or Wednesday will only have one incidence in the * first week. */ $recur = $interval * ($repeats + 1); } if ($this->hasRecurCount()) { $recurrences = 0; /** * Correct the number of recurrences by the number of events * that lay between the start of the start week and the * recurrence start. */ $next = clone $start_week; while ($next->compareDateTime($this->start) < 0) { if ($this->recurOnDay((int) pow(2, $next->dayOfWeek()))) { $recurrences--; } ++$next->mday; } if ($repeats > 0) { $weekdays = $this->recurData; $total_recurrences_per_week = 0; while ($weekdays > 0) { if ($weekdays % 2) { $total_recurrences_per_week++; } $weekdays = ($weekdays - $weekdays % 2) / 2; } $recurrences += $total_recurrences_per_week * $repeats; } } $next = clone $start_week; $next->mday += $recur; while ($next->compareDateTime($after) < 0 && $next->compareDateTime($after_week_end) < 0) { if ($this->hasRecurCount() && $next->compareDateTime($after) < 0 && $this->recurOnDay((int) pow(2, $next->dayOfWeek()))) { $recurrences++; } ++$next->mday; } if ($this->hasRecurCount() && $recurrences >= $this->recurCount) { return false; } if (!$this->hasRecurEnd() || $next->compareDateTime($this->recurEnd) <= 0) { if ($next->compareDateTime($after_week_end) >= 0) { return $this->nextRecurrence($after_week_end); } while (!$this->recurOnDay((int) pow(2, $next->dayOfWeek())) && $next->compareDateTime($after_week_end) < 0) { ++$next->mday; } if (!$this->hasRecurEnd() || $next->compareDateTime($this->recurEnd) <= 0) { if ($next->compareDateTime($after_week_end) >= 0) { return $this->nextRecurrence($after_week_end); } else { return $next; } } } break; case self::RECUR_MONTHLY_DATE: $start = clone $this->start; if ($after->compareDateTime($start) < 0) { $after = clone $start; } else { $after = clone $after; } // If we're starting past this month's recurrence of the event, // look in the next month on the day the event recurs. if ($after->mday > $start->mday) { ++$after->month; $after->mday = $start->mday; } // Adjust $start to be the first match. $offset = $after->month - $start->month + ($after->year - $start->year) * 12; $offset = floor(($offset + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval; if ($this->recurCount && $offset / $this->recurInterval >= $this->recurCount) { return false; } $start->month += $offset; $count = $offset / $this->recurInterval; do { if ($this->recurCount && $count++ >= $this->recurCount) { return false; } // Bail if we've gone past the end of recurrence. if ($this->hasRecurEnd() && $this->recurEnd->compareDateTime($start) < 0) { return false; } if ($start->isValid()) { return $start; } // If the interval is 12, and the date isn't valid, then we // need to see if February 29th is an option. If not, then the // event will _never_ recur, and we need to stop checking to // avoid an infinite loop. if ($this->recurInterval == 12 && ($start->month != 2 || $start->mday > 29)) { return false; } // Add the recurrence interval. $start->month += $this->recurInterval; } while (true); break; case self::RECUR_MONTHLY_WEEKDAY: // Start with the start date of the event. $estart = clone $this->start; // What day of the week, and week of the month, do we recur on? if (isset($this->recurNthDay)) { $nth = $this->recurNthDay; $weekday = log($this->recurData, 2); } else { $nth = ceil($this->start->mday / 7); $weekday = $estart->dayOfWeek(); } // Adjust $estart to be the first candidate. $offset = $after->month - $estart->month + ($after->year - $estart->year) * 12; $offset = floor(($offset + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval; // Adjust our working date until it's after $after. $estart->month += $offset - $this->recurInterval; $count = $offset / $this->recurInterval; do { if ($this->recurCount && $count++ >= $this->recurCount) { return false; } $estart->month += $this->recurInterval; $next = clone $estart; $next->setNthWeekday($weekday, $nth); if ($next->compareDateTime($after) < 0) { // We haven't made it past $after yet, try again. continue; } if ($this->hasRecurEnd() && $next->compareDateTime($this->recurEnd) > 0) { // We've gone past the end of recurrence; we can give up // now. return false; } // We have a candidate to return. break; } while (true); return $next; case self::RECUR_YEARLY_DATE: // Start with the start date of the event. $estart = clone $this->start; $after = clone $after; if ($after->month > $estart->month || $after->month == $estart->month && $after->mday > $estart->mday) { ++$after->year; $after->month = $estart->month; $after->mday = $estart->mday; } // Seperate case here for February 29th if ($estart->month == 2 && $estart->mday == 29) { while (!Horde_Date_Utils::isLeapYear($after->year)) { ++$after->year; } } // Adjust $estart to be the first candidate. $offset = $after->year - $estart->year; if ($offset > 0) { $offset = floor(($offset + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval; $estart->year += $offset; } // We've gone past the end of recurrence; give up. if ($this->recurCount && $offset >= $this->recurCount) { return false; } if ($this->hasRecurEnd() && $this->recurEnd->compareDateTime($estart) < 0) { return false; } return $estart; case self::RECUR_YEARLY_DAY: // Check count first. $dayofyear = $this->start->dayOfYear(); $count = ($after->year - $this->start->year) / $this->recurInterval + 1; if ($this->recurCount && ($count > $this->recurCount || $count == $this->recurCount && $after->dayOfYear() > $dayofyear)) { return false; } // Start with a rough interval. $estart = clone $this->start; $estart->year += floor($count - 1) * $this->recurInterval; // Now add the difference to the required day of year. $estart->mday += $dayofyear - $estart->dayOfYear(); // Add an interval if the estimation was wrong. if ($estart->compareDate($after) < 0) { $estart->year += $this->recurInterval; $estart->mday += $dayofyear - $estart->dayOfYear(); } // We've gone past the end of recurrence; give up. if ($this->hasRecurEnd() && $this->recurEnd->compareDateTime($estart) < 0) { return false; } return $estart; case self::RECUR_YEARLY_WEEKDAY: // Start with the start date of the event. $estart = clone $this->start; // What day of the week, and week of the month, do we recur on? if (isset($this->recurNthDay)) { $nth = $this->recurNthDay; $weekday = log($this->recurData, 2); } else { $nth = ceil($this->start->mday / 7); $weekday = $estart->dayOfWeek(); } // Adjust $estart to be the first candidate. $offset = floor(($after->year - $estart->year + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval; // Adjust our working date until it's after $after. $estart->year += $offset - $this->recurInterval; $count = $offset / $this->recurInterval; do { if ($this->recurCount && $count++ >= $this->recurCount) { return false; } $estart->year += $this->recurInterval; $next = clone $estart; $next->setNthWeekday($weekday, $nth); if ($next->compareDateTime($after) < 0) { // We haven't made it past $after yet, try again. continue; } if ($this->hasRecurEnd() && $next->compareDateTime($this->recurEnd) > 0) { // We've gone past the end of recurrence; we can give up // now. return false; } // We have a candidate to return. break; } while (true); return $next; } // fall-back to RDATE properties if (!empty($this->rdates)) { $next = clone $this->start; foreach ($this->rdates as $rdate) { $next->year = $rdate->year; $next->month = $rdate->month; $next->mday = $rdate->mday; if ($next->compareDateTime($after) > 0) { return $next; } } } // We didn't find anything, the recurType was bad, or something else // went wrong - return false. return false; }
/** * Adds an event to all the days it covers. * * @param array $result The current result list. * @param Kronolith_Event $event An event object. * @param Horde_Date $eventStart The event's start of the actual * recurrence. * @param Horde_Date $eventEnd The event's end of the actual * recurrence. * @param boolean $json Store the results of the events' * toJson() method? * @param Horde_Date $originalStart The actual starting time of a single * event spanning multiple days. * @param Horde_Date $originalEnd The actual ending time of a single * event spanning multiple days. */ public static function addCoverDates(&$results, $event, $eventStart, $eventEnd, $json, $originalStart = null, $originalEnd = null, Horde_Date $endDate = null) { $loopDate = new Horde_Date(array('month' => $eventStart->month, 'mday' => $eventStart->mday, 'year' => $eventStart->year)); $allDay = $event->isAllDay(); while ($loopDate->compareDateTime($eventEnd) <= 0 && $loopDate->compareDateTime($endDate) <= 0) { if (!$allDay || $loopDate->compareDateTime($eventEnd) != 0) { $addEvent = clone $event; if ($originalStart) { $addEvent->originalStart = $originalStart; } if ($originalEnd) { $addEvent->originalEnd = $originalEnd; } /* If this is the start day, set the start time to * the real start time, otherwise set it to * 00:00 */ if ($loopDate->compareDate($eventStart) != 0) { $addEvent->start = clone $loopDate; $addEvent->start->hour = $addEvent->start->min = $addEvent->start->sec = 0; $addEvent->first = false; } else { $addEvent->start = $eventStart; } /* If this is the end day, set the end time to the * real event end, otherwise set it to 23:59. */ if ($loopDate->compareDate($eventEnd) != 0) { $addEvent->end = clone $loopDate; $addEvent->end->hour = 23; $addEvent->end->min = $addEvent->end->sec = 59; $addEvent->last = false; } else { $addEvent->end = $eventEnd; } if ($addEvent->recurs() && $addEvent->recurrence->hasCompletion($loopDate->year, $loopDate->month, $loopDate->mday)) { $addEvent->status = Kronolith::STATUS_CANCELLED; } $results[$loopDate->dateString()][$addEvent->id] = $json ? $addEvent->toJson(array('all_day' => $allDay)) : $addEvent; } $loopDate->mday++; } }
/** * Parses TAF data. * * TAF KLGA 271734Z 271818 11007KT P6SM -RA SCT020 BKN200 * FM2300 14007KT P6SM SCT030 BKN150 * FM0400 VRB03KT P6SM SCT035 OVC080 PROB30 0509 P6SM -RA BKN035 * FM0900 VRB03KT 6SM -RA BR SCT015 OVC035 * TEMPO 1215 5SM -RA BR SCT009 BKN015 * BECMG 1517 16007KT P6SM NSW SCT015 BKN070 * * @param array $data The TAF encoded weather data, spilt on line endings. * * @return array An array of forecast data. Keys include: * - station: (string) The station identifier. * - dataRaw: (string) The raw TAF data. * - update: (timestamp) Timestamp of last update. * - validFrom: (Horde_Date) The valid FROM time. * - validTo: (Horde_Date) The valid TO time. * - time: (array) An array of Horde_Service_Weather_Period objects for * each available valid time provided by the TAF report. */ protected function _parse(array $data) { $tafCode = $this->_getTafCodes(); // Eliminate trailing information for ($i = 0; $i < sizeof($data); $i++) { if (strpos($data[$i], '=') !== false) { $data[$i] = substr($data[$i], 0, strpos($data[$i], '=')); $data = array_slice($data, 0, $i + 1); break; } } // Ok, we have correct data, start with parsing the first line for the last update $forecastData = array(); $forecastData['station'] = ''; $forecastData['dataRaw'] = implode(' ', $data); $forecastData['update'] = strtotime(trim($data[0]) . ' GMT'); $forecastData['updateRaw'] = trim($data[0]); // and prepare the rest for stepping through array_shift($data); $taf = explode(' ', preg_replace('/\\s{2,}/', ' ', implode(' ', $data))); // The timeperiod the data gets added to $fromTime = ''; // If we have FMCs (Forecast Meteorological Conditions), we need this $fmcCount = 0; // Pointer to the array we add the data to $pointer =& $forecastData; for ($i = 0; $i < sizeof($taf); $i++) { $taf[$i] = trim($taf[$i]); if (!strlen($taf[$i])) { continue; } // Init $result = array(); $resultVF = array(); $lresult = array(); $found = false; foreach ($tafCode as $key => $regexp) { // Check if current code matches current taf snippet if (($found = preg_match('/^' . $regexp . '$/i', $taf[$i], $result)) == true) { $insert = array(); switch ($key) { case 'station': $pointer['station'] = $result[0]; unset($tafCode['station']); break; case 'valid': $pointer['validRaw'] = $result[0]; // Generates the timeperiod the report is valid for list($year, $month, $day) = explode('-', gmdate('Y-m-d', $forecastData['update'])); // Date is in next month if ($result[1] < $day) { $month++; } $pointer['validFrom'] = new Horde_Date(array('hour' => $result[2], 'month' => $month, 'mday' => $result[1], 'year' => $year), 'GMT'); $pointer['validTo'] = new Horde_Date(array('hour' => $result[4], 'month' => $month, 'mday' => $result[3], 'year' => $year), 'GMT'); unset($tafCode['valid']); // Now the groups will start, so initialize the time groups $pointer['time'] = array(); $start_time = new Horde_Date(array('year' => $year, 'month' => $month, 'mday' => $result[1], 'hour' => $result[2]), 'UTC'); $fromTime = (string) $start_time; $pointer['time'][$fromTime] = array(); // Set pointer to the first timeperiod $pointer =& $pointer['time'][$fromTime]; break; case 'wind': if ($result[5] == 'KTS') { $result[5] = 'KT'; } $pointer['wind'] = round(Horde_Service_Weather::convertSpeed($result[2], $result[5], $this->_unitMap[self::UNIT_KEY_SPEED])); if ($result[1] == 'VAR' || $result[1] == 'VRB') { $pointer['windDegrees'] = Horde_Service_Weather_Translation::t('Variable'); $pointer['windDirection'] = Horde_Service_Weather_Translation::t('Variable'); } else { $pointer['windDegrees'] = $result[1]; $pointer['windDirection'] = Horde_Service_Weather::degToDirection($result[1]); } if (is_numeric($result[4])) { $pointer['windGust'] = round(Horde_Service_Weather::convertSpeed($result[4], $result[5], $this->_unitMap[self::UNIT_KEY_SPEED])); } if (isset($probability)) { $pointer['windProb'] = $probability; unset($probability); } unset($tafCode['wind']); break; case 'visFrac': // Possible fractional visibility here. // Check if it matches with the next TAF piece for visibility if (!isset($taf[$i + 1]) || !preg_match('/^' . $tafCode['visibility'] . '$/i', $result[1] . ' ' . $taf[$i + 1], $resultVF)) { // No next TAF piece available or not matching. $found = false; break; } // Match. Hand over result and advance TAF $key = 'visibility'; $result = $resultVF; $i++; // Fall through // Fall through case 'visibility': $pointer['visQualifier'] = Horde_Service_Weather_Translation::t('AT'); if (is_numeric($result[1]) && $result[1] == 9999) { // Upper limit of visibility range $visibility = Horde_Service_Weather::convertDistance(10, 'km', $this->_unitMap[self::UNIT_KEY_DISTANCE]); $pointer['visQualifier'] = Horde_Service_Weather_Translation::t('BEYOND'); } elseif (is_numeric($result[1])) { // 4-digit visibility in m $visibility = Horde_Service_Weather::convertDistance($result[1], 'm', $this->_unitMap[self::UNIT_KEY_DISTANCE]); } elseif (!isset($result[11]) || $result[11] != 'CAVOK') { if ($result[3] == 'M') { $pointer['visQualifier'] = Horde_Service_Weather_Translation::t('BELOW'); } elseif ($result[3] == 'P') { $pointer['visQualifier'] = Horde_Service_Weather_Translation::t('BEYOND'); } if (is_numeric($result[5])) { // visibility as one/two-digit number $visibility = Horde_Service_Weather::convertDistance($result[5], $result[10], $this->_unitMap[self::UNIT_KEY_DISTANCE]); } else { // the y/z part, add if we had a x part (see visibility1) if (is_numeric($result[7])) { $visibility = Horde_Service_Weather::convertDistance($result[7] + $result[8] / $result[9], $result[10], $this->_unitMap[self::UNIT_KEY_DISTANCE]); } else { $visibility = Horde_Service_Weather::convertDistance($result[8] / $result[9], $result[10], $this->_unitMap[self::UNIT_KEY_DISTANCE]); } } } else { $pointer['visQualifier'] = Horde_Service_Weather_Translation::t('BEYOND'); $visibility = Horde_Service_Weather::convertDistance(10, 'km', $this->_unitMap[self::UNIT_KEY_DISTANCE]); $pointer['clouds'] = array(array('amount' => Horde_Service_Weather_Translation::t('Clear below'), 'height' => 5000)); $pointer['condition'] = Horde_Service_Weather_Translation::t('No significant weather'); } if (isset($probability)) { $pointer['visProb'] = $probability; unset($probability); } $pointer['visibility'] = $visibility; break; case 'condition': // First some basic setups if (!isset($pointer['condition'])) { $pointer['condition'] = ''; } elseif (strlen($pointer['condition']) > 0) { $pointer['condition'] .= ','; } if (in_array(strtolower($result[0]), $this->_conditions)) { // First try matching the complete string $pointer['condition'] .= ' ' . $this->_conditions[strtolower($result[0])]; } else { // No luck, match part by part array_shift($result); $result = array_unique($result); foreach ($result as $condition) { if (strlen($condition) > 0) { $pointer['condition'] .= ' ' . $this->_conditions[strtolower($condition)]; } } } $pointer['condition'] = trim($pointer['condition']); if (isset($probability)) { $pointer['condition'] .= ' (' . $probability . '% ' . Horde_Service_Weather_Translation::t('probability') . ').'; unset($probability); } break; case 'clouds': if (!isset($pointer['clouds'])) { $pointer['clouds'] = array(); } if (sizeof($result) == 5) { // Only amount and height $cloud = array('amount' => $this->_clouds[strtolower($result[3])]); if ($result[4] == '///') { $cloud['height'] = Horde_Service_Weather_Translation::t('station level or below'); } else { $cloud['height'] = $result[4] * 100; } } elseif (sizeof($result) == 6) { // Amount, height and type $cloud = array('amount' => $this->_clouds[strtolower($result[3])], 'type' => $this->_clouds[strtolower($result[5])]); if ($result[4] == '///') { $cloud['height'] = Horde_Service_Weather_Translation::t('station level or below'); } else { $cloud['height'] = $result[4] * 100; } } else { // SKC or CLR or NSC $cloud = array('amount' => $this->_clouds[strtolower($result[0])]); } if (isset($probability)) { $cloud['prob'] = $probability; unset($probability); } $pointer['clouds'][] = $cloud; break; case 'windshear': // Parse windshear, if available if ($result[4] == 'KTS') { $result[4] = 'KT'; } $pointer['windshear'] = round(Horde_Service_Weather::convertSpeed($result[3], $result[4], $this->_unitMap[self::UNIT_KEY_SPEED])); $pointer['windshearHeight'] = $result[1] * 100; $pointer['windshearDegrees'] = $result[2]; $pointer['windshearDirection'] = Horde_Service_Weather::degToDirection($result[2]); break; case 'tempmax': $forecastData['temperatureHigh'] = Horde_Service_Weather::convertTemperature($result[1], 'c', $this->_unitMap[self::UNIT_KEY_TEMP]); break; case 'tempmin': // Parse max/min temperature $forecastData['temperatureLow'] = Horde_Service_Weather::convertTemperature($result[1], 'c', $this->_unitMap[self::UNIT_KEY_TEMP]); break; case 'tempmaxmin': $forecastData['temperatureHigh'] = Horde_Service_Weather::convertTemperature($result[1], 'c', $this->_unitMap[self::UNIT_KEY_TEMP]); $forecastData['temperatureLow'] = Horde_Service_Weather::convertTemperature($result[4], 'c', $this->_unitMap[self::UNIT_KEY_TEMP]); break; case 'from': // Next timeperiod is coming up, prepare array and // set pointer accordingly $fromTime = clone $start_time; if (sizeof($result) > 2) { // The ICAO way $fromTime->hour = $result[2]; $fromTime->min = $result[3]; } else { // The Australian way (Hey mates!) $fromTime->hour = $result[1]; } if ($start_time->compareDateTime($fromTime) >= 1) { $fromTime->mday++; } $fromTime = (string) $fromTime; $forecastData['time'][$fromTime] = array(); $fmcCount = 0; $pointer =& $forecastData['time'][$fromTime]; break; case 'fmc': // Test, if this is a probability for the next FMC if (isset($result[2]) && preg_match('/^BECMG|TEMPO$/i', $taf[$i + 1], $lresult)) { // Set type to BECMG or TEMPO $type = $lresult[0]; // Set probability $probability = $result[2]; // Now extract time for this group if (preg_match('/^(\\d{2})(\\d{2})$/i', $taf[$i + 2], $lresult)) { $from = clone $start_time; $from->hour = $lresult[1]; if ($start_time->compareDateTime($from) >= 1) { $from->mday++; } $to = clone $from; $to->hour = $lresult[2]; if ($start_time->compareDateTime($to) >= 1) { $to->mday++; } // As we now have type, probability and time for this FMC // from our TAF, increase field-counter $i += 2; } else { // No timegroup present, so just increase field-counter by one $i += 1; } } elseif (preg_match('/^(\\d{2})(\\d{2})\\/(\\d{2})(\\d{2})$/i', $taf[$i + 1], $lresult)) { // Normal group, set type and use extracted time $type = $result[1]; // Check for PROBdd if (isset($result[2])) { $probability = $result[2]; } $from = clone $start_time; $from->hour = $lresult[2]; if ($start_time->compareDateTime($from) >= 1) { $from->mday++; } $to = clone $from; $to->hour = $lresult[4]; if ($start_time->compareDateTime($to) >= 1) { $to->mday++; } // Same as above, we have a time for this FMC from our TAF, // increase field-counter $i += 1; } elseif (isset($result[2])) { // This is either a PROBdd or a malformed TAF with missing timegroup $probability = $result[2]; } // Handle the FMC, generate neccessary array if it's the first... if (isset($type)) { if (!isset($forecastData['time'][$fromTime]['fmc'])) { $forecastData['time'][$fromTime]['fmc'] = array(); } $forecastData['time'][$fromTime]['fmc'][$fmcCount] = array(); // ...and set pointer. $pointer =& $forecastData['time'][$fromTime]['fmc'][$fmcCount]; $fmcCount++; // Insert data $pointer['type'] = $type; unset($type); if (isset($from)) { $pointer['from'] = $from; $pointer['to'] = $to; unset($from, $to); } if (isset($probability)) { $pointer['probability'] = $probability; unset($probability); } } break; default: // Do nothing break; } if ($found) { break; } } } } return $forecastData; }