1<?php
2
3namespace Sabre\VObject;
4
5use DateInterval;
6use DateTimeImmutable;
7use DateTimeZone;
8
9class DateTimeParserTest extends \PHPUnit_Framework_TestCase {
10
11    function testParseICalendarDuration() {
12
13        $this->assertEquals('+1 weeks', DateTimeParser::parseDuration('P1W', true));
14        $this->assertEquals('+5 days',  DateTimeParser::parseDuration('P5D', true));
15        $this->assertEquals('+5 days 3 hours 50 minutes 12 seconds', DateTimeParser::parseDuration('P5DT3H50M12S', true));
16        $this->assertEquals('-1 weeks 50 minutes', DateTimeParser::parseDuration('-P1WT50M', true));
17        $this->assertEquals('+50 days 3 hours 2 seconds', DateTimeParser::parseDuration('+P50DT3H2S', true));
18        $this->assertEquals('+0 seconds', DateTimeParser::parseDuration('+PT0S', true));
19        $this->assertEquals(new DateInterval('PT0S'), DateTimeParser::parseDuration('PT0S'));
20
21    }
22
23    function testParseICalendarDurationDateInterval() {
24
25        $expected = new DateInterval('P7D');
26        $this->assertEquals($expected, DateTimeParser::parseDuration('P1W'));
27        $this->assertEquals($expected, DateTimeParser::parse('P1W'));
28
29        $expected = new DateInterval('PT3M');
30        $expected->invert = true;
31        $this->assertEquals($expected, DateTimeParser::parseDuration('-PT3M'));
32
33    }
34
35    /**
36     * @expectedException \Sabre\VObject\InvalidDataException
37     */
38    function testParseICalendarDurationFail() {
39
40        DateTimeParser::parseDuration('P1X', true);
41
42    }
43
44    function testParseICalendarDateTime() {
45
46        $dateTime = DateTimeParser::parseDateTime('20100316T141405');
47
48        $compare = new DateTimeImmutable('2010-03-16 14:14:05', new DateTimeZone('UTC'));
49
50        $this->assertEquals($compare, $dateTime);
51
52    }
53
54    /**
55     * @depends testParseICalendarDateTime
56     * @expectedException \Sabre\VObject\InvalidDataException
57     */
58    function testParseICalendarDateTimeBadFormat() {
59
60        $dateTime = DateTimeParser::parseDateTime('20100316T141405 ');
61
62    }
63
64    /**
65     * @depends testParseICalendarDateTime
66     * @expectedException \Sabre\VObject\InvalidDataException
67     */
68    function testParseICalendarDateTimeInvalidTime() {
69
70        $dateTime = DateTimeParser::parseDateTime('20100316T251405');
71
72    }
73
74    /**
75     * @depends testParseICalendarDateTime
76     */
77    function testParseICalendarDateTimeUTC() {
78
79        $dateTime = DateTimeParser::parseDateTime('20100316T141405Z');
80
81        $compare = new DateTimeImmutable('2010-03-16 14:14:05', new DateTimeZone('UTC'));
82        $this->assertEquals($compare, $dateTime);
83
84    }
85
86    /**
87     * @depends testParseICalendarDateTime
88     */
89    function testParseICalendarDateTimeUTC2() {
90
91        $dateTime = DateTimeParser::parseDateTime('20101211T160000Z');
92
93        $compare = new DateTimeImmutable('2010-12-11 16:00:00', new DateTimeZone('UTC'));
94        $this->assertEquals($compare, $dateTime);
95
96    }
97
98    /**
99     * @depends testParseICalendarDateTime
100     */
101    function testParseICalendarDateTimeCustomTimeZone() {
102
103        $dateTime = DateTimeParser::parseDateTime('20100316T141405', new DateTimeZone('Europe/Amsterdam'));
104
105        $compare = new DateTimeImmutable('2010-03-16 14:14:05', new DateTimeZone('Europe/Amsterdam'));
106        $this->assertEquals($compare, $dateTime);
107
108    }
109
110    function testParseICalendarDate() {
111
112        $dateTime = DateTimeParser::parseDate('20100316');
113
114        $expected = new DateTimeImmutable('2010-03-16 00:00:00', new DateTimeZone('UTC'));
115
116        $this->assertEquals($expected, $dateTime);
117
118        $dateTime = DateTimeParser::parse('20100316');
119        $this->assertEquals($expected, $dateTime);
120
121    }
122
123    /**
124     * TCheck if a date with year > 4000 will not throw an exception. iOS seems to use 45001231 in yearly recurring events
125     */
126    function testParseICalendarDateGreaterThan4000() {
127
128        $dateTime = DateTimeParser::parseDate('45001231');
129
130        $expected = new DateTimeImmutable('4500-12-31 00:00:00', new DateTimeZone('UTC'));
131
132        $this->assertEquals($expected, $dateTime);
133
134        $dateTime = DateTimeParser::parse('45001231');
135        $this->assertEquals($expected, $dateTime);
136
137    }
138
139    /**
140     * Check if a datetime with year > 4000 will not throw an exception. iOS seems to use 45001231T235959 in yearly recurring events
141     */
142    function testParseICalendarDateTimeGreaterThan4000() {
143
144        $dateTime = DateTimeParser::parseDateTime('45001231T235959');
145
146        $expected = new DateTimeImmutable('4500-12-31 23:59:59', new DateTimeZone('UTC'));
147
148        $this->assertEquals($expected, $dateTime);
149
150        $dateTime = DateTimeParser::parse('45001231T235959');
151        $this->assertEquals($expected, $dateTime);
152
153    }
154
155    /**
156     * @depends testParseICalendarDate
157     * @expectedException \Sabre\VObject\InvalidDataException
158     */
159    function testParseICalendarDateBadFormat() {
160
161        $dateTime = DateTimeParser::parseDate('20100316T141405');
162
163    }
164
165    /**
166     * @depends testParseICalendarDate
167     * @expectedException \Sabre\VObject\InvalidDataException
168     */
169    function testParseICalendarDateInvalidDate() {
170
171        $dateTime = DateTimeParser::parseDate('20101331');
172
173    }
174
175    /**
176     * @dataProvider vcardDates
177     */
178    function testVCardDate($input, $output) {
179
180        $this->assertEquals(
181            $output,
182            DateTimeParser::parseVCardDateTime($input)
183        );
184
185    }
186
187    /**
188     * @expectedException \Sabre\VObject\InvalidDataException
189     */
190    function testBadVCardDate() {
191
192        DateTimeParser::parseVCardDateTime('1985---01');
193
194    }
195
196    /**
197     * @expectedException \Sabre\VObject\InvalidDataException
198     */
199    function testBadVCardTime() {
200
201        DateTimeParser::parseVCardTime('23:12:166');
202
203    }
204
205    function vcardDates() {
206
207        return [
208            [
209                "19961022T140000",
210                [
211                    "year"     => 1996,
212                    "month"    => 10,
213                    "date"     => 22,
214                    "hour"     => 14,
215                    "minute"   => 00,
216                    "second"   => 00,
217                    "timezone" => null
218                ],
219            ],
220            [
221                "--1022T1400",
222                [
223                    "year"     => null,
224                    "month"    => 10,
225                    "date"     => 22,
226                    "hour"     => 14,
227                    "minute"   => 00,
228                    "second"   => null,
229                    "timezone" => null
230                ],
231            ],
232            [
233                "---22T14",
234                [
235                    "year"     => null,
236                    "month"    => null,
237                    "date"     => 22,
238                    "hour"     => 14,
239                    "minute"   => null,
240                    "second"   => null,
241                    "timezone" => null
242                ],
243            ],
244            [
245                "19850412",
246                [
247                    "year"     => 1985,
248                    "month"    => 4,
249                    "date"     => 12,
250                    "hour"     => null,
251                    "minute"   => null,
252                    "second"   => null,
253                    "timezone" => null
254                ],
255            ],
256            [
257                "1985-04",
258                [
259                    "year"     => 1985,
260                    "month"    => 04,
261                    "date"     => null,
262                    "hour"     => null,
263                    "minute"   => null,
264                    "second"   => null,
265                    "timezone" => null
266                ],
267            ],
268            [
269                "1985",
270                [
271                    "year"     => 1985,
272                    "month"    => null,
273                    "date"     => null,
274                    "hour"     => null,
275                    "minute"   => null,
276                    "second"   => null,
277                    "timezone" => null
278                ],
279            ],
280            [
281                "--0412",
282                [
283                    "year"     => null,
284                    "month"    => 4,
285                    "date"     => 12,
286                    "hour"     => null,
287                    "minute"   => null,
288                    "second"   => null,
289                    "timezone" => null
290                ],
291            ],
292            [
293                "---12",
294                [
295                    "year"     => null,
296                    "month"    => null,
297                    "date"     => 12,
298                    "hour"     => null,
299                    "minute"   => null,
300                    "second"   => null,
301                    "timezone" => null
302                ],
303            ],
304            [
305                "T102200",
306                [
307                    "year"     => null,
308                    "month"    => null,
309                    "date"     => null,
310                    "hour"     => 10,
311                    "minute"   => 22,
312                    "second"   => 0,
313                    "timezone" => null
314                ],
315            ],
316            [
317                "T1022",
318                [
319                    "year"     => null,
320                    "month"    => null,
321                    "date"     => null,
322                    "hour"     => 10,
323                    "minute"   => 22,
324                    "second"   => null,
325                    "timezone" => null
326                ],
327            ],
328            [
329                "T10",
330                [
331                    "year"     => null,
332                    "month"    => null,
333                    "date"     => null,
334                    "hour"     => 10,
335                    "minute"   => null,
336                    "second"   => null,
337                    "timezone" => null
338                ],
339            ],
340            [
341                "T-2200",
342                [
343                    "year"     => null,
344                    "month"    => null,
345                    "date"     => null,
346                    "hour"     => null,
347                    "minute"   => 22,
348                    "second"   => 00,
349                    "timezone" => null
350                ],
351            ],
352            [
353                "T--00",
354                [
355                    "year"     => null,
356                    "month"    => null,
357                    "date"     => null,
358                    "hour"     => null,
359                    "minute"   => null,
360                    "second"   => 00,
361                    "timezone" => null
362                ],
363            ],
364            [
365                "T102200Z",
366                [
367                    "year"     => null,
368                    "month"    => null,
369                    "date"     => null,
370                    "hour"     => 10,
371                    "minute"   => 22,
372                    "second"   => 00,
373                    "timezone" => 'Z'
374                ],
375            ],
376            [
377                "T102200-0800",
378                [
379                    "year"     => null,
380                    "month"    => null,
381                    "date"     => null,
382                    "hour"     => 10,
383                    "minute"   => 22,
384                    "second"   => 00,
385                    "timezone" => '-0800'
386                ],
387            ],
388
389            // extended format
390            [
391                "2012-11-29T15:10:53Z",
392                [
393                    "year"     => 2012,
394                    "month"    => 11,
395                    "date"     => 29,
396                    "hour"     => 15,
397                    "minute"   => 10,
398                    "second"   => 53,
399                    "timezone" => 'Z'
400                ],
401            ],
402
403            // with milliseconds
404            [
405                "20121129T151053.123Z",
406                [
407                    "year"     => 2012,
408                    "month"    => 11,
409                    "date"     => 29,
410                    "hour"     => 15,
411                    "minute"   => 10,
412                    "second"   => 53,
413                    "timezone" => 'Z'
414                ],
415            ],
416
417            // extended format with milliseconds
418            [
419                "2012-11-29T15:10:53.123Z",
420                [
421                    "year"     => 2012,
422                    "month"    => 11,
423                    "date"     => 29,
424                    "hour"     => 15,
425                    "minute"   => 10,
426                    "second"   => 53,
427                    "timezone" => 'Z'
428                ],
429            ],
430        ];
431
432    }
433
434    function testDateAndOrTime_DateWithYearMonthDay() {
435
436        $this->assertDateAndOrTimeEqualsTo(
437            '20150128',
438            [
439                'year'  => '2015',
440                'month' => '01',
441                'date'  => '28'
442            ]
443        );
444
445    }
446
447    function testDateAndOrTime_DateWithYearMonth() {
448
449        $this->assertDateAndOrTimeEqualsTo(
450            '2015-01',
451            [
452                'year'  => '2015',
453                'month' => '01'
454            ]
455        );
456
457    }
458
459    function testDateAndOrTime_DateWithMonth() {
460
461        $this->assertDateAndOrTimeEqualsTo(
462            '--01',
463            [
464                'month' => '01'
465            ]
466        );
467
468    }
469
470    function testDateAndOrTime_DateWithMonthDay() {
471
472        $this->assertDateAndOrTimeEqualsTo(
473            '--0128',
474            [
475                'month' => '01',
476                'date'  => '28'
477            ]
478        );
479
480    }
481
482    function testDateAndOrTime_DateWithDay() {
483
484        $this->assertDateAndOrTimeEqualsTo(
485            '---28',
486            [
487                'date' => '28'
488            ]
489        );
490
491    }
492
493    function testDateAndOrTime_TimeWithHour() {
494
495        $this->assertDateAndOrTimeEqualsTo(
496            '13',
497            [
498                'hour' => '13'
499            ]
500        );
501
502    }
503
504    function testDateAndOrTime_TimeWithHourMinute() {
505
506        $this->assertDateAndOrTimeEqualsTo(
507            '1353',
508            [
509                'hour'   => '13',
510                'minute' => '53'
511            ]
512        );
513
514    }
515
516    function testDateAndOrTime_TimeWithHourSecond() {
517
518        $this->assertDateAndOrTimeEqualsTo(
519            '135301',
520            [
521                'hour'   => '13',
522                'minute' => '53',
523                'second' => '01'
524            ]
525
526        );
527
528    }
529
530    function testDateAndOrTime_TimeWithMinute() {
531
532        $this->assertDateAndOrTimeEqualsTo(
533            '-53',
534            [
535                'minute' => '53'
536            ]
537        );
538
539    }
540
541    function testDateAndOrTime_TimeWithMinuteSecond() {
542
543        $this->assertDateAndOrTimeEqualsTo(
544            '-5301',
545            [
546                'minute' => '53',
547                'second' => '01'
548            ]
549        );
550
551    }
552
553    function testDateAndOrTime_TimeWithSecond() {
554
555        $this->assertTrue(true);
556
557        /**
558         * This is unreachable due to a conflict between date and time pattern.
559         * This is an error in the specification, not in our implementation.
560         */
561    }
562
563    function testDateAndOrTime_TimeWithSecondZ() {
564
565        $this->assertDateAndOrTimeEqualsTo(
566            '--01Z',
567            [
568                'second'   => '01',
569                'timezone' => 'Z'
570            ]
571        );
572
573    }
574
575    function testDateAndOrTime_TimeWithSecondTZ() {
576
577        $this->assertDateAndOrTimeEqualsTo(
578            '--01+1234',
579            [
580                'second'   => '01',
581                'timezone' => '+1234'
582            ]
583        );
584
585    }
586
587    function testDateAndOrTime_DateTimeWithYearMonthDayHour() {
588
589        $this->assertDateAndOrTimeEqualsTo(
590            '20150128T13',
591            [
592                'year'  => '2015',
593                'month' => '01',
594                'date'  => '28',
595                'hour'  => '13'
596            ]
597        );
598
599    }
600
601    function testDateAndOrTime_DateTimeWithMonthDayHour() {
602
603        $this->assertDateAndOrTimeEqualsTo(
604            '--0128T13',
605            [
606                'month' => '01',
607                'date'  => '28',
608                'hour'  => '13'
609            ]
610        );
611
612    }
613
614    function testDateAndOrTime_DateTimeWithDayHour() {
615
616        $this->assertDateAndOrTimeEqualsTo(
617            '---28T13',
618            [
619                'date' => '28',
620                'hour' => '13'
621            ]
622        );
623
624    }
625
626    function testDateAndOrTime_DateTimeWithDayHourMinute() {
627
628        $this->assertDateAndOrTimeEqualsTo(
629            '---28T1353',
630            [
631                'date'   => '28',
632                'hour'   => '13',
633                'minute' => '53'
634            ]
635        );
636
637    }
638
639    function testDateAndOrTime_DateTimeWithDayHourMinuteSecond() {
640
641        $this->assertDateAndOrTimeEqualsTo(
642            '---28T135301',
643            [
644                'date'   => '28',
645                'hour'   => '13',
646                'minute' => '53',
647                'second' => '01'
648            ]
649        );
650
651    }
652
653    function testDateAndOrTime_DateTimeWithDayHourZ() {
654
655        $this->assertDateAndOrTimeEqualsTo(
656            '---28T13Z',
657            [
658                'date'     => '28',
659                'hour'     => '13',
660                'timezone' => 'Z'
661            ]
662        );
663
664    }
665
666    function testDateAndOrTime_DateTimeWithDayHourTZ() {
667
668        $this->assertDateAndOrTimeEqualsTo(
669            '---28T13+1234',
670            [
671                'date'     => '28',
672                'hour'     => '13',
673                'timezone' => '+1234'
674            ]
675        );
676
677    }
678
679    protected function assertDateAndOrTimeEqualsTo($date, $parts) {
680
681        $this->assertSame(
682            DateTimeParser::parseVCardDateAndOrTime($date),
683            array_merge(
684                [
685                    'year'     => null,
686                    'month'    => null,
687                    'date'     => null,
688                    'hour'     => null,
689                    'minute'   => null,
690                    'second'   => null,
691                    'timezone' => null
692                ],
693                $parts
694            )
695        );
696
697    }
698
699}
700