Mercurial > hg > rc1
diff vendor/sabre/vobject/lib/Recur/EventIterator.php @ 7:430dbd5346f7
vendor sabre as distributed
| author | Charlie Root |
|---|---|
| date | Sat, 13 Jan 2018 09:06:10 -0500 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/sabre/vobject/lib/Recur/EventIterator.php Sat Jan 13 09:06:10 2018 -0500 @@ -0,0 +1,497 @@ +<?php + +namespace Sabre\VObject\Recur; + +use InvalidArgumentException; +use DateTime; +use DateTimeZone; +use Sabre\VObject\Component; +use Sabre\VObject\Component\VEvent; + +/** + * This class is used to determine new for a recurring event, when the next + * events occur. + * + * This iterator may loop infinitely in the future, therefore it is important + * that if you use this class, you set hard limits for the amount of iterations + * you want to handle. + * + * Note that currently there is not full support for the entire iCalendar + * specification, as it's very complex and contains a lot of permutations + * that's not yet used very often in software. + * + * For the focus has been on features as they actually appear in Calendaring + * software, but this may well get expanded as needed / on demand + * + * The following RRULE properties are supported + * * UNTIL + * * INTERVAL + * * COUNT + * * FREQ=DAILY + * * BYDAY + * * BYHOUR + * * BYMONTH + * * FREQ=WEEKLY + * * BYDAY + * * BYHOUR + * * WKST + * * FREQ=MONTHLY + * * BYMONTHDAY + * * BYDAY + * * BYSETPOS + * * FREQ=YEARLY + * * BYMONTH + * * BYMONTHDAY (only if BYMONTH is also set) + * * BYDAY (only if BYMONTH is also set) + * + * Anything beyond this is 'undefined', which means that it may get ignored, or + * you may get unexpected results. The effect is that in some applications the + * specified recurrence may look incorrect, or is missing. + * + * The recurrence iterator also does not yet support THISANDFUTURE. + * + * @copyright Copyright (C) 2011-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class EventIterator implements \Iterator { + + /** + * Reference timeZone for floating dates and times. + * + * @var DateTimeZone + */ + protected $timeZone; + + /** + * True if we're iterating an all-day event. + * + * @var bool + */ + protected $allDay = false; + + /** + * Creates the iterator + * + * You should pass a VCALENDAR component, as well as the UID of the event + * we're going to traverse. + * + * @param Component $vcal + * @param string|null $uid + * @param DateTimeZone $timeZone Reference timezone for floating dates and + * times. + */ + public function __construct(Component $vcal, $uid = null, DateTimeZone $timeZone = null) { + + if (is_null($this->timeZone)) { + $timeZone = new DateTimeZone('UTC'); + } + $this->timeZone = $timeZone; + + if ($vcal instanceof VEvent) { + // Single instance mode. + $events = array($vcal); + } else { + $uid = (string)$uid; + if (!$uid) { + throw new InvalidArgumentException('The UID argument is required when a VCALENDAR is passed to this constructor'); + } + if (!isset($vcal->VEVENT)) { + throw new InvalidArgumentException('No events found in this calendar'); + } + $events = array(); + foreach($vcal->VEVENT as $event) { + if ($event->uid->getValue() === $uid) { + $events[] = $event; + } + } + + } + + foreach($events as $vevent) { + + if (!isset($vevent->{'RECURRENCE-ID'})) { + + $this->masterEvent = $vevent; + + } else { + + $this->exceptions[ + $vevent->{'RECURRENCE-ID'}->getDateTime($this->timeZone)->getTimeStamp() + ] = true; + $this->overriddenEvents[] = $vevent; + + } + + } + + if (!$this->masterEvent) { + // No base event was found. CalDAV does allow cases where only + // overridden instances are stored. + // + // In this particular case, we're just going to grab the first + // event and use that instead. This may not always give the + // desired result. + if (!count($this->overriddenEvents)) { + throw new InvalidArgumentException('This VCALENDAR did not have an event with UID: ' . $uid); + } + $this->masterEvent = array_shift($this->overriddenEvents); + } + + $this->startDate = $this->masterEvent->DTSTART->getDateTime($this->timeZone); + $this->allDay = !$this->masterEvent->DTSTART->hasTime(); + + if (isset($this->masterEvent->EXDATE)) { + + foreach($this->masterEvent->EXDATE as $exDate) { + + foreach($exDate->getDateTimes($this->timeZone) as $dt) { + $this->exceptions[$dt->getTimeStamp()] = true; + } + + } + + } + + if (isset($this->masterEvent->DTEND)) { + $this->eventDuration = + $this->masterEvent->DTEND->getDateTime($this->timeZone)->getTimeStamp() - + $this->startDate->getTimeStamp(); + } elseif (isset($this->masterEvent->DURATION)) { + $duration = $this->masterEvent->DURATION->getDateInterval(); + $end = clone $this->startDate; + $end->add($duration); + $this->eventDuration = $end->getTimeStamp() - $this->startDate->getTimeStamp(); + } elseif ($this->allDay) { + $this->eventDuration = 3600 * 24; + } else { + $this->eventDuration = 0; + } + + if (isset($this->masterEvent->RDATE)) { + $this->recurIterator = new RDateIterator( + $this->masterEvent->RDATE->getParts(), + $this->startDate + ); + } elseif (isset($this->masterEvent->RRULE)) { + $this->recurIterator = new RRuleIterator( + $this->masterEvent->RRULE->getParts(), + $this->startDate + ); + } else { + $this->recurIterator = new RRuleIterator( + array( + 'FREQ' => 'DAILY', + 'COUNT' => 1, + ), + $this->startDate + ); + } + + $this->rewind(); + if (!$this->valid()) { + throw new NoInstancesException('This recurrence rule does not generate any valid instances'); + } + + } + + /** + * Returns the date for the current position of the iterator. + * + * @return DateTime + */ + public function current() { + + if ($this->currentDate) { + return clone $this->currentDate; + } + + } + + /** + * This method returns the start date for the current iteration of the + * event. + * + * @return DateTime + */ + public function getDtStart() { + + if ($this->currentDate) { + return clone $this->currentDate; + } + + } + + /** + * This method returns the end date for the current iteration of the + * event. + * + * @return DateTime + */ + public function getDtEnd() { + + if (!$this->valid()) { + return null; + } + $end = clone $this->currentDate; + $end->modify('+' . $this->eventDuration . ' seconds'); + return $end; + + } + + /** + * Returns a VEVENT for the current iterations of the event. + * + * This VEVENT will have a recurrence id, and it's DTSTART and DTEND + * altered. + * + * @return VEvent + */ + public function getEventObject() { + + if ($this->currentOverriddenEvent) { + return $this->currentOverriddenEvent; + } + + $event = clone $this->masterEvent; + + // Ignoring the following block, because PHPUnit's code coverage + // ignores most of these lines, and this messes with our stats. + // + // @codeCoverageIgnoreStart + unset( + $event->RRULE, + $event->EXDATE, + $event->RDATE, + $event->EXRULE, + $event->{'RECURRENCE-ID'} + ); + // @codeCoverageIgnoreEnd + + $event->DTSTART->setDateTime($this->getDtStart()); + if (isset($event->DTEND)) { + $event->DTEND->setDateTime($this->getDtEnd()); + } + // Including a RECURRENCE-ID to the object, unless this is the first + // object. + // + // The inner recurIterator is always one step ahead, this is why we're + // checking for the key being higher than 1. + if ($this->recurIterator->key() > 1) { + $recurid = clone $event->DTSTART; + $recurid->name = 'RECURRENCE-ID'; + $event->add($recurid); + } + return $event; + + } + + /** + * Returns the current position of the iterator. + * + * This is for us simply a 0-based index. + * + * @return int + */ + public function key() { + + // The counter is always 1 ahead. + return $this->counter - 1; + + } + + /** + * This is called after next, to see if the iterator is still at a valid + * position, or if it's at the end. + * + * @return bool + */ + public function valid() { + + return !!$this->currentDate; + + } + + /** + * Sets the iterator back to the starting point. + */ + public function rewind() { + + $this->recurIterator->rewind(); + // re-creating overridden event index. + $index = array(); + foreach($this->overriddenEvents as $key=>$event) { + $stamp = $event->DTSTART->getDateTime($this->timeZone)->getTimeStamp(); + $index[$stamp] = $key; + } + krsort($index); + $this->counter = 0; + $this->overriddenEventsIndex = $index; + $this->currentOverriddenEvent = null; + + $this->nextDate = null; + $this->currentDate = clone $this->startDate; + + $this->next(); + + } + + /** + * Advances the iterator with one step. + * + * @return void + */ + public function next() { + + $this->currentOverriddenEvent = null; + $this->counter++; + if ($this->nextDate) { + // We had a stored value. + $nextDate = $this->nextDate; + $this->nextDate = null; + } else { + // We need to ask rruleparser for the next date. + // We need to do this until we find a date that's not in the + // exception list. + do { + if (!$this->recurIterator->valid()) { + $nextDate = null; + break; + } + $nextDate = $this->recurIterator->current(); + $this->recurIterator->next(); + } while(isset($this->exceptions[$nextDate->getTimeStamp()])); + + } + + + // $nextDate now contains what rrule thinks is the next one, but an + // overridden event may cut ahead. + if ($this->overriddenEventsIndex) { + + $offset = end($this->overriddenEventsIndex); + $timestamp = key($this->overriddenEventsIndex); + if (!$nextDate || $timestamp < $nextDate->getTimeStamp()) { + // Overridden event comes first. + $this->currentOverriddenEvent = $this->overriddenEvents[$offset]; + + // Putting the rrule next date aside. + $this->nextDate = $nextDate; + $this->currentDate = $this->currentOverriddenEvent->DTSTART->getDateTime($this->timeZone); + + // Ensuring that this item will only be used once. + array_pop($this->overriddenEventsIndex); + + // Exit point! + return; + + } + + } + + $this->currentDate = $nextDate; + + } + + /** + * Quickly jump to a date in the future. + * + * @param DateTime $dateTime + */ + public function fastForward(DateTime $dateTime) { + + while($this->valid() && $this->getDtEnd() < $dateTime ) { + $this->next(); + } + + } + + /** + * Returns true if this recurring event never ends. + * + * @return bool + */ + public function isInfinite() { + + return $this->recurIterator->isInfinite(); + + } + + /** + * RRULE parser + * + * @var RRuleIterator + */ + protected $recurIterator; + + /** + * The duration, in seconds, of the master event. + * + * We use this to calculate the DTEND for subsequent events. + */ + protected $eventDuration; + + /** + * A reference to the main (master) event. + * + * @var VEVENT + */ + protected $masterEvent; + + /** + * List of overridden events. + * + * @var array + */ + protected $overriddenEvents = array(); + + /** + * Overridden event index. + * + * Key is timestamp, value is the index of the item in the $overriddenEvent + * property. + * + * @var array + */ + protected $overriddenEventsIndex; + + /** + * A list of recurrence-id's that are either part of EXDATE, or are + * overridden. + * + * @var array + */ + protected $exceptions = array(); + + /** + * Internal event counter + * + * @var int + */ + protected $counter; + + /** + * The very start of the iteration process. + * + * @var DateTime + */ + protected $startDate; + + /** + * Where we are currently in the iteration process + * + * @var DateTime + */ + protected $currentDate; + + /** + * The next date from the rrule parser. + * + * Sometimes we need to temporary store the next date, because an + * overridden event came before. + * + * @var DateTime + */ + protected $nextDate; + +}
