xref: /plugin/davcal/vendor/sabre/vobject/lib/Recur/EventIterator.php (revision a1a3b6794e0e143a4a8b51d3185ce2d339be61ab)
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