1<?php
2
3namespace Sabre\VObject\Recur\EventIterator;
4
5use DateTimeImmutable;
6use DateTimeZone;
7use Sabre\VObject\Component\VCalendar;
8use Sabre\VObject\Recur\EventIterator;
9
10class MainTest extends \PHPUnit_Framework_TestCase {
11
12    function testValues() {
13
14        $vcal = new VCalendar();
15        $ev = $vcal->createComponent('VEVENT');
16        $ev->UID = 'bla';
17        $ev->RRULE = 'FREQ=DAILY;BYHOUR=10;BYMINUTE=5;BYSECOND=16;BYWEEKNO=32;BYYEARDAY=100,200';
18        $dtStart = $vcal->createProperty('DTSTART');
19        $dtStart->setDateTime(new DateTimeImmutable('2011-10-07'));
20
21        $ev->add($dtStart);
22
23        $vcal->add($ev);
24
25        $it = new EventIterator($vcal, (string)$ev->UID);
26
27        $this->assertTrue($it->isInfinite());
28
29    }
30
31    /**
32     * @expectedException \Sabre\VObject\InvalidDataException
33     * @depends testValues
34     */
35    function testInvalidFreq() {
36
37        $vcal = new VCalendar();
38        $ev = $vcal->createComponent('VEVENT');
39        $ev->RRULE = 'FREQ=SMONTHLY;INTERVAL=3;UNTIL=20111025T000000Z';
40        $ev->UID = 'foo';
41        $dtStart = $vcal->createProperty('DTSTART');
42        $dtStart->setDateTime(new DateTimeImmutable('2011-10-07', new DateTimeZone('UTC')));
43
44        $ev->add($dtStart);
45        $vcal->add($ev);
46
47        $it = new EventIterator($vcal, (string)$ev->UID);
48
49    }
50
51    /**
52     * @expectedException InvalidArgumentException
53     */
54    function testVCalendarNoUID() {
55
56        $vcal = new VCalendar();
57        $it = new EventIterator($vcal);
58
59    }
60
61    /**
62     * @expectedException InvalidArgumentException
63     */
64    function testVCalendarInvalidUID() {
65
66        $vcal = new VCalendar();
67        $it = new EventIterator($vcal, 'foo');
68
69    }
70
71    /**
72     * @depends testValues
73     */
74    function testHourly() {
75
76        $vcal = new VCalendar();
77        $ev = $vcal->createComponent('VEVENT');
78
79        $ev->UID = 'bla';
80        $ev->RRULE = 'FREQ=HOURLY;INTERVAL=3;UNTIL=20111025T000000Z';
81        $dtStart = $vcal->createProperty('DTSTART');
82        $dtStart->setDateTime(new DateTimeImmutable('2011-10-07 12:00:00', new DateTimeZone('UTC')));
83
84        $ev->add($dtStart);
85        $vcal->add($ev);
86
87        $it = new EventIterator($vcal, $ev->UID);
88
89        // Max is to prevent overflow
90        $max = 12;
91        $result = [];
92        foreach ($it as $item) {
93
94            $result[] = $item;
95            $max--;
96
97            if (!$max) break;
98
99        }
100
101        $tz = new DateTimeZone('UTC');
102
103        $this->assertEquals(
104            [
105                new DateTimeImmutable('2011-10-07 12:00:00', $tz),
106                new DateTimeImmutable('2011-10-07 15:00:00', $tz),
107                new DateTimeImmutable('2011-10-07 18:00:00', $tz),
108                new DateTimeImmutable('2011-10-07 21:00:00', $tz),
109                new DateTimeImmutable('2011-10-08 00:00:00', $tz),
110                new DateTimeImmutable('2011-10-08 03:00:00', $tz),
111                new DateTimeImmutable('2011-10-08 06:00:00', $tz),
112                new DateTimeImmutable('2011-10-08 09:00:00', $tz),
113                new DateTimeImmutable('2011-10-08 12:00:00', $tz),
114                new DateTimeImmutable('2011-10-08 15:00:00', $tz),
115                new DateTimeImmutable('2011-10-08 18:00:00', $tz),
116                new DateTimeImmutable('2011-10-08 21:00:00', $tz),
117            ],
118            $result
119        );
120
121    }
122
123    /**
124     * @depends testValues
125     */
126    function testDaily() {
127
128        $vcal = new VCalendar();
129        $ev = $vcal->createComponent('VEVENT');
130
131        $ev->UID = 'bla';
132        $ev->RRULE = 'FREQ=DAILY;INTERVAL=3;UNTIL=20111025T000000Z';
133        $dtStart = $vcal->createProperty('DTSTART');
134        $dtStart->setDateTime(new DateTimeImmutable('2011-10-07', new DateTimeZone('UTC')));
135
136        $ev->add($dtStart);
137
138        $vcal->add($ev);
139
140        $it = new EventIterator($vcal, $ev->UID);
141
142        // Max is to prevent overflow
143        $max = 12;
144        $result = [];
145        foreach ($it as $item) {
146
147            $result[] = $item;
148            $max--;
149
150            if (!$max) break;
151
152        }
153
154        $tz = new DateTimeZone('UTC');
155
156        $this->assertEquals(
157            [
158                new DateTimeImmutable('2011-10-07', $tz),
159                new DateTimeImmutable('2011-10-10', $tz),
160                new DateTimeImmutable('2011-10-13', $tz),
161                new DateTimeImmutable('2011-10-16', $tz),
162                new DateTimeImmutable('2011-10-19', $tz),
163                new DateTimeImmutable('2011-10-22', $tz),
164                new DateTimeImmutable('2011-10-25', $tz),
165            ],
166            $result
167        );
168
169    }
170
171    /**
172     * @depends testValues
173     */
174    function testNoRRULE() {
175
176        $vcal = new VCalendar();
177        $ev = $vcal->createComponent('VEVENT');
178
179        $ev->UID = 'bla';
180        $dtStart = $vcal->createProperty('DTSTART');
181        $dtStart->setDateTime(new DateTimeImmutable('2011-10-07', new DateTimeZone('UTC')));
182
183        $ev->add($dtStart);
184
185        $vcal->add($ev);
186
187        $it = new EventIterator($vcal, $ev->UID);
188
189        // Max is to prevent overflow
190        $max = 12;
191        $result = [];
192        foreach ($it as $item) {
193
194            $result[] = $item;
195            $max--;
196
197            if (!$max) break;
198
199        }
200
201        $tz = new DateTimeZone('UTC');
202
203        $this->assertEquals(
204            [
205                new DateTimeImmutable('2011-10-07', $tz),
206            ],
207            $result
208        );
209
210    }
211
212    /**
213     * @depends testValues
214     */
215    function testDailyByDayByHour() {
216
217        $vcal = new VCalendar();
218        $ev = $vcal->createComponent('VEVENT');
219
220        $ev->UID = 'bla';
221        $ev->RRULE = 'FREQ=DAILY;BYDAY=SA,SU;BYHOUR=6,7';
222        $dtStart = $vcal->createProperty('DTSTART');
223        $dtStart->setDateTime(new DateTimeImmutable('2011-10-08 06:00:00', new DateTimeZone('UTC')));
224
225        $ev->add($dtStart);
226
227        $vcal->add($ev);
228
229        $it = new EventIterator($vcal, (string)$ev->UID);
230
231        // Grabbing the next 12 items
232        $max = 12;
233        $result = [];
234        foreach ($it as $item) {
235
236            $result[] = $item;
237            $max--;
238
239            if (!$max) break;
240
241        }
242
243        $tz = new DateTimeZone('UTC');
244
245        $this->assertEquals(
246            [
247                new DateTimeImmutable('2011-10-08 06:00:00', $tz),
248                new DateTimeImmutable('2011-10-08 07:00:00', $tz),
249                new DateTimeImmutable('2011-10-09 06:00:00', $tz),
250                new DateTimeImmutable('2011-10-09 07:00:00', $tz),
251                new DateTimeImmutable('2011-10-15 06:00:00', $tz),
252                new DateTimeImmutable('2011-10-15 07:00:00', $tz),
253                new DateTimeImmutable('2011-10-16 06:00:00', $tz),
254                new DateTimeImmutable('2011-10-16 07:00:00', $tz),
255                new DateTimeImmutable('2011-10-22 06:00:00', $tz),
256                new DateTimeImmutable('2011-10-22 07:00:00', $tz),
257                new DateTimeImmutable('2011-10-23 06:00:00', $tz),
258                new DateTimeImmutable('2011-10-23 07:00:00', $tz),
259            ],
260            $result
261        );
262
263    }
264
265    /**
266     * @depends testValues
267     */
268    function testDailyByHour() {
269
270        $vcal = new VCalendar();
271        $ev = $vcal->createComponent('VEVENT');
272
273        $ev->UID = 'bla';
274        $ev->RRULE = 'FREQ=DAILY;INTERVAL=2;BYHOUR=10,11,12,13,14,15';
275        $dtStart = $vcal->createProperty('DTSTART');
276        $dtStart->setDateTime(new DateTimeImmutable('2012-10-11 12:00:00', new DateTimeZone('UTC')));
277
278        $ev->add($dtStart);
279
280        $vcal->add($ev);
281
282        $it = new EventIterator($vcal, (string)$ev->UID);
283
284        // Grabbing the next 12 items
285        $max = 12;
286        $result = [];
287        foreach ($it as $item) {
288
289            $result[] = $item;
290            $max--;
291
292            if (!$max) break;
293
294        }
295
296        $tz = new DateTimeZone('UTC');
297
298        $this->assertEquals(
299            [
300                new DateTimeImmutable('2012-10-11 12:00:00', $tz),
301                new DateTimeImmutable('2012-10-11 13:00:00', $tz),
302                new DateTimeImmutable('2012-10-11 14:00:00', $tz),
303                new DateTimeImmutable('2012-10-11 15:00:00', $tz),
304                new DateTimeImmutable('2012-10-13 10:00:00', $tz),
305                new DateTimeImmutable('2012-10-13 11:00:00', $tz),
306                new DateTimeImmutable('2012-10-13 12:00:00', $tz),
307                new DateTimeImmutable('2012-10-13 13:00:00', $tz),
308                new DateTimeImmutable('2012-10-13 14:00:00', $tz),
309                new DateTimeImmutable('2012-10-13 15:00:00', $tz),
310                new DateTimeImmutable('2012-10-15 10:00:00', $tz),
311                new DateTimeImmutable('2012-10-15 11:00:00', $tz),
312            ],
313            $result
314        );
315
316    }
317
318    /**
319     * @depends testValues
320     */
321    function testDailyByDay() {
322
323        $vcal = new VCalendar();
324        $ev = $vcal->createComponent('VEVENT');
325
326        $ev->UID = 'bla';
327        $ev->RRULE = 'FREQ=DAILY;INTERVAL=2;BYDAY=TU,WE,FR';
328        $dtStart = $vcal->createProperty('DTSTART');
329        $dtStart->setDateTime(new DateTimeImmutable('2011-10-07', new DateTimeZone('UTC')));
330
331        $ev->add($dtStart);
332
333        $vcal->add($ev);
334
335        $it = new EventIterator($vcal, (string)$ev->UID);
336
337        // Grabbing the next 12 items
338        $max = 12;
339        $result = [];
340        foreach ($it as $item) {
341
342            $result[] = $item;
343            $max--;
344
345            if (!$max) break;
346
347        }
348
349        $tz = new DateTimeZone('UTC');
350
351        $this->assertEquals(
352            [
353                new DateTimeImmutable('2011-10-07', $tz),
354                new DateTimeImmutable('2011-10-11', $tz),
355                new DateTimeImmutable('2011-10-19', $tz),
356                new DateTimeImmutable('2011-10-21', $tz),
357                new DateTimeImmutable('2011-10-25', $tz),
358                new DateTimeImmutable('2011-11-02', $tz),
359                new DateTimeImmutable('2011-11-04', $tz),
360                new DateTimeImmutable('2011-11-08', $tz),
361                new DateTimeImmutable('2011-11-16', $tz),
362                new DateTimeImmutable('2011-11-18', $tz),
363                new DateTimeImmutable('2011-11-22', $tz),
364                new DateTimeImmutable('2011-11-30', $tz),
365            ],
366            $result
367        );
368
369    }
370
371    /**
372     * @depends testValues
373     */
374    function testWeekly() {
375
376        $vcal = new VCalendar();
377        $ev = $vcal->createComponent('VEVENT');
378
379        $ev->UID = 'bla';
380        $ev->RRULE = 'FREQ=WEEKLY;INTERVAL=2;COUNT=10';
381        $dtStart = $vcal->createProperty('DTSTART');
382        $dtStart->setDateTime(new DateTimeImmutable('2011-10-07', new DateTimeZone('UTC')));
383
384        $ev->add($dtStart);
385
386        $vcal->add($ev);
387
388        $it = new EventIterator($vcal, (string)$ev->UID);
389
390        // Max is to prevent overflow
391        $max = 12;
392        $result = [];
393        foreach ($it as $item) {
394
395            $result[] = $item;
396            $max--;
397
398            if (!$max) break;
399
400        }
401
402        $tz = new DateTimeZone('UTC');
403
404        $this->assertEquals(
405            [
406                new DateTimeImmutable('2011-10-07', $tz),
407                new DateTimeImmutable('2011-10-21', $tz),
408                new DateTimeImmutable('2011-11-04', $tz),
409                new DateTimeImmutable('2011-11-18', $tz),
410                new DateTimeImmutable('2011-12-02', $tz),
411                new DateTimeImmutable('2011-12-16', $tz),
412                new DateTimeImmutable('2011-12-30', $tz),
413                new DateTimeImmutable('2012-01-13', $tz),
414                new DateTimeImmutable('2012-01-27', $tz),
415                new DateTimeImmutable('2012-02-10', $tz),
416            ],
417            $result
418        );
419
420    }
421
422    /**
423     * @depends testValues
424     */
425    function testWeeklyByDayByHour() {
426
427        $vcal = new VCalendar();
428        $ev = $vcal->createComponent('VEVENT');
429
430        $ev->UID = 'bla';
431        $ev->RRULE = 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=MO;BYHOUR=8,9,10';
432        $dtStart = $vcal->createProperty('DTSTART');
433        $dtStart->setDateTime(new DateTimeImmutable('2011-10-07 08:00:00', new DateTimeZone('UTC')));
434
435        $ev->add($dtStart);
436
437        $vcal->add($ev);
438
439        $it = new EventIterator($vcal, (string)$ev->UID);
440
441        // Grabbing the next 12 items
442        $max = 15;
443        $result = [];
444        foreach ($it as $item) {
445
446            $result[] = $item;
447            $max--;
448
449            if (!$max) break;
450
451        }
452
453        $tz = new DateTimeZone('UTC');
454
455        $this->assertEquals(
456            [
457                new DateTimeImmutable('2011-10-07 08:00:00', $tz),
458                new DateTimeImmutable('2011-10-07 09:00:00', $tz),
459                new DateTimeImmutable('2011-10-07 10:00:00', $tz),
460                new DateTimeImmutable('2011-10-18 08:00:00', $tz),
461                new DateTimeImmutable('2011-10-18 09:00:00', $tz),
462                new DateTimeImmutable('2011-10-18 10:00:00', $tz),
463                new DateTimeImmutable('2011-10-19 08:00:00', $tz),
464                new DateTimeImmutable('2011-10-19 09:00:00', $tz),
465                new DateTimeImmutable('2011-10-19 10:00:00', $tz),
466                new DateTimeImmutable('2011-10-21 08:00:00', $tz),
467                new DateTimeImmutable('2011-10-21 09:00:00', $tz),
468                new DateTimeImmutable('2011-10-21 10:00:00', $tz),
469                new DateTimeImmutable('2011-11-01 08:00:00', $tz),
470                new DateTimeImmutable('2011-11-01 09:00:00', $tz),
471                new DateTimeImmutable('2011-11-01 10:00:00', $tz),
472            ],
473            $result
474        );
475
476    }
477
478    /**
479     * @depends testValues
480     */
481    function testWeeklyByDaySpecificHour() {
482
483        $vcal = new VCalendar();
484        $ev = $vcal->createComponent('VEVENT');
485
486        $ev->UID = 'bla';
487        $ev->RRULE = 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=SU';
488        $dtStart = $vcal->createProperty('DTSTART');
489        $dtStart->setDateTime(new DateTimeImmutable('2011-10-07 18:00:00', new DateTimeZone('UTC')));
490
491        $ev->add($dtStart);
492
493        $vcal->add($ev);
494
495        $it = new EventIterator($vcal, (string)$ev->UID);
496
497        // Grabbing the next 12 items
498        $max = 12;
499        $result = [];
500        foreach ($it as $item) {
501
502            $result[] = $item;
503            $max--;
504
505            if (!$max) break;
506
507        }
508
509        $tz = new DateTimeZone('UTC');
510
511        $this->assertEquals(
512            [
513                new DateTimeImmutable('2011-10-07 18:00:00', $tz),
514                new DateTimeImmutable('2011-10-18 18:00:00', $tz),
515                new DateTimeImmutable('2011-10-19 18:00:00', $tz),
516                new DateTimeImmutable('2011-10-21 18:00:00', $tz),
517                new DateTimeImmutable('2011-11-01 18:00:00', $tz),
518                new DateTimeImmutable('2011-11-02 18:00:00', $tz),
519                new DateTimeImmutable('2011-11-04 18:00:00', $tz),
520                new DateTimeImmutable('2011-11-15 18:00:00', $tz),
521                new DateTimeImmutable('2011-11-16 18:00:00', $tz),
522                new DateTimeImmutable('2011-11-18 18:00:00', $tz),
523                new DateTimeImmutable('2011-11-29 18:00:00', $tz),
524                new DateTimeImmutable('2011-11-30 18:00:00', $tz),
525            ],
526            $result
527        );
528
529    }
530
531    /**
532     * @depends testValues
533     */
534    function testWeeklyByDay() {
535
536        $vcal = new VCalendar();
537        $ev = $vcal->createComponent('VEVENT');
538
539        $ev->UID = 'bla';
540        $ev->RRULE = 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=SU';
541        $dtStart = $vcal->createProperty('DTSTART');
542        $dtStart->setDateTime(new DateTimeImmutable('2011-10-07', new DateTimeZone('UTC')));
543
544        $ev->add($dtStart);
545
546        $vcal->add($ev);
547
548        $it = new EventIterator($vcal, (string)$ev->UID);
549
550        // Grabbing the next 12 items
551        $max = 12;
552        $result = [];
553        foreach ($it as $item) {
554
555            $result[] = $item;
556            $max--;
557
558            if (!$max) break;
559
560        }
561
562        $tz = new DateTimeZone('UTC');
563
564        $this->assertEquals(
565            [
566                new DateTimeImmutable('2011-10-07', $tz),
567                new DateTimeImmutable('2011-10-18', $tz),
568                new DateTimeImmutable('2011-10-19', $tz),
569                new DateTimeImmutable('2011-10-21', $tz),
570                new DateTimeImmutable('2011-11-01', $tz),
571                new DateTimeImmutable('2011-11-02', $tz),
572                new DateTimeImmutable('2011-11-04', $tz),
573                new DateTimeImmutable('2011-11-15', $tz),
574                new DateTimeImmutable('2011-11-16', $tz),
575                new DateTimeImmutable('2011-11-18', $tz),
576                new DateTimeImmutable('2011-11-29', $tz),
577                new DateTimeImmutable('2011-11-30', $tz),
578            ],
579            $result
580        );
581
582    }
583
584    /**
585     * @depends testValues
586     */
587    function testMonthly() {
588
589        $vcal = new VCalendar();
590        $ev = $vcal->createComponent('VEVENT');
591
592        $ev->UID = 'bla';
593        $ev->RRULE = 'FREQ=MONTHLY;INTERVAL=3;COUNT=5';
594        $dtStart = $vcal->createProperty('DTSTART');
595        $dtStart->setDateTime(new DateTimeImmutable('2011-12-05', new DateTimeZone('UTC')));
596
597        $ev->add($dtStart);
598
599        $vcal->add($ev);
600
601        $it = new EventIterator($vcal, (string)$ev->UID);
602
603        $max = 14;
604        $result = [];
605        foreach ($it as $item) {
606
607            $result[] = $item;
608            $max--;
609
610            if (!$max) break;
611
612        }
613
614        $tz = new DateTimeZone('UTC');
615
616        $this->assertEquals(
617            [
618                new DateTimeImmutable('2011-12-05', $tz),
619                new DateTimeImmutable('2012-03-05', $tz),
620                new DateTimeImmutable('2012-06-05', $tz),
621                new DateTimeImmutable('2012-09-05', $tz),
622                new DateTimeImmutable('2012-12-05', $tz),
623            ],
624            $result
625        );
626
627
628    }
629
630    /**
631     * @depends testValues
632     */
633    function testMonthlyEndOfMonth() {
634
635        $vcal = new VCalendar();
636        $ev = $vcal->createComponent('VEVENT');
637
638        $ev->UID = 'bla';
639        $ev->RRULE = 'FREQ=MONTHLY;INTERVAL=2;COUNT=12';
640        $dtStart = $vcal->createProperty('DTSTART');
641        $dtStart->setDateTime(new DateTimeImmutable('2011-12-31', new DateTimeZone('UTC')));
642
643        $ev->add($dtStart);
644
645        $vcal->add($ev);
646
647        $it = new EventIterator($vcal, (string)$ev->UID);
648
649        $max = 14;
650        $result = [];
651        foreach ($it as $item) {
652
653            $result[] = $item;
654            $max--;
655
656            if (!$max) break;
657
658        }
659
660        $tz = new DateTimeZone('UTC');
661
662        $this->assertEquals(
663            [
664                new DateTimeImmutable('2011-12-31', $tz),
665                new DateTimeImmutable('2012-08-31', $tz),
666                new DateTimeImmutable('2012-10-31', $tz),
667                new DateTimeImmutable('2012-12-31', $tz),
668                new DateTimeImmutable('2013-08-31', $tz),
669                new DateTimeImmutable('2013-10-31', $tz),
670                new DateTimeImmutable('2013-12-31', $tz),
671                new DateTimeImmutable('2014-08-31', $tz),
672                new DateTimeImmutable('2014-10-31', $tz),
673                new DateTimeImmutable('2014-12-31', $tz),
674                new DateTimeImmutable('2015-08-31', $tz),
675                new DateTimeImmutable('2015-10-31', $tz),
676            ],
677            $result
678        );
679
680
681    }
682
683    /**
684     * @depends testValues
685     */
686    function testMonthlyByMonthDay() {
687
688        $vcal = new VCalendar();
689        $ev = $vcal->createComponent('VEVENT');
690
691        $ev->UID = 'bla';
692        $ev->RRULE = 'FREQ=MONTHLY;INTERVAL=5;COUNT=9;BYMONTHDAY=1,31,-7';
693        $dtStart = $vcal->createProperty('DTSTART');
694        $dtStart->setDateTime(new DateTimeImmutable('2011-01-01', new DateTimeZone('UTC')));
695
696        $ev->add($dtStart);
697
698        $vcal->add($ev);
699
700        $it = new EventIterator($vcal, (string)$ev->UID);
701
702        $max = 14;
703        $result = [];
704        foreach ($it as $item) {
705
706            $result[] = $item;
707            $max--;
708
709            if (!$max) break;
710
711        }
712
713        $tz = new DateTimeZone('UTC');
714
715        $this->assertEquals(
716            [
717                new DateTimeImmutable('2011-01-01', $tz),
718                new DateTimeImmutable('2011-01-25', $tz),
719                new DateTimeImmutable('2011-01-31', $tz),
720                new DateTimeImmutable('2011-06-01', $tz),
721                new DateTimeImmutable('2011-06-24', $tz),
722                new DateTimeImmutable('2011-11-01', $tz),
723                new DateTimeImmutable('2011-11-24', $tz),
724                new DateTimeImmutable('2012-04-01', $tz),
725                new DateTimeImmutable('2012-04-24', $tz),
726            ],
727            $result
728        );
729
730    }
731
732    /**
733     * A pretty slow test. Had to be marked as 'medium' for phpunit to not die
734     * after 1 second. Would be good to optimize later.
735     *
736     * @depends testValues
737     * @medium
738     */
739    function testMonthlyByDay() {
740
741        $vcal = new VCalendar();
742        $ev = $vcal->createComponent('VEVENT');
743
744        $ev->UID = 'bla';
745        $ev->RRULE = 'FREQ=MONTHLY;INTERVAL=2;COUNT=16;BYDAY=MO,-2TU,+1WE,3TH';
746        $dtStart = $vcal->createProperty('DTSTART');
747        $dtStart->setDateTime(new DateTimeImmutable('2011-01-03', new DateTimeZone('UTC')));
748
749        $ev->add($dtStart);
750
751        $vcal->add($ev);
752
753        $it = new EventIterator($vcal, (string)$ev->UID);
754
755        $max = 20;
756        $result = [];
757        foreach ($it as $item) {
758
759            $result[] = $item;
760            $max--;
761
762            if (!$max) break;
763
764        }
765
766        $tz = new DateTimeZone('UTC');
767
768        $this->assertEquals(
769            [
770                new DateTimeImmutable('2011-01-03', $tz),
771                new DateTimeImmutable('2011-01-05', $tz),
772                new DateTimeImmutable('2011-01-10', $tz),
773                new DateTimeImmutable('2011-01-17', $tz),
774                new DateTimeImmutable('2011-01-18', $tz),
775                new DateTimeImmutable('2011-01-20', $tz),
776                new DateTimeImmutable('2011-01-24', $tz),
777                new DateTimeImmutable('2011-01-31', $tz),
778                new DateTimeImmutable('2011-03-02', $tz),
779                new DateTimeImmutable('2011-03-07', $tz),
780                new DateTimeImmutable('2011-03-14', $tz),
781                new DateTimeImmutable('2011-03-17', $tz),
782                new DateTimeImmutable('2011-03-21', $tz),
783                new DateTimeImmutable('2011-03-22', $tz),
784                new DateTimeImmutable('2011-03-28', $tz),
785                new DateTimeImmutable('2011-05-02', $tz),
786            ],
787            $result
788        );
789
790    }
791
792    /**
793     * @depends testValues
794     */
795    function testMonthlyByDayByMonthDay() {
796
797        $vcal = new VCalendar();
798        $ev = $vcal->createComponent('VEVENT');
799
800        $ev->UID = 'bla';
801        $ev->RRULE = 'FREQ=MONTHLY;COUNT=10;BYDAY=MO;BYMONTHDAY=1';
802        $dtStart = $vcal->createProperty('DTSTART');
803        $dtStart->setDateTime(new DateTimeImmutable('2011-08-01', new DateTimeZone('UTC')));
804
805        $ev->add($dtStart);
806
807        $vcal->add($ev);
808
809        $it = new EventIterator($vcal, (string)$ev->UID);
810
811        $max = 20;
812        $result = [];
813        foreach ($it as $item) {
814
815            $result[] = $item;
816            $max--;
817
818            if (!$max) break;
819
820        }
821
822        $tz = new DateTimeZone('UTC');
823
824        $this->assertEquals(
825            [
826                new DateTimeImmutable('2011-08-01', $tz),
827                new DateTimeImmutable('2012-10-01', $tz),
828                new DateTimeImmutable('2013-04-01', $tz),
829                new DateTimeImmutable('2013-07-01', $tz),
830                new DateTimeImmutable('2014-09-01', $tz),
831                new DateTimeImmutable('2014-12-01', $tz),
832                new DateTimeImmutable('2015-06-01', $tz),
833                new DateTimeImmutable('2016-02-01', $tz),
834                new DateTimeImmutable('2016-08-01', $tz),
835                new DateTimeImmutable('2017-05-01', $tz),
836            ],
837            $result
838        );
839
840    }
841
842    /**
843     * @depends testValues
844     */
845    function testMonthlyByDayBySetPos() {
846
847        $vcal = new VCalendar();
848        $ev = $vcal->createComponent('VEVENT');
849
850        $ev->UID = 'bla';
851        $ev->RRULE = 'FREQ=MONTHLY;COUNT=10;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=1,-1';
852        $dtStart = $vcal->createProperty('DTSTART');
853        $dtStart->setDateTime(new DateTimeImmutable('2011-01-03', new DateTimeZone('UTC')));
854
855        $ev->add($dtStart);
856
857        $vcal->add($ev);
858
859        $it = new EventIterator($vcal, (string)$ev->UID);
860
861        $max = 20;
862        $result = [];
863        foreach ($it as $item) {
864
865            $result[] = $item;
866            $max--;
867
868            if (!$max) break;
869
870        }
871
872        $tz = new DateTimeZone('UTC');
873
874        $this->assertEquals(
875            [
876                new DateTimeImmutable('2011-01-03', $tz),
877                new DateTimeImmutable('2011-01-31', $tz),
878                new DateTimeImmutable('2011-02-01', $tz),
879                new DateTimeImmutable('2011-02-28', $tz),
880                new DateTimeImmutable('2011-03-01', $tz),
881                new DateTimeImmutable('2011-03-31', $tz),
882                new DateTimeImmutable('2011-04-01', $tz),
883                new DateTimeImmutable('2011-04-29', $tz),
884                new DateTimeImmutable('2011-05-02', $tz),
885                new DateTimeImmutable('2011-05-31', $tz),
886            ],
887            $result
888        );
889
890    }
891
892    /**
893     * @depends testValues
894     */
895    function testYearly() {
896
897        $vcal = new VCalendar();
898        $ev = $vcal->createComponent('VEVENT');
899
900        $ev->UID = 'bla';
901        $ev->RRULE = 'FREQ=YEARLY;COUNT=10;INTERVAL=3';
902        $dtStart = $vcal->createProperty('DTSTART');
903        $dtStart->setDateTime(new DateTimeImmutable('2011-01-01', new DateTimeZone('UTC')));
904
905        $ev->add($dtStart);
906
907        $vcal->add($ev);
908
909        $it = new EventIterator($vcal, (string)$ev->UID);
910
911        $max = 20;
912        $result = [];
913        foreach ($it as $item) {
914
915            $result[] = $item;
916            $max--;
917
918            if (!$max) break;
919
920        }
921
922        $tz = new DateTimeZone('UTC');
923
924        $this->assertEquals(
925            [
926                new DateTimeImmutable('2011-01-01', $tz),
927                new DateTimeImmutable('2014-01-01', $tz),
928                new DateTimeImmutable('2017-01-01', $tz),
929                new DateTimeImmutable('2020-01-01', $tz),
930                new DateTimeImmutable('2023-01-01', $tz),
931                new DateTimeImmutable('2026-01-01', $tz),
932                new DateTimeImmutable('2029-01-01', $tz),
933                new DateTimeImmutable('2032-01-01', $tz),
934                new DateTimeImmutable('2035-01-01', $tz),
935                new DateTimeImmutable('2038-01-01', $tz),
936            ],
937            $result
938        );
939
940    }
941
942    /**
943     * @depends testValues
944     */
945    function testYearlyLeapYear() {
946
947        $vcal = new VCalendar();
948        $ev = $vcal->createComponent('VEVENT');
949
950        $ev->UID = 'bla';
951        $ev->RRULE = 'FREQ=YEARLY;COUNT=3';
952        $dtStart = $vcal->createProperty('DTSTART');
953        $dtStart->setDateTime(new DateTimeImmutable('2012-02-29', new DateTimeZone('UTC')));
954
955        $ev->add($dtStart);
956
957        $vcal->add($ev);
958
959        $it = new EventIterator($vcal, (string)$ev->UID);
960
961        $max = 20;
962        $result = [];
963        foreach ($it as $item) {
964
965            $result[] = $item;
966            $max--;
967
968            if (!$max) break;
969
970        }
971
972        $tz = new DateTimeZone('UTC');
973
974        $this->assertEquals(
975            [
976                new DateTimeImmutable('2012-02-29', $tz),
977                new DateTimeImmutable('2016-02-29', $tz),
978                new DateTimeImmutable('2020-02-29', $tz),
979            ],
980            $result
981        );
982
983    }
984
985    /**
986     * @depends testValues
987     */
988    function testYearlyByMonth() {
989
990        $vcal = new VCalendar();
991        $ev = $vcal->createComponent('VEVENT');
992
993        $ev->UID = 'bla';
994        $ev->RRULE = 'FREQ=YEARLY;COUNT=8;INTERVAL=4;BYMONTH=4,10';
995        $dtStart = $vcal->createProperty('DTSTART');
996        $dtStart->setDateTime(new DateTimeImmutable('2011-04-07', new DateTimeZone('UTC')));
997
998        $ev->add($dtStart);
999
1000        $vcal->add($ev);
1001
1002        $it = new EventIterator($vcal, (string)$ev->UID);
1003
1004        $max = 20;
1005        $result = [];
1006        foreach ($it as $item) {
1007
1008            $result[] = $item;
1009            $max--;
1010
1011            if (!$max) break;
1012
1013        }
1014
1015        $tz = new DateTimeZone('UTC');
1016
1017        $this->assertEquals(
1018            [
1019                new DateTimeImmutable('2011-04-07', $tz),
1020                new DateTimeImmutable('2011-10-07', $tz),
1021                new DateTimeImmutable('2015-04-07', $tz),
1022                new DateTimeImmutable('2015-10-07', $tz),
1023                new DateTimeImmutable('2019-04-07', $tz),
1024                new DateTimeImmutable('2019-10-07', $tz),
1025                new DateTimeImmutable('2023-04-07', $tz),
1026                new DateTimeImmutable('2023-10-07', $tz),
1027            ],
1028            $result
1029        );
1030
1031    }
1032
1033    /**
1034     * @depends testValues
1035     */
1036    function testYearlyByMonthByDay() {
1037
1038        $vcal = new VCalendar();
1039        $ev = $vcal->createComponent('VEVENT');
1040
1041        $ev->UID = 'bla';
1042        $ev->RRULE = 'FREQ=YEARLY;COUNT=8;INTERVAL=5;BYMONTH=4,10;BYDAY=1MO,-1SU';
1043        $dtStart = $vcal->createProperty('DTSTART');
1044        $dtStart->setDateTime(new DateTimeImmutable('2011-04-04', new DateTimeZone('UTC')));
1045
1046        $ev->add($dtStart);
1047
1048        $vcal->add($ev);
1049
1050        $it = new EventIterator($vcal, (string)$ev->UID);
1051
1052        $max = 20;
1053        $result = [];
1054        foreach ($it as $item) {
1055
1056            $result[] = $item;
1057            $max--;
1058
1059            if (!$max) break;
1060
1061        }
1062
1063        $tz = new DateTimeZone('UTC');
1064
1065        $this->assertEquals(
1066            [
1067                new DateTimeImmutable('2011-04-04', $tz),
1068                new DateTimeImmutable('2011-04-24', $tz),
1069                new DateTimeImmutable('2011-10-03', $tz),
1070                new DateTimeImmutable('2011-10-30', $tz),
1071                new DateTimeImmutable('2016-04-04', $tz),
1072                new DateTimeImmutable('2016-04-24', $tz),
1073                new DateTimeImmutable('2016-10-03', $tz),
1074                new DateTimeImmutable('2016-10-30', $tz),
1075            ],
1076            $result
1077        );
1078
1079    }
1080
1081    /**
1082     * @depends testValues
1083     */
1084    function testFastForward() {
1085
1086        $vcal = new VCalendar();
1087        $ev = $vcal->createComponent('VEVENT');
1088
1089        $ev->UID = 'bla';
1090        $ev->RRULE = 'FREQ=YEARLY;COUNT=8;INTERVAL=5;BYMONTH=4,10;BYDAY=1MO,-1SU';
1091        $dtStart = $vcal->createProperty('DTSTART');
1092        $dtStart->setDateTime(new DateTimeImmutable('2011-04-04', new DateTimeZone('UTC')));
1093
1094        $ev->add($dtStart);
1095
1096        $vcal->add($ev);
1097
1098        $it = new EventIterator($vcal, (string)$ev->UID);
1099
1100        // The idea is that we're fast-forwarding too far in the future, so
1101        // there will be no results left.
1102        $it->fastForward(new DateTimeImmutable('2020-05-05', new DateTimeZone('UTC')));
1103
1104        $max = 20;
1105        $result = [];
1106        while ($item = $it->current()) {
1107
1108            $result[] = $item;
1109            $max--;
1110
1111            if (!$max) break;
1112            $it->next();
1113
1114        }
1115
1116        $this->assertEquals([], $result);
1117
1118    }
1119
1120    /**
1121     * @depends testValues
1122     */
1123    function testFastForwardAllDayEventThatStopAtTheStartTime() {
1124        $vcal = new VCalendar();
1125        $ev = $vcal->createComponent('VEVENT');
1126
1127        $ev->UID = 'bla';
1128        $ev->RRULE = 'FREQ=DAILY';
1129
1130        $dtStart = $vcal->createProperty('DTSTART');
1131        $dtStart->setDateTime(new DateTimeImmutable('2011-04-04', new DateTimeZone('UTC')));
1132        $ev->add($dtStart);
1133
1134        $dtEnd = $vcal->createProperty('DTSTART');
1135        $dtEnd->setDateTime(new DateTimeImmutable('2011-04-05', new DateTimeZone('UTC')));
1136        $ev->add($dtEnd);
1137
1138        $vcal->add($ev);
1139
1140        $it = new EventIterator($vcal, (string)$ev->UID);
1141
1142        $it->fastForward(new DateTimeImmutable('2011-04-05T000000', new DateTimeZone('UTC')));
1143
1144        $this->assertEquals(new DateTimeImmutable('2011-04-06'), $it->getDTStart());
1145    }
1146
1147    /**
1148     * @depends testValues
1149     */
1150    function testComplexExclusions() {
1151
1152        $vcal = new VCalendar();
1153        $ev = $vcal->createComponent('VEVENT');
1154
1155        $ev->UID = 'bla';
1156        $ev->RRULE = 'FREQ=YEARLY;COUNT=10';
1157        $dtStart = $vcal->createProperty('DTSTART');
1158
1159        $tz = new DateTimeZone('Canada/Eastern');
1160        $dtStart->setDateTime(new DateTimeImmutable('2011-01-01 13:50:20', $tz));
1161
1162        $exDate1 = $vcal->createProperty('EXDATE');
1163        $exDate1->setDateTimes([new DateTimeImmutable('2012-01-01 13:50:20', $tz), new DateTimeImmutable('2014-01-01 13:50:20', $tz)]);
1164        $exDate2 = $vcal->createProperty('EXDATE');
1165        $exDate2->setDateTimes([new DateTimeImmutable('2016-01-01 13:50:20', $tz)]);
1166
1167        $ev->add($dtStart);
1168        $ev->add($exDate1);
1169        $ev->add($exDate2);
1170
1171        $vcal->add($ev);
1172
1173        $it = new EventIterator($vcal, (string)$ev->UID);
1174
1175        $max = 20;
1176        $result = [];
1177        foreach ($it as $item) {
1178
1179            $result[] = $item;
1180            $max--;
1181
1182            if (!$max) break;
1183
1184        }
1185
1186        $this->assertEquals(
1187            [
1188                new DateTimeImmutable('2011-01-01 13:50:20', $tz),
1189                new DateTimeImmutable('2013-01-01 13:50:20', $tz),
1190                new DateTimeImmutable('2015-01-01 13:50:20', $tz),
1191                new DateTimeImmutable('2017-01-01 13:50:20', $tz),
1192                new DateTimeImmutable('2018-01-01 13:50:20', $tz),
1193                new DateTimeImmutable('2019-01-01 13:50:20', $tz),
1194                new DateTimeImmutable('2020-01-01 13:50:20', $tz),
1195            ],
1196            $result
1197        );
1198
1199    }
1200
1201    /**
1202     * @depends testValues
1203     */
1204    function testOverridenEvent() {
1205
1206        $vcal = new VCalendar();
1207
1208        $ev1 = $vcal->createComponent('VEVENT');
1209        $ev1->UID = 'overridden';
1210        $ev1->RRULE = 'FREQ=DAILY;COUNT=10';
1211        $ev1->DTSTART = '20120107T120000Z';
1212        $ev1->SUMMARY = 'baseEvent';
1213
1214        $vcal->add($ev1);
1215
1216        // ev2 overrides an event, and puts it on 2pm instead.
1217        $ev2 = $vcal->createComponent('VEVENT');
1218        $ev2->UID = 'overridden';
1219        $ev2->{'RECURRENCE-ID'} = '20120110T120000Z';
1220        $ev2->DTSTART = '20120110T140000Z';
1221        $ev2->SUMMARY = 'Event 2';
1222
1223        $vcal->add($ev2);
1224
1225        // ev3 overrides an event, and puts it 2 days and 2 hours later
1226        $ev3 = $vcal->createComponent('VEVENT');
1227        $ev3->UID = 'overridden';
1228        $ev3->{'RECURRENCE-ID'} = '20120113T120000Z';
1229        $ev3->DTSTART = '20120115T140000Z';
1230        $ev3->SUMMARY = 'Event 3';
1231
1232        $vcal->add($ev3);
1233
1234        $it = new EventIterator($vcal, 'overridden');
1235
1236        $dates = [];
1237        $summaries = [];
1238        while ($it->valid()) {
1239
1240            $dates[] = $it->getDTStart();
1241            $summaries[] = (string)$it->getEventObject()->SUMMARY;
1242            $it->next();
1243
1244        }
1245
1246        $tz = new DateTimeZone('UTC');
1247        $this->assertEquals([
1248            new DateTimeImmutable('2012-01-07 12:00:00', $tz),
1249            new DateTimeImmutable('2012-01-08 12:00:00', $tz),
1250            new DateTimeImmutable('2012-01-09 12:00:00', $tz),
1251            new DateTimeImmutable('2012-01-10 14:00:00', $tz),
1252            new DateTimeImmutable('2012-01-11 12:00:00', $tz),
1253            new DateTimeImmutable('2012-01-12 12:00:00', $tz),
1254            new DateTimeImmutable('2012-01-14 12:00:00', $tz),
1255            new DateTimeImmutable('2012-01-15 12:00:00', $tz),
1256            new DateTimeImmutable('2012-01-15 14:00:00', $tz),
1257            new DateTimeImmutable('2012-01-16 12:00:00', $tz),
1258        ], $dates);
1259
1260        $this->assertEquals([
1261            'baseEvent',
1262            'baseEvent',
1263            'baseEvent',
1264            'Event 2',
1265            'baseEvent',
1266            'baseEvent',
1267            'baseEvent',
1268            'baseEvent',
1269            'Event 3',
1270            'baseEvent',
1271        ], $summaries);
1272
1273    }
1274
1275    /**
1276     * @depends testValues
1277     */
1278    function testOverridenEvent2() {
1279
1280        $vcal = new VCalendar();
1281
1282        $ev1 = $vcal->createComponent('VEVENT');
1283        $ev1->UID = 'overridden';
1284        $ev1->RRULE = 'FREQ=WEEKLY;COUNT=3';
1285        $ev1->DTSTART = '20120112T120000Z';
1286        $ev1->SUMMARY = 'baseEvent';
1287
1288        $vcal->add($ev1);
1289
1290        // ev2 overrides an event, and puts it 6 days earlier instead.
1291        $ev2 = $vcal->createComponent('VEVENT');
1292        $ev2->UID = 'overridden';
1293        $ev2->{'RECURRENCE-ID'} = '20120119T120000Z';
1294        $ev2->DTSTART = '20120113T120000Z';
1295        $ev2->SUMMARY = 'Override!';
1296
1297        $vcal->add($ev2);
1298
1299        $it = new EventIterator($vcal, 'overridden');
1300
1301        $dates = [];
1302        $summaries = [];
1303        while ($it->valid()) {
1304
1305            $dates[] = $it->getDTStart();
1306            $summaries[] = (string)$it->getEventObject()->SUMMARY;
1307            $it->next();
1308
1309        }
1310
1311        $tz = new DateTimeZone('UTC');
1312        $this->assertEquals([
1313            new DateTimeImmutable('2012-01-12 12:00:00', $tz),
1314            new DateTimeImmutable('2012-01-13 12:00:00', $tz),
1315            new DateTimeImmutable('2012-01-26 12:00:00', $tz),
1316
1317        ], $dates);
1318
1319        $this->assertEquals([
1320            'baseEvent',
1321            'Override!',
1322            'baseEvent',
1323        ], $summaries);
1324
1325    }
1326
1327    /**
1328     * @depends testValues
1329     */
1330    function testOverridenEventNoValuesExpected() {
1331
1332        $vcal = new VCalendar();
1333        $ev1 = $vcal->createComponent('VEVENT');
1334
1335        $ev1->UID = 'overridden';
1336        $ev1->RRULE = 'FREQ=WEEKLY;COUNT=3';
1337        $ev1->DTSTART = '20120124T120000Z';
1338        $ev1->SUMMARY = 'baseEvent';
1339
1340        $vcal->add($ev1);
1341
1342        // ev2 overrides an event, and puts it 6 days earlier instead.
1343        $ev2 = $vcal->createComponent('VEVENT');
1344        $ev2->UID = 'overridden';
1345        $ev2->{'RECURRENCE-ID'} = '20120131T120000Z';
1346        $ev2->DTSTART = '20120125T120000Z';
1347        $ev2->SUMMARY = 'Override!';
1348
1349        $vcal->add($ev2);
1350
1351        $it = new EventIterator($vcal, 'overridden');
1352
1353        $dates = [];
1354        $summaries = [];
1355
1356        // The reported problem was specifically related to the VCALENDAR
1357        // expansion. In this parcitular case, we had to forward to the 28th of
1358        // january.
1359        $it->fastForward(new DateTimeImmutable('2012-01-28 23:00:00'));
1360
1361        // We stop the loop when it hits the 6th of februari. Normally this
1362        // iterator would hit 24, 25 (overriden from 31) and 7 feb but because
1363        // we 'filter' from the 28th till the 6th, we should get 0 results.
1364        while ($it->valid() && $it->getDTStart() < new DateTimeImmutable('2012-02-06 23:00:00')) {
1365
1366            $dates[] = $it->getDTStart();
1367            $summaries[] = (string)$it->getEventObject()->SUMMARY;
1368            $it->next();
1369
1370        }
1371
1372        $this->assertEquals([], $dates);
1373        $this->assertEquals([], $summaries);
1374
1375    }
1376
1377    /**
1378     * @depends testValues
1379     */
1380    function testRDATE() {
1381
1382        $vcal = new VCalendar();
1383        $ev = $vcal->createComponent('VEVENT');
1384
1385        $ev->UID = 'bla';
1386        $ev->RDATE = [
1387            new DateTimeImmutable('2014-08-07', new DateTimeZone('UTC')),
1388            new DateTimeImmutable('2014-08-08', new DateTimeZone('UTC')),
1389        ];
1390        $dtStart = $vcal->createProperty('DTSTART');
1391        $dtStart->setDateTime(new DateTimeImmutable('2011-10-07', new DateTimeZone('UTC')));
1392
1393        $ev->add($dtStart);
1394
1395        $vcal->add($ev);
1396
1397        $it = new EventIterator($vcal, $ev->UID);
1398
1399        // Max is to prevent overflow
1400        $max = 12;
1401        $result = [];
1402        foreach ($it as $item) {
1403
1404            $result[] = $item;
1405            $max--;
1406
1407            if (!$max) break;
1408
1409        }
1410
1411        $tz = new DateTimeZone('UTC');
1412
1413        $this->assertEquals(
1414            [
1415                new DateTimeImmutable('2011-10-07', $tz),
1416                new DateTimeImmutable('2014-08-07', $tz),
1417                new DateTimeImmutable('2014-08-08', $tz),
1418            ],
1419            $result
1420        );
1421
1422    }
1423
1424    /**
1425     * @depends testValues
1426     * @expectedException \InvalidArgumentException
1427     */
1428    function testNoMasterBadUID() {
1429
1430        $vcal = new VCalendar();
1431        // ev2 overrides an event, and puts it on 2pm instead.
1432        $ev2 = $vcal->createComponent('VEVENT');
1433        $ev2->UID = 'overridden';
1434        $ev2->{'RECURRENCE-ID'} = '20120110T120000Z';
1435        $ev2->DTSTART = '20120110T140000Z';
1436        $ev2->SUMMARY = 'Event 2';
1437
1438        $vcal->add($ev2);
1439
1440        // ev3 overrides an event, and puts it 2 days and 2 hours later
1441        $ev3 = $vcal->createComponent('VEVENT');
1442        $ev3->UID = 'overridden';
1443        $ev3->{'RECURRENCE-ID'} = '20120113T120000Z';
1444        $ev3->DTSTART = '20120115T140000Z';
1445        $ev3->SUMMARY = 'Event 3';
1446
1447        $vcal->add($ev3);
1448
1449        $it = new EventIterator($vcal, 'broken');
1450
1451    }
1452}
1453