xref: /plugin/davcal/vendor/sabre/vobject/lib/Recur/RRuleIterator.php (revision a1a3b6794e0e143a4a8b51d3185ce2d339be61ab)
1*a1a3b679SAndreas Boehler<?php
2*a1a3b679SAndreas Boehler
3*a1a3b679SAndreas Boehlernamespace Sabre\VObject\Recur;
4*a1a3b679SAndreas Boehler
5*a1a3b679SAndreas Boehleruse DateTime;
6*a1a3b679SAndreas Boehleruse InvalidArgumentException;
7*a1a3b679SAndreas Boehleruse Iterator;
8*a1a3b679SAndreas Boehleruse Sabre\VObject\DateTimeParser;
9*a1a3b679SAndreas Boehleruse Sabre\VObject\Property;
10*a1a3b679SAndreas Boehler
11*a1a3b679SAndreas Boehler
12*a1a3b679SAndreas Boehler/**
13*a1a3b679SAndreas Boehler * RRuleParser
14*a1a3b679SAndreas Boehler *
15*a1a3b679SAndreas Boehler * This class receives an RRULE string, and allows you to iterate to get a list
16*a1a3b679SAndreas Boehler * of dates in that recurrence.
17*a1a3b679SAndreas Boehler *
18*a1a3b679SAndreas Boehler * For instance, passing: FREQ=DAILY;LIMIT=5 will cause the iterator to contain
19*a1a3b679SAndreas Boehler * 5 items, one for each day.
20*a1a3b679SAndreas Boehler *
21*a1a3b679SAndreas Boehler * @copyright Copyright (C) 2011-2015 fruux GmbH (https://fruux.com/).
22*a1a3b679SAndreas Boehler * @author Evert Pot (http://evertpot.com/)
23*a1a3b679SAndreas Boehler * @license http://sabre.io/license/ Modified BSD License
24*a1a3b679SAndreas Boehler */
25*a1a3b679SAndreas Boehlerclass RRuleIterator implements Iterator {
26*a1a3b679SAndreas Boehler
27*a1a3b679SAndreas Boehler    /**
28*a1a3b679SAndreas Boehler     * Creates the Iterator
29*a1a3b679SAndreas Boehler     *
30*a1a3b679SAndreas Boehler     * @param string|array $rrule
31*a1a3b679SAndreas Boehler     * @param DateTime $start
32*a1a3b679SAndreas Boehler     */
33*a1a3b679SAndreas Boehler    public function __construct($rrule, DateTime $start) {
34*a1a3b679SAndreas Boehler
35*a1a3b679SAndreas Boehler        $this->startDate = $start;
36*a1a3b679SAndreas Boehler        $this->parseRRule($rrule);
37*a1a3b679SAndreas Boehler        $this->currentDate = clone $this->startDate;
38*a1a3b679SAndreas Boehler
39*a1a3b679SAndreas Boehler    }
40*a1a3b679SAndreas Boehler
41*a1a3b679SAndreas Boehler    /* Implementation of the Iterator interface {{{ */
42*a1a3b679SAndreas Boehler
43*a1a3b679SAndreas Boehler    public function current() {
44*a1a3b679SAndreas Boehler
45*a1a3b679SAndreas Boehler        if (!$this->valid()) return null;
46*a1a3b679SAndreas Boehler        return clone $this->currentDate;
47*a1a3b679SAndreas Boehler
48*a1a3b679SAndreas Boehler    }
49*a1a3b679SAndreas Boehler
50*a1a3b679SAndreas Boehler    /**
51*a1a3b679SAndreas Boehler     * Returns the current item number
52*a1a3b679SAndreas Boehler     *
53*a1a3b679SAndreas Boehler     * @return int
54*a1a3b679SAndreas Boehler     */
55*a1a3b679SAndreas Boehler    public function key() {
56*a1a3b679SAndreas Boehler
57*a1a3b679SAndreas Boehler        return $this->counter;
58*a1a3b679SAndreas Boehler
59*a1a3b679SAndreas Boehler    }
60*a1a3b679SAndreas Boehler
61*a1a3b679SAndreas Boehler    /**
62*a1a3b679SAndreas Boehler     * Returns whether the current item is a valid item for the recurrence
63*a1a3b679SAndreas Boehler     * iterator. This will return false if we've gone beyond the UNTIL or COUNT
64*a1a3b679SAndreas Boehler     * statements.
65*a1a3b679SAndreas Boehler     *
66*a1a3b679SAndreas Boehler     * @return bool
67*a1a3b679SAndreas Boehler     */
68*a1a3b679SAndreas Boehler    public function valid() {
69*a1a3b679SAndreas Boehler
70*a1a3b679SAndreas Boehler        if (!is_null($this->count)) {
71*a1a3b679SAndreas Boehler            return $this->counter < $this->count;
72*a1a3b679SAndreas Boehler        }
73*a1a3b679SAndreas Boehler        return is_null($this->until) || $this->currentDate <= $this->until;
74*a1a3b679SAndreas Boehler
75*a1a3b679SAndreas Boehler    }
76*a1a3b679SAndreas Boehler
77*a1a3b679SAndreas Boehler    /**
78*a1a3b679SAndreas Boehler     * Resets the iterator
79*a1a3b679SAndreas Boehler     *
80*a1a3b679SAndreas Boehler     * @return void
81*a1a3b679SAndreas Boehler     */
82*a1a3b679SAndreas Boehler    public function rewind() {
83*a1a3b679SAndreas Boehler
84*a1a3b679SAndreas Boehler        $this->currentDate = clone $this->startDate;
85*a1a3b679SAndreas Boehler        $this->counter = 0;
86*a1a3b679SAndreas Boehler
87*a1a3b679SAndreas Boehler    }
88*a1a3b679SAndreas Boehler
89*a1a3b679SAndreas Boehler    /**
90*a1a3b679SAndreas Boehler     * Goes on to the next iteration
91*a1a3b679SAndreas Boehler     *
92*a1a3b679SAndreas Boehler     * @return void
93*a1a3b679SAndreas Boehler     */
94*a1a3b679SAndreas Boehler    public function next() {
95*a1a3b679SAndreas Boehler
96*a1a3b679SAndreas Boehler        $previousStamp = $this->currentDate->getTimeStamp();
97*a1a3b679SAndreas Boehler
98*a1a3b679SAndreas Boehler        // Otherwise, we find the next event in the normal RRULE
99*a1a3b679SAndreas Boehler        // sequence.
100*a1a3b679SAndreas Boehler        switch($this->frequency) {
101*a1a3b679SAndreas Boehler
102*a1a3b679SAndreas Boehler            case 'hourly' :
103*a1a3b679SAndreas Boehler                $this->nextHourly();
104*a1a3b679SAndreas Boehler                break;
105*a1a3b679SAndreas Boehler
106*a1a3b679SAndreas Boehler            case 'daily' :
107*a1a3b679SAndreas Boehler                $this->nextDaily();
108*a1a3b679SAndreas Boehler                break;
109*a1a3b679SAndreas Boehler
110*a1a3b679SAndreas Boehler            case 'weekly' :
111*a1a3b679SAndreas Boehler                $this->nextWeekly();
112*a1a3b679SAndreas Boehler                break;
113*a1a3b679SAndreas Boehler
114*a1a3b679SAndreas Boehler            case 'monthly' :
115*a1a3b679SAndreas Boehler                $this->nextMonthly();
116*a1a3b679SAndreas Boehler                break;
117*a1a3b679SAndreas Boehler
118*a1a3b679SAndreas Boehler            case 'yearly' :
119*a1a3b679SAndreas Boehler                $this->nextYearly();
120*a1a3b679SAndreas Boehler                break;
121*a1a3b679SAndreas Boehler
122*a1a3b679SAndreas Boehler        }
123*a1a3b679SAndreas Boehler        $this->counter++;
124*a1a3b679SAndreas Boehler
125*a1a3b679SAndreas Boehler    }
126*a1a3b679SAndreas Boehler
127*a1a3b679SAndreas Boehler    /* End of Iterator implementation }}} */
128*a1a3b679SAndreas Boehler
129*a1a3b679SAndreas Boehler    /**
130*a1a3b679SAndreas Boehler     * Returns true if this recurring event never ends.
131*a1a3b679SAndreas Boehler     *
132*a1a3b679SAndreas Boehler     * @return bool
133*a1a3b679SAndreas Boehler     */
134*a1a3b679SAndreas Boehler    public function isInfinite() {
135*a1a3b679SAndreas Boehler
136*a1a3b679SAndreas Boehler        return !$this->count && !$this->until;
137*a1a3b679SAndreas Boehler
138*a1a3b679SAndreas Boehler    }
139*a1a3b679SAndreas Boehler
140*a1a3b679SAndreas Boehler    /**
141*a1a3b679SAndreas Boehler     * This method allows you to quickly go to the next occurrence after the
142*a1a3b679SAndreas Boehler     * specified date.
143*a1a3b679SAndreas Boehler     *
144*a1a3b679SAndreas Boehler     * @param DateTime $dt
145*a1a3b679SAndreas Boehler     * @return void
146*a1a3b679SAndreas Boehler     */
147*a1a3b679SAndreas Boehler    public function fastForward(\DateTime $dt) {
148*a1a3b679SAndreas Boehler
149*a1a3b679SAndreas Boehler        while($this->valid() && $this->currentDate < $dt ) {
150*a1a3b679SAndreas Boehler            $this->next();
151*a1a3b679SAndreas Boehler        }
152*a1a3b679SAndreas Boehler
153*a1a3b679SAndreas Boehler    }
154*a1a3b679SAndreas Boehler
155*a1a3b679SAndreas Boehler    /**
156*a1a3b679SAndreas Boehler     * The reference start date/time for the rrule.
157*a1a3b679SAndreas Boehler     *
158*a1a3b679SAndreas Boehler     * All calculations are based on this initial date.
159*a1a3b679SAndreas Boehler     *
160*a1a3b679SAndreas Boehler     * @var DateTime
161*a1a3b679SAndreas Boehler     */
162*a1a3b679SAndreas Boehler    protected $startDate;
163*a1a3b679SAndreas Boehler
164*a1a3b679SAndreas Boehler    /**
165*a1a3b679SAndreas Boehler     * The date of the current iteration. You can get this by calling
166*a1a3b679SAndreas Boehler     * ->current().
167*a1a3b679SAndreas Boehler     *
168*a1a3b679SAndreas Boehler     * @var DateTime
169*a1a3b679SAndreas Boehler     */
170*a1a3b679SAndreas Boehler    protected $currentDate;
171*a1a3b679SAndreas Boehler
172*a1a3b679SAndreas Boehler    /**
173*a1a3b679SAndreas Boehler     * Frequency is one of: secondly, minutely, hourly, daily, weekly, monthly,
174*a1a3b679SAndreas Boehler     * yearly.
175*a1a3b679SAndreas Boehler     *
176*a1a3b679SAndreas Boehler     * @var string
177*a1a3b679SAndreas Boehler     */
178*a1a3b679SAndreas Boehler    protected $frequency;
179*a1a3b679SAndreas Boehler
180*a1a3b679SAndreas Boehler    /**
181*a1a3b679SAndreas Boehler     * The number of recurrences, or 'null' if infinitely recurring.
182*a1a3b679SAndreas Boehler     *
183*a1a3b679SAndreas Boehler     * @var int
184*a1a3b679SAndreas Boehler     */
185*a1a3b679SAndreas Boehler    protected $count;
186*a1a3b679SAndreas Boehler
187*a1a3b679SAndreas Boehler    /**
188*a1a3b679SAndreas Boehler     * The interval.
189*a1a3b679SAndreas Boehler     *
190*a1a3b679SAndreas Boehler     * If for example frequency is set to daily, interval = 2 would mean every
191*a1a3b679SAndreas Boehler     * 2 days.
192*a1a3b679SAndreas Boehler     *
193*a1a3b679SAndreas Boehler     * @var int
194*a1a3b679SAndreas Boehler     */
195*a1a3b679SAndreas Boehler    protected $interval = 1;
196*a1a3b679SAndreas Boehler
197*a1a3b679SAndreas Boehler    /**
198*a1a3b679SAndreas Boehler     * The last instance of this recurrence, inclusively
199*a1a3b679SAndreas Boehler     *
200*a1a3b679SAndreas Boehler     * @var \DateTime|null
201*a1a3b679SAndreas Boehler     */
202*a1a3b679SAndreas Boehler    protected $until;
203*a1a3b679SAndreas Boehler
204*a1a3b679SAndreas Boehler    /**
205*a1a3b679SAndreas Boehler     * Which seconds to recur.
206*a1a3b679SAndreas Boehler     *
207*a1a3b679SAndreas Boehler     * This is an array of integers (between 0 and 60)
208*a1a3b679SAndreas Boehler     *
209*a1a3b679SAndreas Boehler     * @var array
210*a1a3b679SAndreas Boehler     */
211*a1a3b679SAndreas Boehler    protected $bySecond;
212*a1a3b679SAndreas Boehler
213*a1a3b679SAndreas Boehler    /**
214*a1a3b679SAndreas Boehler     * Which minutes to recur
215*a1a3b679SAndreas Boehler     *
216*a1a3b679SAndreas Boehler     * This is an array of integers (between 0 and 59)
217*a1a3b679SAndreas Boehler     *
218*a1a3b679SAndreas Boehler     * @var array
219*a1a3b679SAndreas Boehler     */
220*a1a3b679SAndreas Boehler    protected $byMinute;
221*a1a3b679SAndreas Boehler
222*a1a3b679SAndreas Boehler    /**
223*a1a3b679SAndreas Boehler     * Which hours to recur
224*a1a3b679SAndreas Boehler     *
225*a1a3b679SAndreas Boehler     * This is an array of integers (between 0 and 23)
226*a1a3b679SAndreas Boehler     *
227*a1a3b679SAndreas Boehler     * @var array
228*a1a3b679SAndreas Boehler     */
229*a1a3b679SAndreas Boehler    protected $byHour;
230*a1a3b679SAndreas Boehler
231*a1a3b679SAndreas Boehler    /**
232*a1a3b679SAndreas Boehler     * The current item in the list.
233*a1a3b679SAndreas Boehler     *
234*a1a3b679SAndreas Boehler     * You can get this number with the key() method.
235*a1a3b679SAndreas Boehler     *
236*a1a3b679SAndreas Boehler     * @var int
237*a1a3b679SAndreas Boehler     */
238*a1a3b679SAndreas Boehler    protected $counter = 0;
239*a1a3b679SAndreas Boehler
240*a1a3b679SAndreas Boehler    /**
241*a1a3b679SAndreas Boehler     * Which weekdays to recur.
242*a1a3b679SAndreas Boehler     *
243*a1a3b679SAndreas Boehler     * This is an array of weekdays
244*a1a3b679SAndreas Boehler     *
245*a1a3b679SAndreas Boehler     * This may also be preceeded by a positive or negative integer. If present,
246*a1a3b679SAndreas Boehler     * this indicates the nth occurrence of a specific day within the monthly or
247*a1a3b679SAndreas Boehler     * yearly rrule. For instance, -2TU indicates the second-last tuesday of
248*a1a3b679SAndreas Boehler     * the month, or year.
249*a1a3b679SAndreas Boehler     *
250*a1a3b679SAndreas Boehler     * @var array
251*a1a3b679SAndreas Boehler     */
252*a1a3b679SAndreas Boehler    protected $byDay;
253*a1a3b679SAndreas Boehler
254*a1a3b679SAndreas Boehler    /**
255*a1a3b679SAndreas Boehler     * Which days of the month to recur
256*a1a3b679SAndreas Boehler     *
257*a1a3b679SAndreas Boehler     * This is an array of days of the months (1-31). The value can also be
258*a1a3b679SAndreas Boehler     * negative. -5 for instance means the 5th last day of the month.
259*a1a3b679SAndreas Boehler     *
260*a1a3b679SAndreas Boehler     * @var array
261*a1a3b679SAndreas Boehler     */
262*a1a3b679SAndreas Boehler    protected $byMonthDay;
263*a1a3b679SAndreas Boehler
264*a1a3b679SAndreas Boehler    /**
265*a1a3b679SAndreas Boehler     * Which days of the year to recur.
266*a1a3b679SAndreas Boehler     *
267*a1a3b679SAndreas Boehler     * This is an array with days of the year (1 to 366). The values can also
268*a1a3b679SAndreas Boehler     * be negative. For instance, -1 will always represent the last day of the
269*a1a3b679SAndreas Boehler     * year. (December 31st).
270*a1a3b679SAndreas Boehler     *
271*a1a3b679SAndreas Boehler     * @var array
272*a1a3b679SAndreas Boehler     */
273*a1a3b679SAndreas Boehler    protected $byYearDay;
274*a1a3b679SAndreas Boehler
275*a1a3b679SAndreas Boehler    /**
276*a1a3b679SAndreas Boehler     * Which week numbers to recur.
277*a1a3b679SAndreas Boehler     *
278*a1a3b679SAndreas Boehler     * This is an array of integers from 1 to 53. The values can also be
279*a1a3b679SAndreas Boehler     * negative. -1 will always refer to the last week of the year.
280*a1a3b679SAndreas Boehler     *
281*a1a3b679SAndreas Boehler     * @var array
282*a1a3b679SAndreas Boehler     */
283*a1a3b679SAndreas Boehler    protected $byWeekNo;
284*a1a3b679SAndreas Boehler
285*a1a3b679SAndreas Boehler    /**
286*a1a3b679SAndreas Boehler     * Which months to recur.
287*a1a3b679SAndreas Boehler     *
288*a1a3b679SAndreas Boehler     * This is an array of integers from 1 to 12.
289*a1a3b679SAndreas Boehler     *
290*a1a3b679SAndreas Boehler     * @var array
291*a1a3b679SAndreas Boehler     */
292*a1a3b679SAndreas Boehler    protected $byMonth;
293*a1a3b679SAndreas Boehler
294*a1a3b679SAndreas Boehler    /**
295*a1a3b679SAndreas Boehler     * Which items in an existing st to recur.
296*a1a3b679SAndreas Boehler     *
297*a1a3b679SAndreas Boehler     * These numbers work together with an existing by* rule. It specifies
298*a1a3b679SAndreas Boehler     * exactly which items of the existing by-rule to filter.
299*a1a3b679SAndreas Boehler     *
300*a1a3b679SAndreas Boehler     * Valid values are 1 to 366 and -1 to -366. As an example, this can be
301*a1a3b679SAndreas Boehler     * used to recur the last workday of the month.
302*a1a3b679SAndreas Boehler     *
303*a1a3b679SAndreas Boehler     * This would be done by setting frequency to 'monthly', byDay to
304*a1a3b679SAndreas Boehler     * 'MO,TU,WE,TH,FR' and bySetPos to -1.
305*a1a3b679SAndreas Boehler     *
306*a1a3b679SAndreas Boehler     * @var array
307*a1a3b679SAndreas Boehler     */
308*a1a3b679SAndreas Boehler    protected $bySetPos;
309*a1a3b679SAndreas Boehler
310*a1a3b679SAndreas Boehler    /**
311*a1a3b679SAndreas Boehler     * When the week starts.
312*a1a3b679SAndreas Boehler     *
313*a1a3b679SAndreas Boehler     * @var string
314*a1a3b679SAndreas Boehler     */
315*a1a3b679SAndreas Boehler    protected $weekStart = 'MO';
316*a1a3b679SAndreas Boehler
317*a1a3b679SAndreas Boehler    /* Functions that advance the iterator {{{ */
318*a1a3b679SAndreas Boehler
319*a1a3b679SAndreas Boehler    /**
320*a1a3b679SAndreas Boehler     * Does the processing for advancing the iterator for hourly frequency.
321*a1a3b679SAndreas Boehler     *
322*a1a3b679SAndreas Boehler     * @return void
323*a1a3b679SAndreas Boehler     */
324*a1a3b679SAndreas Boehler    protected function nextHourly() {
325*a1a3b679SAndreas Boehler
326*a1a3b679SAndreas Boehler        $this->currentDate->modify('+' . $this->interval . ' hours');
327*a1a3b679SAndreas Boehler
328*a1a3b679SAndreas Boehler    }
329*a1a3b679SAndreas Boehler
330*a1a3b679SAndreas Boehler    /**
331*a1a3b679SAndreas Boehler     * Does the processing for advancing the iterator for daily frequency.
332*a1a3b679SAndreas Boehler     *
333*a1a3b679SAndreas Boehler     * @return void
334*a1a3b679SAndreas Boehler     */
335*a1a3b679SAndreas Boehler    protected function nextDaily() {
336*a1a3b679SAndreas Boehler
337*a1a3b679SAndreas Boehler        if (!$this->byHour && !$this->byDay) {
338*a1a3b679SAndreas Boehler            $this->currentDate->modify('+' . $this->interval . ' days');
339*a1a3b679SAndreas Boehler            return;
340*a1a3b679SAndreas Boehler        }
341*a1a3b679SAndreas Boehler
342*a1a3b679SAndreas Boehler        if (isset($this->byHour)) {
343*a1a3b679SAndreas Boehler            $recurrenceHours = $this->getHours();
344*a1a3b679SAndreas Boehler        }
345*a1a3b679SAndreas Boehler
346*a1a3b679SAndreas Boehler        if (isset($this->byDay)) {
347*a1a3b679SAndreas Boehler            $recurrenceDays = $this->getDays();
348*a1a3b679SAndreas Boehler        }
349*a1a3b679SAndreas Boehler
350*a1a3b679SAndreas Boehler        if (isset($this->byMonth)) {
351*a1a3b679SAndreas Boehler            $recurrenceMonths = $this->getMonths();
352*a1a3b679SAndreas Boehler        }
353*a1a3b679SAndreas Boehler
354*a1a3b679SAndreas Boehler        do {
355*a1a3b679SAndreas Boehler            if ($this->byHour) {
356*a1a3b679SAndreas Boehler                if ($this->currentDate->format('G') == '23') {
357*a1a3b679SAndreas Boehler                    // to obey the interval rule
358*a1a3b679SAndreas Boehler                    $this->currentDate->modify('+' . $this->interval-1 . ' days');
359*a1a3b679SAndreas Boehler                }
360*a1a3b679SAndreas Boehler
361*a1a3b679SAndreas Boehler                $this->currentDate->modify('+1 hours');
362*a1a3b679SAndreas Boehler
363*a1a3b679SAndreas Boehler            } else {
364*a1a3b679SAndreas Boehler                $this->currentDate->modify('+' . $this->interval . ' days');
365*a1a3b679SAndreas Boehler
366*a1a3b679SAndreas Boehler            }
367*a1a3b679SAndreas Boehler
368*a1a3b679SAndreas Boehler            // Current month of the year
369*a1a3b679SAndreas Boehler            $currentMonth = $this->currentDate->format('n');
370*a1a3b679SAndreas Boehler
371*a1a3b679SAndreas Boehler            // Current day of the week
372*a1a3b679SAndreas Boehler            $currentDay = $this->currentDate->format('w');
373*a1a3b679SAndreas Boehler
374*a1a3b679SAndreas Boehler            // Current hour of the day
375*a1a3b679SAndreas Boehler            $currentHour = $this->currentDate->format('G');
376*a1a3b679SAndreas Boehler
377*a1a3b679SAndreas Boehler        } while (
378*a1a3b679SAndreas Boehler            ($this->byDay   && !in_array($currentDay, $recurrenceDays)) ||
379*a1a3b679SAndreas Boehler            ($this->byHour  && !in_array($currentHour, $recurrenceHours)) ||
380*a1a3b679SAndreas Boehler            ($this->byMonth && !in_array($currentMonth, $recurrenceMonths))
381*a1a3b679SAndreas Boehler        );
382*a1a3b679SAndreas Boehler
383*a1a3b679SAndreas Boehler    }
384*a1a3b679SAndreas Boehler
385*a1a3b679SAndreas Boehler    /**
386*a1a3b679SAndreas Boehler     * Does the processing for advancing the iterator for weekly frequency.
387*a1a3b679SAndreas Boehler     *
388*a1a3b679SAndreas Boehler     * @return void
389*a1a3b679SAndreas Boehler     */
390*a1a3b679SAndreas Boehler    protected function nextWeekly() {
391*a1a3b679SAndreas Boehler
392*a1a3b679SAndreas Boehler        if (!$this->byHour && !$this->byDay) {
393*a1a3b679SAndreas Boehler            $this->currentDate->modify('+' . $this->interval . ' weeks');
394*a1a3b679SAndreas Boehler            return;
395*a1a3b679SAndreas Boehler        }
396*a1a3b679SAndreas Boehler
397*a1a3b679SAndreas Boehler        if ($this->byHour) {
398*a1a3b679SAndreas Boehler            $recurrenceHours = $this->getHours();
399*a1a3b679SAndreas Boehler        }
400*a1a3b679SAndreas Boehler
401*a1a3b679SAndreas Boehler        if ($this->byDay) {
402*a1a3b679SAndreas Boehler            $recurrenceDays = $this->getDays();
403*a1a3b679SAndreas Boehler        }
404*a1a3b679SAndreas Boehler
405*a1a3b679SAndreas Boehler        // First day of the week:
406*a1a3b679SAndreas Boehler        $firstDay = $this->dayMap[$this->weekStart];
407*a1a3b679SAndreas Boehler
408*a1a3b679SAndreas Boehler        do {
409*a1a3b679SAndreas Boehler
410*a1a3b679SAndreas Boehler            if ($this->byHour) {
411*a1a3b679SAndreas Boehler                $this->currentDate->modify('+1 hours');
412*a1a3b679SAndreas Boehler            } else {
413*a1a3b679SAndreas Boehler                $this->currentDate->modify('+1 days');
414*a1a3b679SAndreas Boehler            }
415*a1a3b679SAndreas Boehler
416*a1a3b679SAndreas Boehler            // Current day of the week
417*a1a3b679SAndreas Boehler            $currentDay = (int) $this->currentDate->format('w');
418*a1a3b679SAndreas Boehler
419*a1a3b679SAndreas Boehler            // Current hour of the day
420*a1a3b679SAndreas Boehler            $currentHour = (int) $this->currentDate->format('G');
421*a1a3b679SAndreas Boehler
422*a1a3b679SAndreas Boehler            // We need to roll over to the next week
423*a1a3b679SAndreas Boehler            if ($currentDay === $firstDay && (!$this->byHour || $currentHour == '0')) {
424*a1a3b679SAndreas Boehler                $this->currentDate->modify('+' . $this->interval-1 . ' weeks');
425*a1a3b679SAndreas Boehler
426*a1a3b679SAndreas Boehler                // We need to go to the first day of this week, but only if we
427*a1a3b679SAndreas Boehler                // are not already on this first day of this week.
428*a1a3b679SAndreas Boehler                if($this->currentDate->format('w') != $firstDay) {
429*a1a3b679SAndreas Boehler                    $this->currentDate->modify('last ' . $this->dayNames[$this->dayMap[$this->weekStart]]);
430*a1a3b679SAndreas Boehler                }
431*a1a3b679SAndreas Boehler            }
432*a1a3b679SAndreas Boehler
433*a1a3b679SAndreas Boehler            // We have a match
434*a1a3b679SAndreas Boehler        } while (($this->byDay && !in_array($currentDay, $recurrenceDays)) || ($this->byHour && !in_array($currentHour, $recurrenceHours)));
435*a1a3b679SAndreas Boehler    }
436*a1a3b679SAndreas Boehler
437*a1a3b679SAndreas Boehler    /**
438*a1a3b679SAndreas Boehler     * Does the processing for advancing the iterator for monthly frequency.
439*a1a3b679SAndreas Boehler     *
440*a1a3b679SAndreas Boehler     * @return void
441*a1a3b679SAndreas Boehler     */
442*a1a3b679SAndreas Boehler    protected function nextMonthly() {
443*a1a3b679SAndreas Boehler
444*a1a3b679SAndreas Boehler        $currentDayOfMonth = $this->currentDate->format('j');
445*a1a3b679SAndreas Boehler        if (!$this->byMonthDay && !$this->byDay) {
446*a1a3b679SAndreas Boehler
447*a1a3b679SAndreas Boehler            // If the current day is higher than the 28th, rollover can
448*a1a3b679SAndreas Boehler            // occur to the next month. We Must skip these invalid
449*a1a3b679SAndreas Boehler            // entries.
450*a1a3b679SAndreas Boehler            if ($currentDayOfMonth < 29) {
451*a1a3b679SAndreas Boehler                $this->currentDate->modify('+' . $this->interval . ' months');
452*a1a3b679SAndreas Boehler            } else {
453*a1a3b679SAndreas Boehler                $increase = 0;
454*a1a3b679SAndreas Boehler                do {
455*a1a3b679SAndreas Boehler                    $increase++;
456*a1a3b679SAndreas Boehler                    $tempDate = clone $this->currentDate;
457*a1a3b679SAndreas Boehler                    $tempDate->modify('+ ' . ($this->interval*$increase) . ' months');
458*a1a3b679SAndreas Boehler                } while ($tempDate->format('j') != $currentDayOfMonth);
459*a1a3b679SAndreas Boehler                $this->currentDate = $tempDate;
460*a1a3b679SAndreas Boehler            }
461*a1a3b679SAndreas Boehler            return;
462*a1a3b679SAndreas Boehler        }
463*a1a3b679SAndreas Boehler
464*a1a3b679SAndreas Boehler        while(true) {
465*a1a3b679SAndreas Boehler
466*a1a3b679SAndreas Boehler            $occurrences = $this->getMonthlyOccurrences();
467*a1a3b679SAndreas Boehler
468*a1a3b679SAndreas Boehler            foreach($occurrences as $occurrence) {
469*a1a3b679SAndreas Boehler
470*a1a3b679SAndreas Boehler                // The first occurrence thats higher than the current
471*a1a3b679SAndreas Boehler                // day of the month wins.
472*a1a3b679SAndreas Boehler                if ($occurrence > $currentDayOfMonth) {
473*a1a3b679SAndreas Boehler                    break 2;
474*a1a3b679SAndreas Boehler                }
475*a1a3b679SAndreas Boehler
476*a1a3b679SAndreas Boehler            }
477*a1a3b679SAndreas Boehler
478*a1a3b679SAndreas Boehler            // If we made it all the way here, it means there were no
479*a1a3b679SAndreas Boehler            // valid occurrences, and we need to advance to the next
480*a1a3b679SAndreas Boehler            // month.
481*a1a3b679SAndreas Boehler            //
482*a1a3b679SAndreas Boehler            // This line does not currently work in hhvm. Temporary workaround
483*a1a3b679SAndreas Boehler            // follows:
484*a1a3b679SAndreas Boehler            // $this->currentDate->modify('first day of this month');
485*a1a3b679SAndreas Boehler            $this->currentDate = new \DateTime($this->currentDate->format('Y-m-1 H:i:s'), $this->currentDate->getTimezone());
486*a1a3b679SAndreas Boehler            // end of workaround
487*a1a3b679SAndreas Boehler            $this->currentDate->modify('+ ' . $this->interval . ' months');
488*a1a3b679SAndreas Boehler
489*a1a3b679SAndreas Boehler            // This goes to 0 because we need to start counting at the
490*a1a3b679SAndreas Boehler            // beginning.
491*a1a3b679SAndreas Boehler            $currentDayOfMonth = 0;
492*a1a3b679SAndreas Boehler
493*a1a3b679SAndreas Boehler        }
494*a1a3b679SAndreas Boehler
495*a1a3b679SAndreas Boehler        $this->currentDate->setDate($this->currentDate->format('Y'), $this->currentDate->format('n'), $occurrence);
496*a1a3b679SAndreas Boehler
497*a1a3b679SAndreas Boehler    }
498*a1a3b679SAndreas Boehler
499*a1a3b679SAndreas Boehler    /**
500*a1a3b679SAndreas Boehler     * Does the processing for advancing the iterator for yearly frequency.
501*a1a3b679SAndreas Boehler     *
502*a1a3b679SAndreas Boehler     * @return void
503*a1a3b679SAndreas Boehler     */
504*a1a3b679SAndreas Boehler    protected function nextYearly() {
505*a1a3b679SAndreas Boehler
506*a1a3b679SAndreas Boehler        $currentMonth = $this->currentDate->format('n');
507*a1a3b679SAndreas Boehler        $currentYear = $this->currentDate->format('Y');
508*a1a3b679SAndreas Boehler        $currentDayOfMonth = $this->currentDate->format('j');
509*a1a3b679SAndreas Boehler
510*a1a3b679SAndreas Boehler        // No sub-rules, so we just advance by year
511*a1a3b679SAndreas Boehler        if (!$this->byMonth) {
512*a1a3b679SAndreas Boehler
513*a1a3b679SAndreas Boehler            // Unless it was a leap day!
514*a1a3b679SAndreas Boehler            if ($currentMonth==2 && $currentDayOfMonth==29) {
515*a1a3b679SAndreas Boehler
516*a1a3b679SAndreas Boehler                $counter = 0;
517*a1a3b679SAndreas Boehler                do {
518*a1a3b679SAndreas Boehler                    $counter++;
519*a1a3b679SAndreas Boehler                    // Here we increase the year count by the interval, until
520*a1a3b679SAndreas Boehler                    // we hit a date that's also in a leap year.
521*a1a3b679SAndreas Boehler                    //
522*a1a3b679SAndreas Boehler                    // We could just find the next interval that's dividable by
523*a1a3b679SAndreas Boehler                    // 4, but that would ignore the rule that there's no leap
524*a1a3b679SAndreas Boehler                    // year every year that's dividable by a 100, but not by
525*a1a3b679SAndreas Boehler                    // 400. (1800, 1900, 2100). So we just rely on the datetime
526*a1a3b679SAndreas Boehler                    // functions instead.
527*a1a3b679SAndreas Boehler                    $nextDate = clone $this->currentDate;
528*a1a3b679SAndreas Boehler                    $nextDate->modify('+ ' . ($this->interval*$counter) . ' years');
529*a1a3b679SAndreas Boehler                } while ($nextDate->format('n')!=2);
530*a1a3b679SAndreas Boehler                $this->currentDate = $nextDate;
531*a1a3b679SAndreas Boehler
532*a1a3b679SAndreas Boehler                return;
533*a1a3b679SAndreas Boehler
534*a1a3b679SAndreas Boehler            }
535*a1a3b679SAndreas Boehler
536*a1a3b679SAndreas Boehler            // The easiest form
537*a1a3b679SAndreas Boehler            $this->currentDate->modify('+' . $this->interval . ' years');
538*a1a3b679SAndreas Boehler            return;
539*a1a3b679SAndreas Boehler
540*a1a3b679SAndreas Boehler        }
541*a1a3b679SAndreas Boehler
542*a1a3b679SAndreas Boehler        $currentMonth = $this->currentDate->format('n');
543*a1a3b679SAndreas Boehler        $currentYear = $this->currentDate->format('Y');
544*a1a3b679SAndreas Boehler        $currentDayOfMonth = $this->currentDate->format('j');
545*a1a3b679SAndreas Boehler
546*a1a3b679SAndreas Boehler        $advancedToNewMonth = false;
547*a1a3b679SAndreas Boehler
548*a1a3b679SAndreas Boehler        // If we got a byDay or getMonthDay filter, we must first expand
549*a1a3b679SAndreas Boehler        // further.
550*a1a3b679SAndreas Boehler        if ($this->byDay || $this->byMonthDay) {
551*a1a3b679SAndreas Boehler
552*a1a3b679SAndreas Boehler            while(true) {
553*a1a3b679SAndreas Boehler
554*a1a3b679SAndreas Boehler                $occurrences = $this->getMonthlyOccurrences();
555*a1a3b679SAndreas Boehler
556*a1a3b679SAndreas Boehler                foreach($occurrences as $occurrence) {
557*a1a3b679SAndreas Boehler
558*a1a3b679SAndreas Boehler                    // The first occurrence that's higher than the current
559*a1a3b679SAndreas Boehler                    // day of the month wins.
560*a1a3b679SAndreas Boehler                    // If we advanced to the next month or year, the first
561*a1a3b679SAndreas Boehler                    // occurrence is always correct.
562*a1a3b679SAndreas Boehler                    if ($occurrence > $currentDayOfMonth || $advancedToNewMonth) {
563*a1a3b679SAndreas Boehler                        break 2;
564*a1a3b679SAndreas Boehler                    }
565*a1a3b679SAndreas Boehler
566*a1a3b679SAndreas Boehler                }
567*a1a3b679SAndreas Boehler
568*a1a3b679SAndreas Boehler                // If we made it here, it means we need to advance to
569*a1a3b679SAndreas Boehler                // the next month or year.
570*a1a3b679SAndreas Boehler                $currentDayOfMonth = 1;
571*a1a3b679SAndreas Boehler                $advancedToNewMonth = true;
572*a1a3b679SAndreas Boehler                do {
573*a1a3b679SAndreas Boehler
574*a1a3b679SAndreas Boehler                    $currentMonth++;
575*a1a3b679SAndreas Boehler                    if ($currentMonth>12) {
576*a1a3b679SAndreas Boehler                        $currentYear+=$this->interval;
577*a1a3b679SAndreas Boehler                        $currentMonth = 1;
578*a1a3b679SAndreas Boehler                    }
579*a1a3b679SAndreas Boehler                } while (!in_array($currentMonth, $this->byMonth));
580*a1a3b679SAndreas Boehler
581*a1a3b679SAndreas Boehler                $this->currentDate->setDate($currentYear, $currentMonth, $currentDayOfMonth);
582*a1a3b679SAndreas Boehler
583*a1a3b679SAndreas Boehler            }
584*a1a3b679SAndreas Boehler
585*a1a3b679SAndreas Boehler            // If we made it here, it means we got a valid occurrence
586*a1a3b679SAndreas Boehler            $this->currentDate->setDate($currentYear, $currentMonth, $occurrence);
587*a1a3b679SAndreas Boehler            return;
588*a1a3b679SAndreas Boehler
589*a1a3b679SAndreas Boehler        } else {
590*a1a3b679SAndreas Boehler
591*a1a3b679SAndreas Boehler            // These are the 'byMonth' rules, if there are no byDay or
592*a1a3b679SAndreas Boehler            // byMonthDay sub-rules.
593*a1a3b679SAndreas Boehler            do {
594*a1a3b679SAndreas Boehler
595*a1a3b679SAndreas Boehler                $currentMonth++;
596*a1a3b679SAndreas Boehler                if ($currentMonth>12) {
597*a1a3b679SAndreas Boehler                    $currentYear+=$this->interval;
598*a1a3b679SAndreas Boehler                    $currentMonth = 1;
599*a1a3b679SAndreas Boehler                }
600*a1a3b679SAndreas Boehler            } while (!in_array($currentMonth, $this->byMonth));
601*a1a3b679SAndreas Boehler            $this->currentDate->setDate($currentYear, $currentMonth, $currentDayOfMonth);
602*a1a3b679SAndreas Boehler
603*a1a3b679SAndreas Boehler            return;
604*a1a3b679SAndreas Boehler
605*a1a3b679SAndreas Boehler        }
606*a1a3b679SAndreas Boehler
607*a1a3b679SAndreas Boehler    }
608*a1a3b679SAndreas Boehler
609*a1a3b679SAndreas Boehler    /* }}} */
610*a1a3b679SAndreas Boehler
611*a1a3b679SAndreas Boehler    /**
612*a1a3b679SAndreas Boehler     * This method receives a string from an RRULE property, and populates this
613*a1a3b679SAndreas Boehler     * class with all the values.
614*a1a3b679SAndreas Boehler     *
615*a1a3b679SAndreas Boehler     * @param string|array $rrule
616*a1a3b679SAndreas Boehler     * @return void
617*a1a3b679SAndreas Boehler     */
618*a1a3b679SAndreas Boehler    protected function parseRRule($rrule) {
619*a1a3b679SAndreas Boehler
620*a1a3b679SAndreas Boehler        if (is_string($rrule)) {
621*a1a3b679SAndreas Boehler            $rrule = Property\ICalendar\Recur::stringToArray($rrule);
622*a1a3b679SAndreas Boehler        }
623*a1a3b679SAndreas Boehler
624*a1a3b679SAndreas Boehler        foreach($rrule as $key=>$value) {
625*a1a3b679SAndreas Boehler
626*a1a3b679SAndreas Boehler            $key = strtoupper($key);
627*a1a3b679SAndreas Boehler            switch($key) {
628*a1a3b679SAndreas Boehler
629*a1a3b679SAndreas Boehler                case 'FREQ' :
630*a1a3b679SAndreas Boehler                    $value = strtolower($value);
631*a1a3b679SAndreas Boehler                    if (!in_array(
632*a1a3b679SAndreas Boehler                        $value,
633*a1a3b679SAndreas Boehler                        array('secondly','minutely','hourly','daily','weekly','monthly','yearly')
634*a1a3b679SAndreas Boehler                    )) {
635*a1a3b679SAndreas Boehler                        throw new InvalidArgumentException('Unknown value for FREQ=' . strtoupper($value));
636*a1a3b679SAndreas Boehler                    }
637*a1a3b679SAndreas Boehler                    $this->frequency = $value;
638*a1a3b679SAndreas Boehler                    break;
639*a1a3b679SAndreas Boehler
640*a1a3b679SAndreas Boehler                case 'UNTIL' :
641*a1a3b679SAndreas Boehler                    $this->until = DateTimeParser::parse($value, $this->startDate->getTimezone());
642*a1a3b679SAndreas Boehler
643*a1a3b679SAndreas Boehler                    // In some cases events are generated with an UNTIL=
644*a1a3b679SAndreas Boehler                    // parameter before the actual start of the event.
645*a1a3b679SAndreas Boehler                    //
646*a1a3b679SAndreas Boehler                    // Not sure why this is happening. We assume that the
647*a1a3b679SAndreas Boehler                    // intention was that the event only recurs once.
648*a1a3b679SAndreas Boehler                    //
649*a1a3b679SAndreas Boehler                    // So we are modifying the parameter so our code doesn't
650*a1a3b679SAndreas Boehler                    // break.
651*a1a3b679SAndreas Boehler                    if($this->until < $this->startDate) {
652*a1a3b679SAndreas Boehler                        $this->until = $this->startDate;
653*a1a3b679SAndreas Boehler                    }
654*a1a3b679SAndreas Boehler                    break;
655*a1a3b679SAndreas Boehler
656*a1a3b679SAndreas Boehler                case 'INTERVAL' :
657*a1a3b679SAndreas Boehler                    // No break
658*a1a3b679SAndreas Boehler
659*a1a3b679SAndreas Boehler                case 'COUNT' :
660*a1a3b679SAndreas Boehler                    $val = (int)$value;
661*a1a3b679SAndreas Boehler                    if ($val < 1) {
662*a1a3b679SAndreas Boehler                        throw new \InvalidArgumentException(strtoupper($key) . ' in RRULE must be a positive integer!');
663*a1a3b679SAndreas Boehler                    }
664*a1a3b679SAndreas Boehler                    $key = strtolower($key);
665*a1a3b679SAndreas Boehler                    $this->$key = $val;
666*a1a3b679SAndreas Boehler                    break;
667*a1a3b679SAndreas Boehler
668*a1a3b679SAndreas Boehler                case 'BYSECOND' :
669*a1a3b679SAndreas Boehler                    $this->bySecond = (array)$value;
670*a1a3b679SAndreas Boehler                    break;
671*a1a3b679SAndreas Boehler
672*a1a3b679SAndreas Boehler                case 'BYMINUTE' :
673*a1a3b679SAndreas Boehler                    $this->byMinute = (array)$value;
674*a1a3b679SAndreas Boehler                    break;
675*a1a3b679SAndreas Boehler
676*a1a3b679SAndreas Boehler                case 'BYHOUR' :
677*a1a3b679SAndreas Boehler                    $this->byHour = (array)$value;
678*a1a3b679SAndreas Boehler                    break;
679*a1a3b679SAndreas Boehler
680*a1a3b679SAndreas Boehler                case 'BYDAY' :
681*a1a3b679SAndreas Boehler                    $value = (array)$value;
682*a1a3b679SAndreas Boehler                    foreach($value as $part) {
683*a1a3b679SAndreas Boehler                        if (!preg_match('#^  (-|\+)? ([1-5])? (MO|TU|WE|TH|FR|SA|SU) $# xi', $part)) {
684*a1a3b679SAndreas Boehler                            throw new \InvalidArgumentException('Invalid part in BYDAY clause: ' . $part);
685*a1a3b679SAndreas Boehler                        }
686*a1a3b679SAndreas Boehler                    }
687*a1a3b679SAndreas Boehler                    $this->byDay = $value;
688*a1a3b679SAndreas Boehler                    break;
689*a1a3b679SAndreas Boehler
690*a1a3b679SAndreas Boehler                case 'BYMONTHDAY' :
691*a1a3b679SAndreas Boehler                    $this->byMonthDay = (array)$value;
692*a1a3b679SAndreas Boehler                    break;
693*a1a3b679SAndreas Boehler
694*a1a3b679SAndreas Boehler                case 'BYYEARDAY' :
695*a1a3b679SAndreas Boehler                    $this->byYearDay = (array)$value;
696*a1a3b679SAndreas Boehler                    break;
697*a1a3b679SAndreas Boehler
698*a1a3b679SAndreas Boehler                case 'BYWEEKNO' :
699*a1a3b679SAndreas Boehler                    $this->byWeekNo = (array)$value;
700*a1a3b679SAndreas Boehler                    break;
701*a1a3b679SAndreas Boehler
702*a1a3b679SAndreas Boehler                case 'BYMONTH' :
703*a1a3b679SAndreas Boehler                    $this->byMonth = (array)$value;
704*a1a3b679SAndreas Boehler                    break;
705*a1a3b679SAndreas Boehler
706*a1a3b679SAndreas Boehler                case 'BYSETPOS' :
707*a1a3b679SAndreas Boehler                    $this->bySetPos = (array)$value;
708*a1a3b679SAndreas Boehler                    break;
709*a1a3b679SAndreas Boehler
710*a1a3b679SAndreas Boehler                case 'WKST' :
711*a1a3b679SAndreas Boehler                    $this->weekStart = strtoupper($value);
712*a1a3b679SAndreas Boehler                    break;
713*a1a3b679SAndreas Boehler
714*a1a3b679SAndreas Boehler                default:
715*a1a3b679SAndreas Boehler                    throw new \InvalidArgumentException('Not supported: ' . strtoupper($key));
716*a1a3b679SAndreas Boehler
717*a1a3b679SAndreas Boehler            }
718*a1a3b679SAndreas Boehler
719*a1a3b679SAndreas Boehler        }
720*a1a3b679SAndreas Boehler
721*a1a3b679SAndreas Boehler    }
722*a1a3b679SAndreas Boehler
723*a1a3b679SAndreas Boehler    /**
724*a1a3b679SAndreas Boehler     * Mappings between the day number and english day name.
725*a1a3b679SAndreas Boehler     *
726*a1a3b679SAndreas Boehler     * @var array
727*a1a3b679SAndreas Boehler     */
728*a1a3b679SAndreas Boehler    protected $dayNames = array(
729*a1a3b679SAndreas Boehler        0 => 'Sunday',
730*a1a3b679SAndreas Boehler        1 => 'Monday',
731*a1a3b679SAndreas Boehler        2 => 'Tuesday',
732*a1a3b679SAndreas Boehler        3 => 'Wednesday',
733*a1a3b679SAndreas Boehler        4 => 'Thursday',
734*a1a3b679SAndreas Boehler        5 => 'Friday',
735*a1a3b679SAndreas Boehler        6 => 'Saturday',
736*a1a3b679SAndreas Boehler    );
737*a1a3b679SAndreas Boehler
738*a1a3b679SAndreas Boehler    /**
739*a1a3b679SAndreas Boehler     * Returns all the occurrences for a monthly frequency with a 'byDay' or
740*a1a3b679SAndreas Boehler     * 'byMonthDay' expansion for the current month.
741*a1a3b679SAndreas Boehler     *
742*a1a3b679SAndreas Boehler     * The returned list is an array of integers with the day of month (1-31).
743*a1a3b679SAndreas Boehler     *
744*a1a3b679SAndreas Boehler     * @return array
745*a1a3b679SAndreas Boehler     */
746*a1a3b679SAndreas Boehler    protected function getMonthlyOccurrences() {
747*a1a3b679SAndreas Boehler
748*a1a3b679SAndreas Boehler        $startDate = clone $this->currentDate;
749*a1a3b679SAndreas Boehler
750*a1a3b679SAndreas Boehler        $byDayResults = array();
751*a1a3b679SAndreas Boehler
752*a1a3b679SAndreas Boehler        // Our strategy is to simply go through the byDays, advance the date to
753*a1a3b679SAndreas Boehler        // that point and add it to the results.
754*a1a3b679SAndreas Boehler        if ($this->byDay) foreach($this->byDay as $day) {
755*a1a3b679SAndreas Boehler
756*a1a3b679SAndreas Boehler            $dayName = $this->dayNames[$this->dayMap[substr($day,-2)]];
757*a1a3b679SAndreas Boehler
758*a1a3b679SAndreas Boehler
759*a1a3b679SAndreas Boehler            // Dayname will be something like 'wednesday'. Now we need to find
760*a1a3b679SAndreas Boehler            // all wednesdays in this month.
761*a1a3b679SAndreas Boehler            $dayHits = array();
762*a1a3b679SAndreas Boehler
763*a1a3b679SAndreas Boehler            // workaround for missing 'first day of the month' support in hhvm
764*a1a3b679SAndreas Boehler            $checkDate = new \DateTime($startDate->format('Y-m-1'));
765*a1a3b679SAndreas Boehler            // workaround modify always advancing the date even if the current day is a $dayName in hhvm
766*a1a3b679SAndreas Boehler            if ($checkDate->format('l') !== $dayName) {
767*a1a3b679SAndreas Boehler                $checkDate->modify($dayName);
768*a1a3b679SAndreas Boehler            }
769*a1a3b679SAndreas Boehler
770*a1a3b679SAndreas Boehler            do {
771*a1a3b679SAndreas Boehler                $dayHits[] = $checkDate->format('j');
772*a1a3b679SAndreas Boehler                $checkDate->modify('next ' . $dayName);
773*a1a3b679SAndreas Boehler            } while ($checkDate->format('n') === $startDate->format('n'));
774*a1a3b679SAndreas Boehler
775*a1a3b679SAndreas Boehler            // So now we have 'all wednesdays' for month. It is however
776*a1a3b679SAndreas Boehler            // possible that the user only really wanted the 1st, 2nd or last
777*a1a3b679SAndreas Boehler            // wednesday.
778*a1a3b679SAndreas Boehler            if (strlen($day)>2) {
779*a1a3b679SAndreas Boehler                $offset = (int)substr($day,0,-2);
780*a1a3b679SAndreas Boehler
781*a1a3b679SAndreas Boehler                if ($offset>0) {
782*a1a3b679SAndreas Boehler                    // It is possible that the day does not exist, such as a
783*a1a3b679SAndreas Boehler                    // 5th or 6th wednesday of the month.
784*a1a3b679SAndreas Boehler                    if (isset($dayHits[$offset-1])) {
785*a1a3b679SAndreas Boehler                        $byDayResults[] = $dayHits[$offset-1];
786*a1a3b679SAndreas Boehler                    }
787*a1a3b679SAndreas Boehler                } else {
788*a1a3b679SAndreas Boehler
789*a1a3b679SAndreas Boehler                    // if it was negative we count from the end of the array
790*a1a3b679SAndreas Boehler                    // might not exist, fx. -5th tuesday
791*a1a3b679SAndreas Boehler                    if (isset($dayHits[count($dayHits) + $offset])) {
792*a1a3b679SAndreas Boehler                        $byDayResults[] = $dayHits[count($dayHits) + $offset];
793*a1a3b679SAndreas Boehler                    }
794*a1a3b679SAndreas Boehler                }
795*a1a3b679SAndreas Boehler            } else {
796*a1a3b679SAndreas Boehler                // There was no counter (first, second, last wednesdays), so we
797*a1a3b679SAndreas Boehler                // just need to add the all to the list).
798*a1a3b679SAndreas Boehler                $byDayResults = array_merge($byDayResults, $dayHits);
799*a1a3b679SAndreas Boehler
800*a1a3b679SAndreas Boehler            }
801*a1a3b679SAndreas Boehler
802*a1a3b679SAndreas Boehler        }
803*a1a3b679SAndreas Boehler
804*a1a3b679SAndreas Boehler        $byMonthDayResults = array();
805*a1a3b679SAndreas Boehler        if ($this->byMonthDay) foreach($this->byMonthDay as $monthDay) {
806*a1a3b679SAndreas Boehler
807*a1a3b679SAndreas Boehler            // Removing values that are out of range for this month
808*a1a3b679SAndreas Boehler            if ($monthDay > $startDate->format('t') ||
809*a1a3b679SAndreas Boehler                $monthDay < 0-$startDate->format('t')) {
810*a1a3b679SAndreas Boehler                    continue;
811*a1a3b679SAndreas Boehler            }
812*a1a3b679SAndreas Boehler            if ($monthDay>0) {
813*a1a3b679SAndreas Boehler                $byMonthDayResults[] = $monthDay;
814*a1a3b679SAndreas Boehler            } else {
815*a1a3b679SAndreas Boehler                // Negative values
816*a1a3b679SAndreas Boehler                $byMonthDayResults[] = $startDate->format('t') + 1 + $monthDay;
817*a1a3b679SAndreas Boehler            }
818*a1a3b679SAndreas Boehler        }
819*a1a3b679SAndreas Boehler
820*a1a3b679SAndreas Boehler        // If there was just byDay or just byMonthDay, they just specify our
821*a1a3b679SAndreas Boehler        // (almost) final list. If both were provided, then byDay limits the
822*a1a3b679SAndreas Boehler        // list.
823*a1a3b679SAndreas Boehler        if ($this->byMonthDay && $this->byDay) {
824*a1a3b679SAndreas Boehler            $result = array_intersect($byMonthDayResults, $byDayResults);
825*a1a3b679SAndreas Boehler        } elseif ($this->byMonthDay) {
826*a1a3b679SAndreas Boehler            $result = $byMonthDayResults;
827*a1a3b679SAndreas Boehler        } else {
828*a1a3b679SAndreas Boehler            $result = $byDayResults;
829*a1a3b679SAndreas Boehler        }
830*a1a3b679SAndreas Boehler        $result = array_unique($result);
831*a1a3b679SAndreas Boehler        sort($result, SORT_NUMERIC);
832*a1a3b679SAndreas Boehler
833*a1a3b679SAndreas Boehler        // The last thing that needs checking is the BYSETPOS. If it's set, it
834*a1a3b679SAndreas Boehler        // means only certain items in the set survive the filter.
835*a1a3b679SAndreas Boehler        if (!$this->bySetPos) {
836*a1a3b679SAndreas Boehler            return $result;
837*a1a3b679SAndreas Boehler        }
838*a1a3b679SAndreas Boehler
839*a1a3b679SAndreas Boehler        $filteredResult = array();
840*a1a3b679SAndreas Boehler        foreach($this->bySetPos as $setPos) {
841*a1a3b679SAndreas Boehler
842*a1a3b679SAndreas Boehler            if ($setPos<0) {
843*a1a3b679SAndreas Boehler                $setPos = count($result)+($setPos+1);
844*a1a3b679SAndreas Boehler            }
845*a1a3b679SAndreas Boehler            if (isset($result[$setPos-1])) {
846*a1a3b679SAndreas Boehler                $filteredResult[] = $result[$setPos-1];
847*a1a3b679SAndreas Boehler            }
848*a1a3b679SAndreas Boehler        }
849*a1a3b679SAndreas Boehler
850*a1a3b679SAndreas Boehler        sort($filteredResult, SORT_NUMERIC);
851*a1a3b679SAndreas Boehler        return $filteredResult;
852*a1a3b679SAndreas Boehler
853*a1a3b679SAndreas Boehler    }
854*a1a3b679SAndreas Boehler
855*a1a3b679SAndreas Boehler    /**
856*a1a3b679SAndreas Boehler     * Simple mapping from iCalendar day names to day numbers
857*a1a3b679SAndreas Boehler     *
858*a1a3b679SAndreas Boehler     * @var array
859*a1a3b679SAndreas Boehler     */
860*a1a3b679SAndreas Boehler    protected $dayMap = array(
861*a1a3b679SAndreas Boehler        'SU' => 0,
862*a1a3b679SAndreas Boehler        'MO' => 1,
863*a1a3b679SAndreas Boehler        'TU' => 2,
864*a1a3b679SAndreas Boehler        'WE' => 3,
865*a1a3b679SAndreas Boehler        'TH' => 4,
866*a1a3b679SAndreas Boehler        'FR' => 5,
867*a1a3b679SAndreas Boehler        'SA' => 6,
868*a1a3b679SAndreas Boehler    );
869*a1a3b679SAndreas Boehler
870*a1a3b679SAndreas Boehler    protected function getHours()
871*a1a3b679SAndreas Boehler    {
872*a1a3b679SAndreas Boehler        $recurrenceHours = array();
873*a1a3b679SAndreas Boehler        foreach($this->byHour as $byHour) {
874*a1a3b679SAndreas Boehler            $recurrenceHours[] = $byHour;
875*a1a3b679SAndreas Boehler        }
876*a1a3b679SAndreas Boehler
877*a1a3b679SAndreas Boehler        return $recurrenceHours;
878*a1a3b679SAndreas Boehler    }
879*a1a3b679SAndreas Boehler
880*a1a3b679SAndreas Boehler    protected function getDays() {
881*a1a3b679SAndreas Boehler
882*a1a3b679SAndreas Boehler        $recurrenceDays = array();
883*a1a3b679SAndreas Boehler        foreach($this->byDay as $byDay) {
884*a1a3b679SAndreas Boehler
885*a1a3b679SAndreas Boehler            // The day may be preceeded with a positive (+n) or
886*a1a3b679SAndreas Boehler            // negative (-n) integer. However, this does not make
887*a1a3b679SAndreas Boehler            // sense in 'weekly' so we ignore it here.
888*a1a3b679SAndreas Boehler            $recurrenceDays[] = $this->dayMap[substr($byDay,-2)];
889*a1a3b679SAndreas Boehler
890*a1a3b679SAndreas Boehler        }
891*a1a3b679SAndreas Boehler
892*a1a3b679SAndreas Boehler        return $recurrenceDays;
893*a1a3b679SAndreas Boehler    }
894*a1a3b679SAndreas Boehler
895*a1a3b679SAndreas Boehler    protected function getMonths() {
896*a1a3b679SAndreas Boehler
897*a1a3b679SAndreas Boehler        $recurrenceMonths = array();
898*a1a3b679SAndreas Boehler        foreach($this->byMonth as $byMonth) {
899*a1a3b679SAndreas Boehler            $recurrenceMonths[] = $byMonth;
900*a1a3b679SAndreas Boehler        }
901*a1a3b679SAndreas Boehler
902*a1a3b679SAndreas Boehler        return $recurrenceMonths;
903*a1a3b679SAndreas Boehler    }
904*a1a3b679SAndreas Boehler}
905