1*a1a3b679SAndreas Boehler<?php 2*a1a3b679SAndreas Boehler 3*a1a3b679SAndreas Boehlernamespace Sabre\VObject\Recur; 4*a1a3b679SAndreas Boehler 5*a1a3b679SAndreas Boehleruse InvalidArgumentException; 6*a1a3b679SAndreas Boehleruse DateTime; 7*a1a3b679SAndreas Boehleruse DateTimeZone; 8*a1a3b679SAndreas Boehleruse Sabre\VObject\Component; 9*a1a3b679SAndreas Boehleruse Sabre\VObject\Component\VEvent; 10*a1a3b679SAndreas Boehler 11*a1a3b679SAndreas Boehler/** 12*a1a3b679SAndreas Boehler * This class is used to determine new for a recurring event, when the next 13*a1a3b679SAndreas Boehler * events occur. 14*a1a3b679SAndreas Boehler * 15*a1a3b679SAndreas Boehler * This iterator may loop infinitely in the future, therefore it is important 16*a1a3b679SAndreas Boehler * that if you use this class, you set hard limits for the amount of iterations 17*a1a3b679SAndreas Boehler * you want to handle. 18*a1a3b679SAndreas Boehler * 19*a1a3b679SAndreas Boehler * Note that currently there is not full support for the entire iCalendar 20*a1a3b679SAndreas Boehler * specification, as it's very complex and contains a lot of permutations 21*a1a3b679SAndreas Boehler * that's not yet used very often in software. 22*a1a3b679SAndreas Boehler * 23*a1a3b679SAndreas Boehler * For the focus has been on features as they actually appear in Calendaring 24*a1a3b679SAndreas Boehler * software, but this may well get expanded as needed / on demand 25*a1a3b679SAndreas Boehler * 26*a1a3b679SAndreas Boehler * The following RRULE properties are supported 27*a1a3b679SAndreas Boehler * * UNTIL 28*a1a3b679SAndreas Boehler * * INTERVAL 29*a1a3b679SAndreas Boehler * * COUNT 30*a1a3b679SAndreas Boehler * * FREQ=DAILY 31*a1a3b679SAndreas Boehler * * BYDAY 32*a1a3b679SAndreas Boehler * * BYHOUR 33*a1a3b679SAndreas Boehler * * BYMONTH 34*a1a3b679SAndreas Boehler * * FREQ=WEEKLY 35*a1a3b679SAndreas Boehler * * BYDAY 36*a1a3b679SAndreas Boehler * * BYHOUR 37*a1a3b679SAndreas Boehler * * WKST 38*a1a3b679SAndreas Boehler * * FREQ=MONTHLY 39*a1a3b679SAndreas Boehler * * BYMONTHDAY 40*a1a3b679SAndreas Boehler * * BYDAY 41*a1a3b679SAndreas Boehler * * BYSETPOS 42*a1a3b679SAndreas Boehler * * FREQ=YEARLY 43*a1a3b679SAndreas Boehler * * BYMONTH 44*a1a3b679SAndreas Boehler * * BYMONTHDAY (only if BYMONTH is also set) 45*a1a3b679SAndreas Boehler * * BYDAY (only if BYMONTH is also set) 46*a1a3b679SAndreas Boehler * 47*a1a3b679SAndreas Boehler * Anything beyond this is 'undefined', which means that it may get ignored, or 48*a1a3b679SAndreas Boehler * you may get unexpected results. The effect is that in some applications the 49*a1a3b679SAndreas Boehler * specified recurrence may look incorrect, or is missing. 50*a1a3b679SAndreas Boehler * 51*a1a3b679SAndreas Boehler * The recurrence iterator also does not yet support THISANDFUTURE. 52*a1a3b679SAndreas Boehler * 53*a1a3b679SAndreas Boehler * @copyright Copyright (C) 2011-2015 fruux GmbH (https://fruux.com/). 54*a1a3b679SAndreas Boehler * @author Evert Pot (http://evertpot.com/) 55*a1a3b679SAndreas Boehler * @license http://sabre.io/license/ Modified BSD License 56*a1a3b679SAndreas Boehler */ 57*a1a3b679SAndreas Boehlerclass EventIterator implements \Iterator { 58*a1a3b679SAndreas Boehler 59*a1a3b679SAndreas Boehler /** 60*a1a3b679SAndreas Boehler * Reference timeZone for floating dates and times. 61*a1a3b679SAndreas Boehler * 62*a1a3b679SAndreas Boehler * @var DateTimeZone 63*a1a3b679SAndreas Boehler */ 64*a1a3b679SAndreas Boehler protected $timeZone; 65*a1a3b679SAndreas Boehler 66*a1a3b679SAndreas Boehler /** 67*a1a3b679SAndreas Boehler * True if we're iterating an all-day event. 68*a1a3b679SAndreas Boehler * 69*a1a3b679SAndreas Boehler * @var bool 70*a1a3b679SAndreas Boehler */ 71*a1a3b679SAndreas Boehler protected $allDay = false; 72*a1a3b679SAndreas Boehler 73*a1a3b679SAndreas Boehler /** 74*a1a3b679SAndreas Boehler * Creates the iterator 75*a1a3b679SAndreas Boehler * 76*a1a3b679SAndreas Boehler * There's three ways to set up the iterator. 77*a1a3b679SAndreas Boehler * 78*a1a3b679SAndreas Boehler * 1. You can pass a VCALENDAR component and a UID. 79*a1a3b679SAndreas Boehler * 2. You can pass an array of VEVENTs (all UIDS should match). 80*a1a3b679SAndreas Boehler * 3. You can pass a single VEVENT component. 81*a1a3b679SAndreas Boehler * 82*a1a3b679SAndreas Boehler * Only the second method is recomended. The other 1 and 3 will be removed 83*a1a3b679SAndreas Boehler * at some point in the future. 84*a1a3b679SAndreas Boehler * 85*a1a3b679SAndreas Boehler * The $uid parameter is only required for the first method. 86*a1a3b679SAndreas Boehler * 87*a1a3b679SAndreas Boehler * @param Component|array $input 88*a1a3b679SAndreas Boehler * @param string|null $uid 89*a1a3b679SAndreas Boehler * @param DateTimeZone $timeZone Reference timezone for floating dates and 90*a1a3b679SAndreas Boehler * times. 91*a1a3b679SAndreas Boehler */ 92*a1a3b679SAndreas Boehler public function __construct($input, $uid = null, DateTimeZone $timeZone = null) { 93*a1a3b679SAndreas Boehler 94*a1a3b679SAndreas Boehler if (is_null($this->timeZone)) { 95*a1a3b679SAndreas Boehler $timeZone = new DateTimeZone('UTC'); 96*a1a3b679SAndreas Boehler } 97*a1a3b679SAndreas Boehler $this->timeZone = $timeZone; 98*a1a3b679SAndreas Boehler 99*a1a3b679SAndreas Boehler if (is_array($input)) { 100*a1a3b679SAndreas Boehler $events = $input; 101*a1a3b679SAndreas Boehler } elseif ($input instanceof VEvent) { 102*a1a3b679SAndreas Boehler // Single instance mode. 103*a1a3b679SAndreas Boehler $events = array($input); 104*a1a3b679SAndreas Boehler } else { 105*a1a3b679SAndreas Boehler // Calendar + UID mode. 106*a1a3b679SAndreas Boehler $uid = (string)$uid; 107*a1a3b679SAndreas Boehler if (!$uid) { 108*a1a3b679SAndreas Boehler throw new InvalidArgumentException('The UID argument is required when a VCALENDAR is passed to this constructor'); 109*a1a3b679SAndreas Boehler } 110*a1a3b679SAndreas Boehler if (!isset($input->VEVENT)) { 111*a1a3b679SAndreas Boehler throw new InvalidArgumentException('No events found in this calendar'); 112*a1a3b679SAndreas Boehler } 113*a1a3b679SAndreas Boehler $events = $input->getByUID($uid); 114*a1a3b679SAndreas Boehler 115*a1a3b679SAndreas Boehler } 116*a1a3b679SAndreas Boehler 117*a1a3b679SAndreas Boehler foreach($events as $vevent) { 118*a1a3b679SAndreas Boehler 119*a1a3b679SAndreas Boehler if (!isset($vevent->{'RECURRENCE-ID'})) { 120*a1a3b679SAndreas Boehler 121*a1a3b679SAndreas Boehler $this->masterEvent = $vevent; 122*a1a3b679SAndreas Boehler 123*a1a3b679SAndreas Boehler } else { 124*a1a3b679SAndreas Boehler 125*a1a3b679SAndreas Boehler $this->exceptions[ 126*a1a3b679SAndreas Boehler $vevent->{'RECURRENCE-ID'}->getDateTime($this->timeZone)->getTimeStamp() 127*a1a3b679SAndreas Boehler ] = true; 128*a1a3b679SAndreas Boehler $this->overriddenEvents[] = $vevent; 129*a1a3b679SAndreas Boehler 130*a1a3b679SAndreas Boehler } 131*a1a3b679SAndreas Boehler 132*a1a3b679SAndreas Boehler } 133*a1a3b679SAndreas Boehler 134*a1a3b679SAndreas Boehler if (!$this->masterEvent) { 135*a1a3b679SAndreas Boehler // No base event was found. CalDAV does allow cases where only 136*a1a3b679SAndreas Boehler // overridden instances are stored. 137*a1a3b679SAndreas Boehler // 138*a1a3b679SAndreas Boehler // In this particular case, we're just going to grab the first 139*a1a3b679SAndreas Boehler // event and use that instead. This may not always give the 140*a1a3b679SAndreas Boehler // desired result. 141*a1a3b679SAndreas Boehler if (!count($this->overriddenEvents)) { 142*a1a3b679SAndreas Boehler throw new InvalidArgumentException('This VCALENDAR did not have an event with UID: ' . $uid); 143*a1a3b679SAndreas Boehler } 144*a1a3b679SAndreas Boehler $this->masterEvent = array_shift($this->overriddenEvents); 145*a1a3b679SAndreas Boehler } 146*a1a3b679SAndreas Boehler 147*a1a3b679SAndreas Boehler $this->startDate = $this->masterEvent->DTSTART->getDateTime($this->timeZone); 148*a1a3b679SAndreas Boehler $this->allDay = !$this->masterEvent->DTSTART->hasTime(); 149*a1a3b679SAndreas Boehler 150*a1a3b679SAndreas Boehler if (isset($this->masterEvent->EXDATE)) { 151*a1a3b679SAndreas Boehler 152*a1a3b679SAndreas Boehler foreach($this->masterEvent->EXDATE as $exDate) { 153*a1a3b679SAndreas Boehler 154*a1a3b679SAndreas Boehler foreach($exDate->getDateTimes($this->timeZone) as $dt) { 155*a1a3b679SAndreas Boehler $this->exceptions[$dt->getTimeStamp()] = true; 156*a1a3b679SAndreas Boehler } 157*a1a3b679SAndreas Boehler 158*a1a3b679SAndreas Boehler } 159*a1a3b679SAndreas Boehler 160*a1a3b679SAndreas Boehler } 161*a1a3b679SAndreas Boehler 162*a1a3b679SAndreas Boehler if (isset($this->masterEvent->DTEND)) { 163*a1a3b679SAndreas Boehler $this->eventDuration = 164*a1a3b679SAndreas Boehler $this->masterEvent->DTEND->getDateTime($this->timeZone)->getTimeStamp() - 165*a1a3b679SAndreas Boehler $this->startDate->getTimeStamp(); 166*a1a3b679SAndreas Boehler } elseif (isset($this->masterEvent->DURATION)) { 167*a1a3b679SAndreas Boehler $duration = $this->masterEvent->DURATION->getDateInterval(); 168*a1a3b679SAndreas Boehler $end = clone $this->startDate; 169*a1a3b679SAndreas Boehler $end->add($duration); 170*a1a3b679SAndreas Boehler $this->eventDuration = $end->getTimeStamp() - $this->startDate->getTimeStamp(); 171*a1a3b679SAndreas Boehler } elseif ($this->allDay) { 172*a1a3b679SAndreas Boehler $this->eventDuration = 3600 * 24; 173*a1a3b679SAndreas Boehler } else { 174*a1a3b679SAndreas Boehler $this->eventDuration = 0; 175*a1a3b679SAndreas Boehler } 176*a1a3b679SAndreas Boehler 177*a1a3b679SAndreas Boehler if (isset($this->masterEvent->RDATE)) { 178*a1a3b679SAndreas Boehler $this->recurIterator = new RDateIterator( 179*a1a3b679SAndreas Boehler $this->masterEvent->RDATE->getParts(), 180*a1a3b679SAndreas Boehler $this->startDate 181*a1a3b679SAndreas Boehler ); 182*a1a3b679SAndreas Boehler } elseif (isset($this->masterEvent->RRULE)) { 183*a1a3b679SAndreas Boehler $this->recurIterator = new RRuleIterator( 184*a1a3b679SAndreas Boehler $this->masterEvent->RRULE->getParts(), 185*a1a3b679SAndreas Boehler $this->startDate 186*a1a3b679SAndreas Boehler ); 187*a1a3b679SAndreas Boehler } else { 188*a1a3b679SAndreas Boehler $this->recurIterator = new RRuleIterator( 189*a1a3b679SAndreas Boehler array( 190*a1a3b679SAndreas Boehler 'FREQ' => 'DAILY', 191*a1a3b679SAndreas Boehler 'COUNT' => 1, 192*a1a3b679SAndreas Boehler ), 193*a1a3b679SAndreas Boehler $this->startDate 194*a1a3b679SAndreas Boehler ); 195*a1a3b679SAndreas Boehler } 196*a1a3b679SAndreas Boehler 197*a1a3b679SAndreas Boehler $this->rewind(); 198*a1a3b679SAndreas Boehler if (!$this->valid()) { 199*a1a3b679SAndreas Boehler throw new NoInstancesException('This recurrence rule does not generate any valid instances'); 200*a1a3b679SAndreas Boehler } 201*a1a3b679SAndreas Boehler 202*a1a3b679SAndreas Boehler } 203*a1a3b679SAndreas Boehler 204*a1a3b679SAndreas Boehler /** 205*a1a3b679SAndreas Boehler * Returns the date for the current position of the iterator. 206*a1a3b679SAndreas Boehler * 207*a1a3b679SAndreas Boehler * @return DateTime 208*a1a3b679SAndreas Boehler */ 209*a1a3b679SAndreas Boehler public function current() { 210*a1a3b679SAndreas Boehler 211*a1a3b679SAndreas Boehler if ($this->currentDate) { 212*a1a3b679SAndreas Boehler return clone $this->currentDate; 213*a1a3b679SAndreas Boehler } 214*a1a3b679SAndreas Boehler 215*a1a3b679SAndreas Boehler } 216*a1a3b679SAndreas Boehler 217*a1a3b679SAndreas Boehler /** 218*a1a3b679SAndreas Boehler * This method returns the start date for the current iteration of the 219*a1a3b679SAndreas Boehler * event. 220*a1a3b679SAndreas Boehler * 221*a1a3b679SAndreas Boehler * @return DateTime 222*a1a3b679SAndreas Boehler */ 223*a1a3b679SAndreas Boehler public function getDtStart() { 224*a1a3b679SAndreas Boehler 225*a1a3b679SAndreas Boehler if ($this->currentDate) { 226*a1a3b679SAndreas Boehler return clone $this->currentDate; 227*a1a3b679SAndreas Boehler } 228*a1a3b679SAndreas Boehler 229*a1a3b679SAndreas Boehler } 230*a1a3b679SAndreas Boehler 231*a1a3b679SAndreas Boehler /** 232*a1a3b679SAndreas Boehler * This method returns the end date for the current iteration of the 233*a1a3b679SAndreas Boehler * event. 234*a1a3b679SAndreas Boehler * 235*a1a3b679SAndreas Boehler * @return DateTime 236*a1a3b679SAndreas Boehler */ 237*a1a3b679SAndreas Boehler public function getDtEnd() { 238*a1a3b679SAndreas Boehler 239*a1a3b679SAndreas Boehler if (!$this->valid()) { 240*a1a3b679SAndreas Boehler return null; 241*a1a3b679SAndreas Boehler } 242*a1a3b679SAndreas Boehler $end = clone $this->currentDate; 243*a1a3b679SAndreas Boehler $end->modify('+' . $this->eventDuration . ' seconds'); 244*a1a3b679SAndreas Boehler return $end; 245*a1a3b679SAndreas Boehler 246*a1a3b679SAndreas Boehler } 247*a1a3b679SAndreas Boehler 248*a1a3b679SAndreas Boehler /** 249*a1a3b679SAndreas Boehler * Returns a VEVENT for the current iterations of the event. 250*a1a3b679SAndreas Boehler * 251*a1a3b679SAndreas Boehler * This VEVENT will have a recurrence id, and it's DTSTART and DTEND 252*a1a3b679SAndreas Boehler * altered. 253*a1a3b679SAndreas Boehler * 254*a1a3b679SAndreas Boehler * @return VEvent 255*a1a3b679SAndreas Boehler */ 256*a1a3b679SAndreas Boehler public function getEventObject() { 257*a1a3b679SAndreas Boehler 258*a1a3b679SAndreas Boehler if ($this->currentOverriddenEvent) { 259*a1a3b679SAndreas Boehler return $this->currentOverriddenEvent; 260*a1a3b679SAndreas Boehler } 261*a1a3b679SAndreas Boehler 262*a1a3b679SAndreas Boehler $event = clone $this->masterEvent; 263*a1a3b679SAndreas Boehler 264*a1a3b679SAndreas Boehler // Ignoring the following block, because PHPUnit's code coverage 265*a1a3b679SAndreas Boehler // ignores most of these lines, and this messes with our stats. 266*a1a3b679SAndreas Boehler // 267*a1a3b679SAndreas Boehler // @codeCoverageIgnoreStart 268*a1a3b679SAndreas Boehler unset( 269*a1a3b679SAndreas Boehler $event->RRULE, 270*a1a3b679SAndreas Boehler $event->EXDATE, 271*a1a3b679SAndreas Boehler $event->RDATE, 272*a1a3b679SAndreas Boehler $event->EXRULE, 273*a1a3b679SAndreas Boehler $event->{'RECURRENCE-ID'} 274*a1a3b679SAndreas Boehler ); 275*a1a3b679SAndreas Boehler // @codeCoverageIgnoreEnd 276*a1a3b679SAndreas Boehler 277*a1a3b679SAndreas Boehler $event->DTSTART->setDateTime($this->getDtStart(), $event->DTSTART->isFloating()); 278*a1a3b679SAndreas Boehler if (isset($event->DTEND)) { 279*a1a3b679SAndreas Boehler $event->DTEND->setDateTime($this->getDtEnd(), $event->DTEND->isFloating()); 280*a1a3b679SAndreas Boehler } 281*a1a3b679SAndreas Boehler // Including a RECURRENCE-ID to the object, unless this is the first 282*a1a3b679SAndreas Boehler // object. 283*a1a3b679SAndreas Boehler // 284*a1a3b679SAndreas Boehler // The inner recurIterator is always one step ahead, this is why we're 285*a1a3b679SAndreas Boehler // checking for the key being higher than 1. 286*a1a3b679SAndreas Boehler if ($this->recurIterator->key() > 1) { 287*a1a3b679SAndreas Boehler $recurid = clone $event->DTSTART; 288*a1a3b679SAndreas Boehler $recurid->name = 'RECURRENCE-ID'; 289*a1a3b679SAndreas Boehler $event->add($recurid); 290*a1a3b679SAndreas Boehler } 291*a1a3b679SAndreas Boehler return $event; 292*a1a3b679SAndreas Boehler 293*a1a3b679SAndreas Boehler } 294*a1a3b679SAndreas Boehler 295*a1a3b679SAndreas Boehler /** 296*a1a3b679SAndreas Boehler * Returns the current position of the iterator. 297*a1a3b679SAndreas Boehler * 298*a1a3b679SAndreas Boehler * This is for us simply a 0-based index. 299*a1a3b679SAndreas Boehler * 300*a1a3b679SAndreas Boehler * @return int 301*a1a3b679SAndreas Boehler */ 302*a1a3b679SAndreas Boehler public function key() { 303*a1a3b679SAndreas Boehler 304*a1a3b679SAndreas Boehler // The counter is always 1 ahead. 305*a1a3b679SAndreas Boehler return $this->counter - 1; 306*a1a3b679SAndreas Boehler 307*a1a3b679SAndreas Boehler } 308*a1a3b679SAndreas Boehler 309*a1a3b679SAndreas Boehler /** 310*a1a3b679SAndreas Boehler * This is called after next, to see if the iterator is still at a valid 311*a1a3b679SAndreas Boehler * position, or if it's at the end. 312*a1a3b679SAndreas Boehler * 313*a1a3b679SAndreas Boehler * @return bool 314*a1a3b679SAndreas Boehler */ 315*a1a3b679SAndreas Boehler public function valid() { 316*a1a3b679SAndreas Boehler 317*a1a3b679SAndreas Boehler return !!$this->currentDate; 318*a1a3b679SAndreas Boehler 319*a1a3b679SAndreas Boehler } 320*a1a3b679SAndreas Boehler 321*a1a3b679SAndreas Boehler /** 322*a1a3b679SAndreas Boehler * Sets the iterator back to the starting point. 323*a1a3b679SAndreas Boehler */ 324*a1a3b679SAndreas Boehler public function rewind() { 325*a1a3b679SAndreas Boehler 326*a1a3b679SAndreas Boehler $this->recurIterator->rewind(); 327*a1a3b679SAndreas Boehler // re-creating overridden event index. 328*a1a3b679SAndreas Boehler $index = array(); 329*a1a3b679SAndreas Boehler foreach($this->overriddenEvents as $key=>$event) { 330*a1a3b679SAndreas Boehler $stamp = $event->DTSTART->getDateTime($this->timeZone)->getTimeStamp(); 331*a1a3b679SAndreas Boehler $index[$stamp] = $key; 332*a1a3b679SAndreas Boehler } 333*a1a3b679SAndreas Boehler krsort($index); 334*a1a3b679SAndreas Boehler $this->counter = 0; 335*a1a3b679SAndreas Boehler $this->overriddenEventsIndex = $index; 336*a1a3b679SAndreas Boehler $this->currentOverriddenEvent = null; 337*a1a3b679SAndreas Boehler 338*a1a3b679SAndreas Boehler $this->nextDate = null; 339*a1a3b679SAndreas Boehler $this->currentDate = clone $this->startDate; 340*a1a3b679SAndreas Boehler 341*a1a3b679SAndreas Boehler $this->next(); 342*a1a3b679SAndreas Boehler 343*a1a3b679SAndreas Boehler } 344*a1a3b679SAndreas Boehler 345*a1a3b679SAndreas Boehler /** 346*a1a3b679SAndreas Boehler * Advances the iterator with one step. 347*a1a3b679SAndreas Boehler * 348*a1a3b679SAndreas Boehler * @return void 349*a1a3b679SAndreas Boehler */ 350*a1a3b679SAndreas Boehler public function next() { 351*a1a3b679SAndreas Boehler 352*a1a3b679SAndreas Boehler $this->currentOverriddenEvent = null; 353*a1a3b679SAndreas Boehler $this->counter++; 354*a1a3b679SAndreas Boehler if ($this->nextDate) { 355*a1a3b679SAndreas Boehler // We had a stored value. 356*a1a3b679SAndreas Boehler $nextDate = $this->nextDate; 357*a1a3b679SAndreas Boehler $this->nextDate = null; 358*a1a3b679SAndreas Boehler } else { 359*a1a3b679SAndreas Boehler // We need to ask rruleparser for the next date. 360*a1a3b679SAndreas Boehler // We need to do this until we find a date that's not in the 361*a1a3b679SAndreas Boehler // exception list. 362*a1a3b679SAndreas Boehler do { 363*a1a3b679SAndreas Boehler if (!$this->recurIterator->valid()) { 364*a1a3b679SAndreas Boehler $nextDate = null; 365*a1a3b679SAndreas Boehler break; 366*a1a3b679SAndreas Boehler } 367*a1a3b679SAndreas Boehler $nextDate = $this->recurIterator->current(); 368*a1a3b679SAndreas Boehler $this->recurIterator->next(); 369*a1a3b679SAndreas Boehler } while(isset($this->exceptions[$nextDate->getTimeStamp()])); 370*a1a3b679SAndreas Boehler 371*a1a3b679SAndreas Boehler } 372*a1a3b679SAndreas Boehler 373*a1a3b679SAndreas Boehler 374*a1a3b679SAndreas Boehler // $nextDate now contains what rrule thinks is the next one, but an 375*a1a3b679SAndreas Boehler // overridden event may cut ahead. 376*a1a3b679SAndreas Boehler if ($this->overriddenEventsIndex) { 377*a1a3b679SAndreas Boehler 378*a1a3b679SAndreas Boehler $offset = end($this->overriddenEventsIndex); 379*a1a3b679SAndreas Boehler $timestamp = key($this->overriddenEventsIndex); 380*a1a3b679SAndreas Boehler if (!$nextDate || $timestamp < $nextDate->getTimeStamp()) { 381*a1a3b679SAndreas Boehler // Overridden event comes first. 382*a1a3b679SAndreas Boehler $this->currentOverriddenEvent = $this->overriddenEvents[$offset]; 383*a1a3b679SAndreas Boehler 384*a1a3b679SAndreas Boehler // Putting the rrule next date aside. 385*a1a3b679SAndreas Boehler $this->nextDate = $nextDate; 386*a1a3b679SAndreas Boehler $this->currentDate = $this->currentOverriddenEvent->DTSTART->getDateTime($this->timeZone); 387*a1a3b679SAndreas Boehler 388*a1a3b679SAndreas Boehler // Ensuring that this item will only be used once. 389*a1a3b679SAndreas Boehler array_pop($this->overriddenEventsIndex); 390*a1a3b679SAndreas Boehler 391*a1a3b679SAndreas Boehler // Exit point! 392*a1a3b679SAndreas Boehler return; 393*a1a3b679SAndreas Boehler 394*a1a3b679SAndreas Boehler } 395*a1a3b679SAndreas Boehler 396*a1a3b679SAndreas Boehler } 397*a1a3b679SAndreas Boehler 398*a1a3b679SAndreas Boehler $this->currentDate = $nextDate; 399*a1a3b679SAndreas Boehler 400*a1a3b679SAndreas Boehler } 401*a1a3b679SAndreas Boehler 402*a1a3b679SAndreas Boehler /** 403*a1a3b679SAndreas Boehler * Quickly jump to a date in the future. 404*a1a3b679SAndreas Boehler * 405*a1a3b679SAndreas Boehler * @param DateTime $dateTime 406*a1a3b679SAndreas Boehler */ 407*a1a3b679SAndreas Boehler public function fastForward(DateTime $dateTime) { 408*a1a3b679SAndreas Boehler 409*a1a3b679SAndreas Boehler while($this->valid() && $this->getDtEnd() < $dateTime ) { 410*a1a3b679SAndreas Boehler $this->next(); 411*a1a3b679SAndreas Boehler } 412*a1a3b679SAndreas Boehler 413*a1a3b679SAndreas Boehler } 414*a1a3b679SAndreas Boehler 415*a1a3b679SAndreas Boehler /** 416*a1a3b679SAndreas Boehler * Returns true if this recurring event never ends. 417*a1a3b679SAndreas Boehler * 418*a1a3b679SAndreas Boehler * @return bool 419*a1a3b679SAndreas Boehler */ 420*a1a3b679SAndreas Boehler public function isInfinite() { 421*a1a3b679SAndreas Boehler 422*a1a3b679SAndreas Boehler return $this->recurIterator->isInfinite(); 423*a1a3b679SAndreas Boehler 424*a1a3b679SAndreas Boehler } 425*a1a3b679SAndreas Boehler 426*a1a3b679SAndreas Boehler /** 427*a1a3b679SAndreas Boehler * RRULE parser 428*a1a3b679SAndreas Boehler * 429*a1a3b679SAndreas Boehler * @var RRuleIterator 430*a1a3b679SAndreas Boehler */ 431*a1a3b679SAndreas Boehler protected $recurIterator; 432*a1a3b679SAndreas Boehler 433*a1a3b679SAndreas Boehler /** 434*a1a3b679SAndreas Boehler * The duration, in seconds, of the master event. 435*a1a3b679SAndreas Boehler * 436*a1a3b679SAndreas Boehler * We use this to calculate the DTEND for subsequent events. 437*a1a3b679SAndreas Boehler */ 438*a1a3b679SAndreas Boehler protected $eventDuration; 439*a1a3b679SAndreas Boehler 440*a1a3b679SAndreas Boehler /** 441*a1a3b679SAndreas Boehler * A reference to the main (master) event. 442*a1a3b679SAndreas Boehler * 443*a1a3b679SAndreas Boehler * @var VEVENT 444*a1a3b679SAndreas Boehler */ 445*a1a3b679SAndreas Boehler protected $masterEvent; 446*a1a3b679SAndreas Boehler 447*a1a3b679SAndreas Boehler /** 448*a1a3b679SAndreas Boehler * List of overridden events. 449*a1a3b679SAndreas Boehler * 450*a1a3b679SAndreas Boehler * @var array 451*a1a3b679SAndreas Boehler */ 452*a1a3b679SAndreas Boehler protected $overriddenEvents = array(); 453*a1a3b679SAndreas Boehler 454*a1a3b679SAndreas Boehler /** 455*a1a3b679SAndreas Boehler * Overridden event index. 456*a1a3b679SAndreas Boehler * 457*a1a3b679SAndreas Boehler * Key is timestamp, value is the index of the item in the $overriddenEvent 458*a1a3b679SAndreas Boehler * property. 459*a1a3b679SAndreas Boehler * 460*a1a3b679SAndreas Boehler * @var array 461*a1a3b679SAndreas Boehler */ 462*a1a3b679SAndreas Boehler protected $overriddenEventsIndex; 463*a1a3b679SAndreas Boehler 464*a1a3b679SAndreas Boehler /** 465*a1a3b679SAndreas Boehler * A list of recurrence-id's that are either part of EXDATE, or are 466*a1a3b679SAndreas Boehler * overridden. 467*a1a3b679SAndreas Boehler * 468*a1a3b679SAndreas Boehler * @var array 469*a1a3b679SAndreas Boehler */ 470*a1a3b679SAndreas Boehler protected $exceptions = array(); 471*a1a3b679SAndreas Boehler 472*a1a3b679SAndreas Boehler /** 473*a1a3b679SAndreas Boehler * Internal event counter 474*a1a3b679SAndreas Boehler * 475*a1a3b679SAndreas Boehler * @var int 476*a1a3b679SAndreas Boehler */ 477*a1a3b679SAndreas Boehler protected $counter; 478*a1a3b679SAndreas Boehler 479*a1a3b679SAndreas Boehler /** 480*a1a3b679SAndreas Boehler * The very start of the iteration process. 481*a1a3b679SAndreas Boehler * 482*a1a3b679SAndreas Boehler * @var DateTime 483*a1a3b679SAndreas Boehler */ 484*a1a3b679SAndreas Boehler protected $startDate; 485*a1a3b679SAndreas Boehler 486*a1a3b679SAndreas Boehler /** 487*a1a3b679SAndreas Boehler * Where we are currently in the iteration process 488*a1a3b679SAndreas Boehler * 489*a1a3b679SAndreas Boehler * @var DateTime 490*a1a3b679SAndreas Boehler */ 491*a1a3b679SAndreas Boehler protected $currentDate; 492*a1a3b679SAndreas Boehler 493*a1a3b679SAndreas Boehler /** 494*a1a3b679SAndreas Boehler * The next date from the rrule parser. 495*a1a3b679SAndreas Boehler * 496*a1a3b679SAndreas Boehler * Sometimes we need to temporary store the next date, because an 497*a1a3b679SAndreas Boehler * overridden event came before. 498*a1a3b679SAndreas Boehler * 499*a1a3b679SAndreas Boehler * @var DateTime 500*a1a3b679SAndreas Boehler */ 501*a1a3b679SAndreas Boehler protected $nextDate; 502*a1a3b679SAndreas Boehler 503*a1a3b679SAndreas Boehler} 504