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