source: pmb4.2/trunk/fuentes/pmb/admin/connecteurs/out/webdav/vendor/sabre/vobject/lib/Sabre/VObject/RecurrenceIterator.php @ 815

Last change on this file since 815 was 815, checked in by jrpelegrina, 4 years ago

Initial release of pmb 4.2

  • Property svn:executable set to *
File size: 31.1 KB
Line 
1<?php
2
3namespace Sabre\VObject;
4
5/**
6 * This class is used to determine new for a recurring event, when the next
7 * events occur.
8 *
9 * This iterator may loop infinitely in the future, therefore it is important
10 * that if you use this class, you set hard limits for the amount of iterations
11 * you want to handle.
12 *
13 * Note that currently there is not full support for the entire iCalendar
14 * specification, as it's very complex and contains a lot of permutations
15 * that's not yet used very often in software.
16 *
17 * For the focus has been on features as they actually appear in Calendaring
18 * software, but this may well get expanded as needed / on demand
19 *
20 * The following RRULE properties are supported
21 *   * UNTIL
22 *   * INTERVAL
23 *   * COUNT
24 *   * FREQ=DAILY
25 *     * BYDAY
26 *     * BYHOUR
27 *   * FREQ=WEEKLY
28 *     * BYDAY
29 *     * BYHOUR
30 *     * WKST
31 *   * FREQ=MONTHLY
32 *     * BYMONTHDAY
33 *     * BYDAY
34 *     * BYSETPOS
35 *   * FREQ=YEARLY
36 *     * BYMONTH
37 *     * BYMONTHDAY (only if BYMONTH is also set)
38 *     * BYDAY (only if BYMONTH is also set)
39 *
40 * Anything beyond this is 'undefined', which means that it may get ignored, or
41 * you may get unexpected results. The effect is that in some applications the
42 * specified recurrence may look incorrect, or is missing.
43 *
44 * @copyright Copyright (C) 2007-2013 Rooftop Solutions. All rights reserved.
45 * @author Evert Pot (http://www.rooftopsolutions.nl/)
46 * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
47 */
48class RecurrenceIterator implements \Iterator {
49
50    /**
51     * The initial event date
52     *
53     * @var DateTime
54     */
55    public $startDate;
56
57    /**
58     * The end-date of the initial event
59     *
60     * @var DateTime
61     */
62    public $endDate;
63
64    /**
65     * The 'current' recurrence.
66     *
67     * This will be increased for every iteration.
68     *
69     * @var DateTime
70     */
71    public $currentDate;
72
73
74    /**
75     * List of dates that are excluded from the rules.
76     *
77     * This list contains the items that have been overriden by the EXDATE
78     * property.
79     *
80     * @var array
81     */
82    public $exceptionDates = array();
83
84    /**
85     * Base event
86     *
87     * @var Component\VEvent
88     */
89    public $baseEvent;
90
91    /**
92     * List of dates that are overridden by other events.
93     * Similar to $overriddenEvents, but this just contains the original dates.
94     *
95     * @var array
96     */
97    public $overriddenDates = array();
98
99    /**
100     * list of events that are 'overridden'.
101     *
102     * This is an array of Component\VEvent objects.
103     *
104     * @var array
105     */
106    public $overriddenEvents = array();
107
108
109    /**
110     * Frequency is one of: secondly, minutely, hourly, daily, weekly, monthly,
111     * yearly.
112     *
113     * @var string
114     */
115    public $frequency;
116
117    /**
118     * The last instance of this recurrence, inclusively
119     *
120     * @var DateTime|null
121     */
122    public $until;
123
124    /**
125     * The number of recurrences, or 'null' if infinitely recurring.
126     *
127     * @var int
128     */
129    public $count;
130
131    /**
132     * The interval.
133     *
134     * If for example frequency is set to daily, interval = 2 would mean every
135     * 2 days.
136     *
137     * @var int
138     */
139    public $interval = 1;
140
141    /**
142     * Which seconds to recur.
143     *
144     * This is an array of integers (between 0 and 60)
145     *
146     * @var array
147     */
148    public $bySecond;
149
150    /**
151     * Which minutes to recur
152     *
153     * This is an array of integers (between 0 and 59)
154     *
155     * @var array
156     */
157    public $byMinute;
158
159    /**
160     * Which hours to recur
161     *
162     * This is an array of integers (between 0 and 23)
163     *
164     * @var array
165     */
166    public $byHour;
167
168    /**
169     * Which weekdays to recur.
170     *
171     * This is an array of weekdays
172     *
173     * This may also be preceeded by a positive or negative integer. If present,
174     * this indicates the nth occurrence of a specific day within the monthly or
175     * yearly rrule. For instance, -2TU indicates the second-last tuesday of
176     * the month, or year.
177     *
178     * @var array
179     */
180    public $byDay;
181
182    /**
183     * Which days of the month to recur
184     *
185     * This is an array of days of the months (1-31). The value can also be
186     * negative. -5 for instance means the 5th last day of the month.
187     *
188     * @var array
189     */
190    public $byMonthDay;
191
192    /**
193     * Which days of the year to recur.
194     *
195     * This is an array with days of the year (1 to 366). The values can also
196     * be negative. For instance, -1 will always represent the last day of the
197     * year. (December 31st).
198     *
199     * @var array
200     */
201    public $byYearDay;
202
203    /**
204     * Which week numbers to recur.
205     *
206     * This is an array of integers from 1 to 53. The values can also be
207     * negative. -1 will always refer to the last week of the year.
208     *
209     * @var array
210     */
211    public $byWeekNo;
212
213    /**
214     * Which months to recur
215     *
216     * This is an array of integers from 1 to 12.
217     *
218     * @var array
219     */
220    public $byMonth;
221
222    /**
223     * Which items in an existing st to recur.
224     *
225     * These numbers work together with an existing by* rule. It specifies
226     * exactly which items of the existing by-rule to filter.
227     *
228     * Valid values are 1 to 366 and -1 to -366. As an example, this can be
229     * used to recur the last workday of the month.
230     *
231     * This would be done by setting frequency to 'monthly', byDay to
232     * 'MO,TU,WE,TH,FR' and bySetPos to -1.
233     *
234     * @var array
235     */
236    public $bySetPos;
237
238    /**
239     * When a week starts
240     *
241     * @var string
242     */
243    public $weekStart = 'MO';
244
245    /**
246     * The current item in the list
247     *
248     * @var int
249     */
250    public $counter = 0;
251
252    /**
253     * Simple mapping from iCalendar day names to day numbers
254     *
255     * @var array
256     */
257    private $dayMap = array(
258        'SU' => 0,
259        'MO' => 1,
260        'TU' => 2,
261        'WE' => 3,
262        'TH' => 4,
263        'FR' => 5,
264        'SA' => 6,
265    );
266
267    /**
268     * Mappings between the day number and english day name.
269     *
270     * @var array
271     */
272    private $dayNames = array(
273        0 => 'Sunday',
274        1 => 'Monday',
275        2 => 'Tuesday',
276        3 => 'Wednesday',
277        4 => 'Thursday',
278        5 => 'Friday',
279        6 => 'Saturday',
280    );
281
282    /**
283     * If the current iteration of the event is an overriden event, this
284     * property will hold the VObject
285     *
286     * @var Component
287     */
288    private $currentOverriddenEvent;
289
290    /**
291     * This property may contain the date of the next not-overridden event.
292     * This date is calculated sometimes a bit early, before overridden events
293     * are evaluated.
294     *
295     * @var DateTime
296     */
297    private $nextDate;
298
299    /**
300     * Creates the iterator
301     *
302     * You should pass a VCALENDAR component, as well as the UID of the event
303     * we're going to traverse.
304     *
305     * @param Component $vcal
306     * @param string|null $uid
307     */
308    public function __construct(Component $vcal, $uid=null) {
309
310        if (is_null($uid)) {
311            if ($vcal->name === 'VCALENDAR') {
312                throw new \InvalidArgumentException('If you pass a VCALENDAR object, you must pass a uid argument as well');
313            }
314            $components = array($vcal);
315            $uid = (string)$vcal->uid;
316        } else {
317            $components = $vcal->select('VEVENT');
318        }
319        foreach($components as $component) {
320            if ((string)$component->uid == $uid) {
321                if (isset($component->{'RECURRENCE-ID'})) {
322                    $this->overriddenEvents[$component->DTSTART->getDateTime()->getTimeStamp()] = $component;
323                    $this->overriddenDates[] = $component->{'RECURRENCE-ID'}->getDateTime();
324                } else {
325                    $this->baseEvent = $component;
326                }
327            }
328        }
329        if (!$this->baseEvent) {
330            throw new \InvalidArgumentException('Could not find a base event with uid: ' . $uid);
331        }
332
333        $this->startDate = clone $this->baseEvent->DTSTART->getDateTime();
334
335        $this->endDate = null;
336        if (isset($this->baseEvent->DTEND)) {
337            $this->endDate = clone $this->baseEvent->DTEND->getDateTime();
338        } else {
339            $this->endDate = clone $this->startDate;
340            if (isset($this->baseEvent->DURATION)) {
341                $this->endDate->add(DateTimeParser::parse($this->baseEvent->DURATION->value));
342            } elseif ($this->baseEvent->DTSTART->getDateType()===Property\DateTime::DATE) {
343                $this->endDate->modify('+1 day');
344            }
345        }
346        $this->currentDate = clone $this->startDate;
347
348        $rrule = (string)$this->baseEvent->RRULE;
349
350        $parts = explode(';', $rrule);
351
352        // If no rrule was specified, we create a default setting
353        if (!$rrule) {
354            $this->frequency = 'daily';
355            $this->count = 1;
356        } else foreach($parts as $part) {
357
358            list($key, $value) = explode('=', $part, 2);
359
360            switch(strtoupper($key)) {
361
362                case 'FREQ' :
363                    if (!in_array(
364                        strtolower($value),
365                        array('secondly','minutely','hourly','daily','weekly','monthly','yearly')
366                    )) {
367                        throw new \InvalidArgumentException('Unknown value for FREQ=' . strtoupper($value));
368
369                    }
370                    $this->frequency = strtolower($value);
371                    break;
372
373                case 'UNTIL' :
374                    $this->until = DateTimeParser::parse($value);
375
376                    // In some cases events are generated with an UNTIL=
377                    // parameter before the actual start of the event.
378                    //
379                    // Not sure why this is happening. We assume that the
380                    // intention was that the event only recurs once.
381                    //
382                    // So we are modifying the parameter so our code doesn't
383                    // break.
384                    if($this->until < $this->baseEvent->DTSTART->getDateTime()) {
385                        $this->until = $this->baseEvent->DTSTART->getDateTime();
386                    }
387                    break;
388
389                case 'COUNT' :
390                    $this->count = (int)$value;
391                    break;
392
393                case 'INTERVAL' :
394                    $this->interval = (int)$value;
395                    if ($this->interval < 1) {
396                        throw new \InvalidArgumentException('INTERVAL in RRULE must be a positive integer!');
397                    }
398                    break;
399
400                case 'BYSECOND' :
401                    $this->bySecond = explode(',', $value);
402                    break;
403
404                case 'BYMINUTE' :
405                    $this->byMinute = explode(',', $value);
406                    break;
407
408                case 'BYHOUR' :
409                    $this->byHour = explode(',', $value);
410                    break;
411
412                case 'BYDAY' :
413                    $this->byDay = explode(',', strtoupper($value));
414                    break;
415
416                case 'BYMONTHDAY' :
417                    $this->byMonthDay = explode(',', $value);
418                    break;
419
420                case 'BYYEARDAY' :
421                    $this->byYearDay = explode(',', $value);
422                    break;
423
424                case 'BYWEEKNO' :
425                    $this->byWeekNo = explode(',', $value);
426                    break;
427
428                case 'BYMONTH' :
429                    $this->byMonth = explode(',', $value);
430                    break;
431
432                case 'BYSETPOS' :
433                    $this->bySetPos = explode(',', $value);
434                    break;
435
436                case 'WKST' :
437                    $this->weekStart = strtoupper($value);
438                    break;
439
440            }
441
442        }
443
444        // Parsing exception dates
445        if (isset($this->baseEvent->EXDATE)) {
446            foreach($this->baseEvent->EXDATE as $exDate) {
447
448                foreach(explode(',', (string)$exDate) as $exceptionDate) {
449
450                    $this->exceptionDates[] =
451                        DateTimeParser::parse($exceptionDate, $this->startDate->getTimeZone());
452
453                }
454
455            }
456
457        }
458
459    }
460
461    /**
462     * Returns the current item in the list
463     *
464     * @return DateTime
465     */
466    public function current() {
467
468        if (!$this->valid()) return null;
469        return clone $this->currentDate;
470
471    }
472
473    /**
474     * This method returns the startdate for the current iteration of the
475     * event.
476     *
477     * @return DateTime
478     */
479    public function getDtStart() {
480
481        if (!$this->valid()) return null;
482        return clone $this->currentDate;
483
484    }
485
486    /**
487     * This method returns the enddate for the current iteration of the
488     * event.
489     *
490     * @return DateTime
491     */
492    public function getDtEnd() {
493
494        if (!$this->valid()) return null;
495        $dtEnd = clone $this->currentDate;
496        $dtEnd->add( $this->startDate->diff( $this->endDate ) );
497        return clone $dtEnd;
498
499    }
500
501    /**
502     * Returns a VEVENT object with the updated start and end date.
503     *
504     * Any recurrence information is removed, and this function may return an
505     * 'overridden' event instead.
506     *
507     * This method always returns a cloned instance.
508     *
509     * @return Component\VEvent
510     */
511    public function getEventObject() {
512
513        if ($this->currentOverriddenEvent) {
514            return clone $this->currentOverriddenEvent;
515        }
516        $event = clone $this->baseEvent;
517        unset($event->RRULE);
518        unset($event->EXDATE);
519        unset($event->RDATE);
520        unset($event->EXRULE);
521
522        $event->DTSTART->setDateTime($this->getDTStart(), $event->DTSTART->getDateType());
523        if (isset($event->DTEND)) {
524            $event->DTEND->setDateTime($this->getDtEnd(), $event->DTSTART->getDateType());
525        }
526        if ($this->counter > 0) {
527            $event->{'RECURRENCE-ID'} = (string)$event->DTSTART;
528        }
529
530        return $event;
531
532    }
533
534    /**
535     * Returns the current item number
536     *
537     * @return int
538     */
539    public function key() {
540
541        return $this->counter;
542
543    }
544
545    /**
546     * Whether or not there is a 'next item'
547     *
548     * @return bool
549     */
550    public function valid() {
551
552        if (!is_null($this->count)) {
553            return $this->counter < $this->count;
554        }
555        if (!is_null($this->until)) {
556            return $this->currentDate <= $this->until;
557        }
558        return true;
559
560    }
561
562    /**
563     * Resets the iterator
564     *
565     * @return void
566     */
567    public function rewind() {
568
569        $this->currentDate = clone $this->startDate;
570        $this->counter = 0;
571
572    }
573
574    /**
575     * This method allows you to quickly go to the next occurrence after the
576     * specified date.
577     *
578     * Note that this checks the current 'endDate', not the 'stardDate'. This
579     * means that if you forward to January 1st, the iterator will stop at the
580     * first event that ends *after* January 1st.
581     *
582     * @param DateTime $dt
583     * @return void
584     */
585    public function fastForward(\DateTime $dt) {
586
587        while($this->valid() && $this->getDTEnd() <= $dt) {
588            $this->next();
589        }
590
591    }
592
593    /**
594     * Returns true if this recurring event never ends.
595     *
596     * @return bool
597     */
598    public function isInfinite() {
599
600        return !$this->count && !$this->until;
601
602    }
603
604    /**
605     * Goes on to the next iteration
606     *
607     * @return void
608     */
609    public function next() {
610
611        /*
612        if (!is_null($this->count) && $this->counter >= $this->count) {
613            $this->currentDate = null;
614        }*/
615
616
617        $previousStamp = $this->currentDate->getTimeStamp();
618
619        while(true) {
620
621            $this->currentOverriddenEvent = null;
622
623            // If we have a next date 'stored', we use that
624            if ($this->nextDate) {
625                $this->currentDate = $this->nextDate;
626                $currentStamp = $this->currentDate->getTimeStamp();
627                $this->nextDate = null;
628            } else {
629
630                // Otherwise, we calculate it
631                switch($this->frequency) {
632
633                    case 'hourly' :
634                        $this->nextHourly();
635                        break;
636
637                    case 'daily' :
638                        $this->nextDaily();
639                        break;
640
641                    case 'weekly' :
642                        $this->nextWeekly();
643                        break;
644
645                    case 'monthly' :
646                        $this->nextMonthly();
647                        break;
648
649                    case 'yearly' :
650                        $this->nextYearly();
651                        break;
652
653                }
654                $currentStamp = $this->currentDate->getTimeStamp();
655
656                // Checking exception dates
657                foreach($this->exceptionDates as $exceptionDate) {
658                    if ($this->currentDate == $exceptionDate) {
659                        $this->counter++;
660                        continue 2;
661                    }
662                }
663                foreach($this->overriddenDates as $overriddenDate) {
664                    if ($this->currentDate == $overriddenDate) {
665                        continue 2;
666                    }
667                }
668
669            }
670
671            // Checking overridden events
672            foreach($this->overriddenEvents as $index=>$event) {
673                if ($index > $previousStamp && $index <= $currentStamp) {
674
675                    // We're moving the 'next date' aside, for later use.
676                    $this->nextDate = clone $this->currentDate;
677
678                    $this->currentDate = $event->DTSTART->getDateTime();
679                    $this->currentOverriddenEvent = $event;
680
681                    break;
682                }
683            }
684
685            break;
686
687        }
688
689        /*
690        if (!is_null($this->until)) {
691            if($this->currentDate > $this->until) {
692                $this->currentDate = null;
693            }
694        }*/
695
696        $this->counter++;
697
698    }
699
700    /**
701     * Does the processing for advancing the iterator for hourly frequency.
702     *
703     * @return void
704     */
705    protected function nextHourly() {
706
707        if (!$this->byHour) {
708            $this->currentDate->modify('+' . $this->interval . ' hours');
709            return;
710        }
711    }
712
713    /**
714     * Does the processing for advancing the iterator for daily frequency.
715     *
716     * @return void
717     */
718    protected function nextDaily() {
719
720        if (!$this->byHour && !$this->byDay) {
721            $this->currentDate->modify('+' . $this->interval . ' days');
722            return;
723        }
724
725        if (isset($this->byHour)) {
726            $recurrenceHours = $this->getHours();
727        }
728
729        if (isset($this->byDay)) {
730            $recurrenceDays = $this->getDays();
731        }
732
733        do {
734
735            if ($this->byHour) {
736                if ($this->currentDate->format('G') == '23') {
737                    // to obey the interval rule
738                    $this->currentDate->modify('+' . $this->interval-1 . ' days');
739                }
740
741                $this->currentDate->modify('+1 hours');
742
743            } else {
744                $this->currentDate->modify('+' . $this->interval . ' days');
745
746            }
747
748            // Current day of the week
749            $currentDay = $this->currentDate->format('w');
750
751            // Current hour of the day
752            $currentHour = $this->currentDate->format('G');
753
754        } while (($this->byDay && !in_array($currentDay, $recurrenceDays)) || ($this->byHour && !in_array($currentHour, $recurrenceHours)));
755
756    }
757
758    /**
759     * Does the processing for advancing the iterator for weekly frequency.
760     *
761     * @return void
762     */
763    protected function nextWeekly() {
764
765        if (!$this->byHour && !$this->byDay) {
766            $this->currentDate->modify('+' . $this->interval . ' weeks');
767            return;
768        }
769
770        if ($this->byHour) {
771            $recurrenceHours = $this->getHours();
772        }
773
774        if ($this->byDay) {
775            $recurrenceDays = $this->getDays();
776        }
777
778        // First day of the week:
779        $firstDay = $this->dayMap[$this->weekStart];
780
781        do {
782
783            if ($this->byHour) {
784                $this->currentDate->modify('+1 hours');
785            } else {
786                $this->currentDate->modify('+1 days');
787            }
788
789            // Current day of the week
790            $currentDay = (int) $this->currentDate->format('w');
791
792            // Current hour of the day
793            $currentHour = (int) $this->currentDate->format('G');
794
795            // We need to roll over to the next week
796            if ($currentDay === $firstDay && (!$this->byHour || $currentHour == '0')) {
797                $this->currentDate->modify('+' . $this->interval-1 . ' weeks');
798
799                // We need to go to the first day of this week, but only if we
800                // are not already on this first day of this week.
801                if($this->currentDate->format('w') != $firstDay) {
802                    $this->currentDate->modify('last ' . $this->dayNames[$this->dayMap[$this->weekStart]]);
803                }
804            }
805
806            // We have a match
807        } while (($this->byDay && !in_array($currentDay, $recurrenceDays)) || ($this->byHour && !in_array($currentHour, $recurrenceHours)));
808    }
809
810    /**
811     * Does the processing for advancing the iterator for monthly frequency.
812     *
813     * @return void
814     */
815    protected function nextMonthly() {
816
817        $currentDayOfMonth = $this->currentDate->format('j');
818        if (!$this->byMonthDay && !$this->byDay) {
819
820            // If the current day is higher than the 28th, rollover can
821            // occur to the next month. We Must skip these invalid
822            // entries.
823            if ($currentDayOfMonth < 29) {
824                $this->currentDate->modify('+' . $this->interval . ' months');
825            } else {
826                $increase = 0;
827                do {
828                    $increase++;
829                    $tempDate = clone $this->currentDate;
830                    $tempDate->modify('+ ' . ($this->interval*$increase) . ' months');
831                } while ($tempDate->format('j') != $currentDayOfMonth);
832                $this->currentDate = $tempDate;
833            }
834            return;
835        }
836
837        while(true) {
838
839            $occurrences = $this->getMonthlyOccurrences();
840
841            foreach($occurrences as $occurrence) {
842
843                // The first occurrence thats higher than the current
844                // day of the month wins.
845                if ($occurrence > $currentDayOfMonth) {
846                    break 2;
847                }
848
849            }
850
851            // If we made it all the way here, it means there were no
852            // valid occurrences, and we need to advance to the next
853            // month.
854            $this->currentDate->modify('first day of this month');
855            $this->currentDate->modify('+ ' . $this->interval . ' months');
856
857            // This goes to 0 because we need to start counting at hte
858            // beginning.
859            $currentDayOfMonth = 0;
860
861        }
862
863        $this->currentDate->setDate($this->currentDate->format('Y'), $this->currentDate->format('n'), $occurrence);
864
865    }
866
867    /**
868     * Does the processing for advancing the iterator for yearly frequency.
869     *
870     * @return void
871     */
872    protected function nextYearly() {
873
874        $currentMonth = $this->currentDate->format('n');
875        $currentYear = $this->currentDate->format('Y');
876        $currentDayOfMonth = $this->currentDate->format('j');
877
878        // No sub-rules, so we just advance by year
879        if (!$this->byMonth) {
880
881            // Unless it was a leap day!
882            if ($currentMonth==2 && $currentDayOfMonth==29) {
883
884                $counter = 0;
885                do {
886                    $counter++;
887                    // Here we increase the year count by the interval, until
888                    // we hit a date that's also in a leap year.
889                    //
890                    // We could just find the next interval that's dividable by
891                    // 4, but that would ignore the rule that there's no leap
892                    // year every year that's dividable by a 100, but not by
893                    // 400. (1800, 1900, 2100). So we just rely on the datetime
894                    // functions instead.
895                    $nextDate = clone $this->currentDate;
896                    $nextDate->modify('+ ' . ($this->interval*$counter) . ' years');
897                } while ($nextDate->format('n')!=2);
898                $this->currentDate = $nextDate;
899
900                return;
901
902            }
903
904            // The easiest form
905            $this->currentDate->modify('+' . $this->interval . ' years');
906            return;
907
908        }
909
910        $currentMonth = $this->currentDate->format('n');
911        $currentYear = $this->currentDate->format('Y');
912        $currentDayOfMonth = $this->currentDate->format('j');
913
914        $advancedToNewMonth = false;
915
916        // If we got a byDay or getMonthDay filter, we must first expand
917        // further.
918        if ($this->byDay || $this->byMonthDay) {
919
920            while(true) {
921
922                $occurrences = $this->getMonthlyOccurrences();
923
924                foreach($occurrences as $occurrence) {
925
926                    // The first occurrence that's higher than the current
927                    // day of the month wins.
928                    // If we advanced to the next month or year, the first
929                    // occurrence is always correct.
930                    if ($occurrence > $currentDayOfMonth || $advancedToNewMonth) {
931                        break 2;
932                    }
933
934                }
935
936                // If we made it here, it means we need to advance to
937                // the next month or year.
938                $currentDayOfMonth = 1;
939                $advancedToNewMonth = true;
940                do {
941
942                    $currentMonth++;
943                    if ($currentMonth>12) {
944                        $currentYear+=$this->interval;
945                        $currentMonth = 1;
946                    }
947                } while (!in_array($currentMonth, $this->byMonth));
948
949                $this->currentDate->setDate($currentYear, $currentMonth, $currentDayOfMonth);
950
951            }
952
953            // If we made it here, it means we got a valid occurrence
954            $this->currentDate->setDate($currentYear, $currentMonth, $occurrence);
955            return;
956
957        } else {
958
959            // These are the 'byMonth' rules, if there are no byDay or
960            // byMonthDay sub-rules.
961            do {
962
963                $currentMonth++;
964                if ($currentMonth>12) {
965                    $currentYear+=$this->interval;
966                    $currentMonth = 1;
967                }
968            } while (!in_array($currentMonth, $this->byMonth));
969            $this->currentDate->setDate($currentYear, $currentMonth, $currentDayOfMonth);
970
971            return;
972
973        }
974
975    }
976
977    /**
978     * Returns all the occurrences for a monthly frequency with a 'byDay' or
979     * 'byMonthDay' expansion for the current month.
980     *
981     * The returned list is an array of integers with the day of month (1-31).
982     *
983     * @return array
984     */
985    protected function getMonthlyOccurrences() {
986
987        $startDate = clone $this->currentDate;
988
989        $byDayResults = array();
990
991        // Our strategy is to simply go through the byDays, advance the date to
992        // that point and add it to the results.
993        if ($this->byDay) foreach($this->byDay as $day) {
994
995            $dayName = $this->dayNames[$this->dayMap[substr($day,-2)]];
996
997            // Dayname will be something like 'wednesday'. Now we need to find
998            // all wednesdays in this month.
999            $dayHits = array();
1000
1001            $checkDate = clone $startDate;
1002            $checkDate->modify('first day of this month');
1003            $checkDate->modify($dayName);
1004
1005            do {
1006                $dayHits[] = $checkDate->format('j');
1007                $checkDate->modify('next ' . $dayName);
1008            } while ($checkDate->format('n') === $startDate->format('n'));
1009
1010            // So now we have 'all wednesdays' for month. It is however
1011            // possible that the user only really wanted the 1st, 2nd or last
1012            // wednesday.
1013            if (strlen($day)>2) {
1014                $offset = (int)substr($day,0,-2);
1015
1016                if ($offset>0) {
1017                    // It is possible that the day does not exist, such as a
1018                    // 5th or 6th wednesday of the month.
1019                    if (isset($dayHits[$offset-1])) {
1020                        $byDayResults[] = $dayHits[$offset-1];
1021                    }
1022                } else {
1023
1024                    // if it was negative we count from the end of the array
1025                    $byDayResults[] = $dayHits[count($dayHits) + $offset];
1026                }
1027            } else {
1028                // There was no counter (first, second, last wednesdays), so we
1029                // just need to add the all to the list).
1030                $byDayResults = array_merge($byDayResults, $dayHits);
1031
1032            }
1033
1034        }
1035
1036        $byMonthDayResults = array();
1037        if ($this->byMonthDay) foreach($this->byMonthDay as $monthDay) {
1038
1039            // Removing values that are out of range for this month
1040            if ($monthDay > $startDate->format('t') ||
1041                $monthDay < 0-$startDate->format('t')) {
1042                    continue;
1043            }
1044            if ($monthDay>0) {
1045                $byMonthDayResults[] = $monthDay;
1046            } else {
1047                // Negative values
1048                $byMonthDayResults[] = $startDate->format('t') + 1 + $monthDay;
1049            }
1050        }
1051
1052        // If there was just byDay or just byMonthDay, they just specify our
1053        // (almost) final list. If both were provided, then byDay limits the
1054        // list.
1055        if ($this->byMonthDay && $this->byDay) {
1056            $result = array_intersect($byMonthDayResults, $byDayResults);
1057        } elseif ($this->byMonthDay) {
1058            $result = $byMonthDayResults;
1059        } else {
1060            $result = $byDayResults;
1061        }
1062        $result = array_unique($result);
1063        sort($result, SORT_NUMERIC);
1064
1065        // The last thing that needs checking is the BYSETPOS. If it's set, it
1066        // means only certain items in the set survive the filter.
1067        if (!$this->bySetPos) {
1068            return $result;
1069        }
1070
1071        $filteredResult = array();
1072        foreach($this->bySetPos as $setPos) {
1073
1074            if ($setPos<0) {
1075                $setPos = count($result)-($setPos+1);
1076            }
1077            if (isset($result[$setPos-1])) {
1078                $filteredResult[] = $result[$setPos-1];
1079            }
1080        }
1081
1082        sort($filteredResult, SORT_NUMERIC);
1083        return $filteredResult;
1084
1085    }
1086
1087    protected function getHours()
1088    {
1089        $recurrenceHours = array();
1090        foreach($this->byHour as $byHour) {
1091            $recurrenceHours[] = $byHour;
1092        }
1093
1094        return $recurrenceHours;
1095    }
1096
1097    protected function getDays()
1098    {
1099        $recurrenceDays = array();
1100        foreach($this->byDay as $byDay) {
1101
1102            // The day may be preceeded with a positive (+n) or
1103            // negative (-n) integer. However, this does not make
1104            // sense in 'weekly' so we ignore it here.
1105            $recurrenceDays[] = $this->dayMap[substr($byDay,-2)];
1106
1107        }
1108
1109        return $recurrenceDays;
1110    }
1111}
1112
Note: See TracBrowser for help on using the repository browser.