1<?php 2 3namespace Sabre\VObject\Recur; 4 5use DateTime; 6use DateTimeZone; 7 8class RRuleIteratorTest extends \PHPUnit_Framework_TestCase { 9 10 function testHourly() { 11 12 $this->parse( 13 'FREQ=HOURLY;INTERVAL=3;COUNT=12', 14 '2011-10-07 12:00:00', 15 array( 16 '2011-10-07 12:00:00', 17 '2011-10-07 15:00:00', 18 '2011-10-07 18:00:00', 19 '2011-10-07 21:00:00', 20 '2011-10-08 00:00:00', 21 '2011-10-08 03:00:00', 22 '2011-10-08 06:00:00', 23 '2011-10-08 09:00:00', 24 '2011-10-08 12:00:00', 25 '2011-10-08 15:00:00', 26 '2011-10-08 18:00:00', 27 '2011-10-08 21:00:00', 28 ) 29 ); 30 31 } 32 33 function testDaily() { 34 35 $this->parse( 36 'FREQ=DAILY;INTERVAL=3;UNTIL=20111025T000000Z', 37 '2011-10-07', 38 array( 39 '2011-10-07 00:00:00', 40 '2011-10-10 00:00:00', 41 '2011-10-13 00:00:00', 42 '2011-10-16 00:00:00', 43 '2011-10-19 00:00:00', 44 '2011-10-22 00:00:00', 45 '2011-10-25 00:00:00', 46 ) 47 ); 48 49 } 50 51 function testDailyByDayByHour() { 52 53 $this->parse( 54 'FREQ=DAILY;BYDAY=SA,SU;BYHOUR=6,7', 55 '2011-10-08 06:00:00', 56 array( 57 '2011-10-08 06:00:00', 58 '2011-10-08 07:00:00', 59 '2011-10-09 06:00:00', 60 '2011-10-09 07:00:00', 61 '2011-10-15 06:00:00', 62 '2011-10-15 07:00:00', 63 '2011-10-16 06:00:00', 64 '2011-10-16 07:00:00', 65 '2011-10-22 06:00:00', 66 '2011-10-22 07:00:00', 67 '2011-10-23 06:00:00', 68 '2011-10-23 07:00:00', 69 ) 70 ); 71 72 } 73 74 function testDailyByHour() { 75 76 $this->parse( 77 'FREQ=DAILY;INTERVAL=2;BYHOUR=10,11,12,13,14,15', 78 '2012-10-11 12:00:00', 79 array( 80 '2012-10-11 12:00:00', 81 '2012-10-11 13:00:00', 82 '2012-10-11 14:00:00', 83 '2012-10-11 15:00:00', 84 '2012-10-13 10:00:00', 85 '2012-10-13 11:00:00', 86 '2012-10-13 12:00:00', 87 '2012-10-13 13:00:00', 88 '2012-10-13 14:00:00', 89 '2012-10-13 15:00:00', 90 '2012-10-15 10:00:00', 91 '2012-10-15 11:00:00', 92 ) 93 ); 94 95 } 96 97 function testDailyByDay() { 98 99 $this->parse( 100 'FREQ=DAILY;INTERVAL=2;BYDAY=TU,WE,FR', 101 '2011-10-07 12:00:00', 102 array( 103 '2011-10-07 12:00:00', 104 '2011-10-11 12:00:00', 105 '2011-10-19 12:00:00', 106 '2011-10-21 12:00:00', 107 '2011-10-25 12:00:00', 108 '2011-11-02 12:00:00', 109 '2011-11-04 12:00:00', 110 '2011-11-08 12:00:00', 111 '2011-11-16 12:00:00', 112 '2011-11-18 12:00:00', 113 '2011-11-22 12:00:00', 114 '2011-11-30 12:00:00', 115 ) 116 ); 117 118 } 119 120 function testDailyCount() { 121 122 $this->parse( 123 'FREQ=DAILY;COUNT=5', 124 '2014-08-01 18:03:00', 125 array( 126 '2014-08-01 18:03:00', 127 '2014-08-02 18:03:00', 128 '2014-08-03 18:03:00', 129 '2014-08-04 18:03:00', 130 '2014-08-05 18:03:00', 131 ) 132 ); 133 134 } 135 136 function testDailyByMonth() { 137 138 $this->parse( 139 'FREQ=DAILY;BYMONTH=9,10;BYDAY=SU', 140 '2007-10-04 16:00:00', 141 array( 142 "2013-09-29 16:00:00", 143 "2013-10-06 16:00:00", 144 "2013-10-13 16:00:00", 145 "2013-10-20 16:00:00", 146 "2013-10-27 16:00:00", 147 "2014-09-07 16:00:00" 148 ), 149 '2013-09-28' 150 ); 151 152 } 153 154 function testWeekly() { 155 156 $this->parse( 157 'FREQ=WEEKLY;INTERVAL=2;COUNT=10', 158 '2011-10-07 00:00:00', 159 array( 160 '2011-10-07 00:00:00', 161 '2011-10-21 00:00:00', 162 '2011-11-04 00:00:00', 163 '2011-11-18 00:00:00', 164 '2011-12-02 00:00:00', 165 '2011-12-16 00:00:00', 166 '2011-12-30 00:00:00', 167 '2012-01-13 00:00:00', 168 '2012-01-27 00:00:00', 169 '2012-02-10 00:00:00', 170 ) 171 ); 172 173 } 174 175 function testWeeklyByDay() { 176 177 $this->parse( 178 'FREQ=WEEKLY;INTERVAL=1;COUNT=4;BYDAY=MO;WKST=SA', 179 '2014-08-01 00:00:00', 180 array( 181 '2014-08-01 00:00:00', 182 '2014-08-04 00:00:00', 183 '2014-08-11 00:00:00', 184 '2014-08-18 00:00:00', 185 ) 186 ); 187 188 } 189 190 function testWeeklyByDay2() { 191 192 $this->parse( 193 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=SU', 194 '2011-10-07 00:00:00', 195 array( 196 '2011-10-07 00:00:00', 197 '2011-10-18 00:00:00', 198 '2011-10-19 00:00:00', 199 '2011-10-21 00:00:00', 200 '2011-11-01 00:00:00', 201 '2011-11-02 00:00:00', 202 '2011-11-04 00:00:00', 203 '2011-11-15 00:00:00', 204 '2011-11-16 00:00:00', 205 '2011-11-18 00:00:00', 206 '2011-11-29 00:00:00', 207 '2011-11-30 00:00:00', 208 ) 209 ); 210 211 } 212 213 function testWeeklyByDayByHour() { 214 215 $this->parse( 216 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=MO;BYHOUR=8,9,10', 217 '2011-10-07 08:00:00', 218 array( 219 '2011-10-07 08:00:00', 220 '2011-10-07 09:00:00', 221 '2011-10-07 10:00:00', 222 '2011-10-18 08:00:00', 223 '2011-10-18 09:00:00', 224 '2011-10-18 10:00:00', 225 '2011-10-19 08:00:00', 226 '2011-10-19 09:00:00', 227 '2011-10-19 10:00:00', 228 '2011-10-21 08:00:00', 229 '2011-10-21 09:00:00', 230 '2011-10-21 10:00:00', 231 '2011-11-01 08:00:00', 232 '2011-11-01 09:00:00', 233 '2011-11-01 10:00:00', 234 ) 235 ); 236 237 } 238 239 function testWeeklyByDaySpecificHour() { 240 241 $this->parse( 242 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=SU', 243 '2011-10-07 18:00:00', 244 array( 245 '2011-10-07 18:00:00', 246 '2011-10-18 18:00:00', 247 '2011-10-19 18:00:00', 248 '2011-10-21 18:00:00', 249 '2011-11-01 18:00:00', 250 '2011-11-02 18:00:00', 251 '2011-11-04 18:00:00', 252 '2011-11-15 18:00:00', 253 '2011-11-16 18:00:00', 254 '2011-11-18 18:00:00', 255 '2011-11-29 18:00:00', 256 '2011-11-30 18:00:00', 257 ) 258 ); 259 260 } 261 262 function testMonthly() { 263 264 $this->parse( 265 'FREQ=MONTHLY;INTERVAL=3;COUNT=5', 266 '2011-12-05 00:00:00', 267 array( 268 '2011-12-05 00:00:00', 269 '2012-03-05 00:00:00', 270 '2012-06-05 00:00:00', 271 '2012-09-05 00:00:00', 272 '2012-12-05 00:00:00', 273 ) 274 ); 275 276 } 277 278 function testMonlthyEndOfMonth() { 279 280 $this->parse( 281 'FREQ=MONTHLY;INTERVAL=2;COUNT=12', 282 '2011-12-31 00:00:00', 283 array( 284 '2011-12-31 00:00:00', 285 '2012-08-31 00:00:00', 286 '2012-10-31 00:00:00', 287 '2012-12-31 00:00:00', 288 '2013-08-31 00:00:00', 289 '2013-10-31 00:00:00', 290 '2013-12-31 00:00:00', 291 '2014-08-31 00:00:00', 292 '2014-10-31 00:00:00', 293 '2014-12-31 00:00:00', 294 '2015-08-31 00:00:00', 295 '2015-10-31 00:00:00', 296 ) 297 ); 298 299 } 300 301 function testMonthlyByMonthDay() { 302 303 $this->parse( 304 'FREQ=MONTHLY;INTERVAL=5;COUNT=9;BYMONTHDAY=1,31,-7', 305 '2011-01-01 00:00:00', 306 array( 307 '2011-01-01 00:00:00', 308 '2011-01-25 00:00:00', 309 '2011-01-31 00:00:00', 310 '2011-06-01 00:00:00', 311 '2011-06-24 00:00:00', 312 '2011-11-01 00:00:00', 313 '2011-11-24 00:00:00', 314 '2012-04-01 00:00:00', 315 '2012-04-24 00:00:00', 316 ) 317 ); 318 319 } 320 321 function testMonthlyByDay() { 322 323 $this->parse( 324 'FREQ=MONTHLY;INTERVAL=2;COUNT=16;BYDAY=MO,-2TU,+1WE,3TH', 325 '2011-01-03 00:00:00', 326 array( 327 '2011-01-03 00:00:00', 328 '2011-01-05 00:00:00', 329 '2011-01-10 00:00:00', 330 '2011-01-17 00:00:00', 331 '2011-01-18 00:00:00', 332 '2011-01-20 00:00:00', 333 '2011-01-24 00:00:00', 334 '2011-01-31 00:00:00', 335 '2011-03-02 00:00:00', 336 '2011-03-07 00:00:00', 337 '2011-03-14 00:00:00', 338 '2011-03-17 00:00:00', 339 '2011-03-21 00:00:00', 340 '2011-03-22 00:00:00', 341 '2011-03-28 00:00:00', 342 '2011-05-02 00:00:00', 343 ) 344 ); 345 346 } 347 348 function testMonthlyByDayByMonthDay() { 349 350 $this->parse( 351 'FREQ=MONTHLY;COUNT=10;BYDAY=MO;BYMONTHDAY=1', 352 '2011-08-01 00:00:00', 353 array( 354 '2011-08-01 00:00:00', 355 '2012-10-01 00:00:00', 356 '2013-04-01 00:00:00', 357 '2013-07-01 00:00:00', 358 '2014-09-01 00:00:00', 359 '2014-12-01 00:00:00', 360 '2015-06-01 00:00:00', 361 '2016-02-01 00:00:00', 362 '2016-08-01 00:00:00', 363 '2017-05-01 00:00:00', 364 ) 365 ); 366 367 } 368 369 function testMonthlyByDayBySetPos() { 370 371 $this->parse( 372 'FREQ=MONTHLY;COUNT=10;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=1,-1', 373 '2011-01-03 00:00:00', 374 array( 375 '2011-01-03 00:00:00', 376 '2011-01-31 00:00:00', 377 '2011-02-01 00:00:00', 378 '2011-02-28 00:00:00', 379 '2011-03-01 00:00:00', 380 '2011-03-31 00:00:00', 381 '2011-04-01 00:00:00', 382 '2011-04-29 00:00:00', 383 '2011-05-02 00:00:00', 384 '2011-05-31 00:00:00', 385 ) 386 ); 387 388 } 389 390 function testYearly() { 391 392 $this->parse( 393 'FREQ=YEARLY;COUNT=10;INTERVAL=3', 394 '2011-01-01 00:00:00', 395 array( 396 '2011-01-01 00:00:00', 397 '2014-01-01 00:00:00', 398 '2017-01-01 00:00:00', 399 '2020-01-01 00:00:00', 400 '2023-01-01 00:00:00', 401 '2026-01-01 00:00:00', 402 '2029-01-01 00:00:00', 403 '2032-01-01 00:00:00', 404 '2035-01-01 00:00:00', 405 '2038-01-01 00:00:00', 406 ) 407 ); 408 } 409 410 function testYearlyLeapYear() { 411 412 $this->parse( 413 'FREQ=YEARLY;COUNT=3', 414 '2012-02-29 00:00:00', 415 array( 416 '2012-02-29 00:00:00', 417 '2016-02-29 00:00:00', 418 '2020-02-29 00:00:00', 419 ) 420 ); 421 } 422 423 function testYearlyByMonth() { 424 425 $this->parse( 426 'FREQ=YEARLY;COUNT=8;INTERVAL=4;BYMONTH=4,10', 427 '2011-04-07 00:00:00', 428 array( 429 '2011-04-07 00:00:00', 430 '2011-10-07 00:00:00', 431 '2015-04-07 00:00:00', 432 '2015-10-07 00:00:00', 433 '2019-04-07 00:00:00', 434 '2019-10-07 00:00:00', 435 '2023-04-07 00:00:00', 436 '2023-10-07 00:00:00', 437 ) 438 ); 439 440 } 441 442 function testYearlyByMonthByDay() { 443 444 $this->parse( 445 'FREQ=YEARLY;COUNT=8;INTERVAL=5;BYMONTH=4,10;BYDAY=1MO,-1SU', 446 '2011-04-04 00:00:00', 447 array( 448 '2011-04-04 00:00:00', 449 '2011-04-24 00:00:00', 450 '2011-10-03 00:00:00', 451 '2011-10-30 00:00:00', 452 '2016-04-04 00:00:00', 453 '2016-04-24 00:00:00', 454 '2016-10-03 00:00:00', 455 '2016-10-30 00:00:00', 456 ) 457 ); 458 459 } 460 461 function testFastForward() { 462 463 // The idea is that we're fast-forwarding too far in the future, so 464 // there will be no results left. 465 $this->parse( 466 'FREQ=YEARLY;COUNT=8;INTERVAL=5;BYMONTH=4,10;BYDAY=1MO,-1SU', 467 '2011-04-04 00:00:00', 468 array(), 469 '2020-05-05 00:00:00' 470 ); 471 472 } 473 474 /** 475 * The bug that was in the 476 * system before would fail on the 5th tuesday of the month, if the 5th 477 * tuesday did not exist. 478 * 479 * A pretty slow test. Had to be marked as 'medium' for phpunit to not die 480 * after 1 second. Would be good to optimize later. 481 * 482 * @medium 483 */ 484 function testFifthTuesdayProblem() { 485 486 $this->parse( 487 'FREQ=MONTHLY;INTERVAL=1;UNTIL=20071030T035959Z;BYDAY=5TU', 488 '2007-10-04 14:46:42', 489 array( 490 "2007-10-04 14:46:42", 491 ) 492 ); 493 494 } 495 496 /** 497 * This bug came from a Fruux customer. This would result in a never-ending 498 * request. 499 */ 500 function testFastFowardTooFar() { 501 502 $this->parse( 503 'FREQ=WEEKLY;BYDAY=MO;UNTIL=20090704T205959Z;INTERVAL=1', 504 '2009-04-20 18:00:00', 505 array( 506 '2009-04-20 18:00:00', 507 '2009-04-27 18:00:00', 508 '2009-05-04 18:00:00', 509 '2009-05-11 18:00:00', 510 '2009-05-18 18:00:00', 511 '2009-05-25 18:00:00', 512 '2009-06-01 18:00:00', 513 '2009-06-08 18:00:00', 514 '2009-06-15 18:00:00', 515 '2009-06-22 18:00:00', 516 '2009-06-29 18:00:00', 517 ) 518 ); 519 520 } 521 522 /** 523 * This also at one point caused an infinite loop. We're keeping the test. 524 */ 525 function testYearlyByMonthLoop() { 526 527 $this->parse( 528 'FREQ=YEARLY;INTERVAL=1;UNTIL=20120203T225959Z;BYMONTH=2;BYSETPOS=1;BYDAY=SU,MO,TU,WE,TH,FR,SA', 529 '2012-01-01 15:45:00', 530 array( 531 '2012-02-01 15:45:00', 532 ), 533 '2012-01-29 23:00:00' 534 ); 535 536 537 } 538 539 /** 540 * Something, somewhere produced an ics with an interval set to 0. Because 541 * this means we increase the current day (or week, month) by 0, this also 542 * results in an infinite loop. 543 * 544 * @expectedException InvalidArgumentException 545 */ 546 function testZeroInterval() { 547 548 $this->parse( 549 'FREQ=YEARLY;INTERVAL=0', 550 '2012-08-24 14:57:00', 551 array(), 552 '2013-01-01 23:00:00' 553 ); 554 555 } 556 557 /** 558 * @expectedException InvalidArgumentException 559 */ 560 function testInvalidFreq() { 561 562 $this->parse( 563 'FREQ=SMONTHLY;INTERVAL=3;UNTIL=20111025T000000Z', 564 '2011-10-07', 565 array() 566 ); 567 568 } 569 570 /** 571 * @expectedException InvalidArgumentException 572 */ 573 function testByDayBadOffset() { 574 575 $this->parse( 576 'FREQ=WEEKLY;INTERVAL=1;COUNT=4;BYDAY=0MO;WKST=SA', 577 '2014-08-01 00:00:00', 578 array() 579 ); 580 581 } 582 583 function testUntilBeginHAsTimezone() { 584 585 $this->parse( 586 'FREQ=WEEKLY;UNTIL=20131118T183000', 587 '2013-09-23 18:30:00', 588 array( 589 '2013-09-23 18:30:00', 590 '2013-09-30 18:30:00', 591 '2013-10-07 18:30:00', 592 '2013-10-14 18:30:00', 593 '2013-10-21 18:30:00', 594 '2013-10-28 18:30:00', 595 '2013-11-04 18:30:00', 596 '2013-11-11 18:30:00', 597 '2013-11-18 18:30:00', 598 ), 599 null, 600 'America/New_York' 601 ); 602 603 } 604 605 function testUntilBeforeDtStart() { 606 607 $this->parse( 608 'FREQ=DAILY;UNTIL=20140101T000000Z', 609 '2014-08-02 00:15:00', 610 array( 611 '2014-08-02 00:15:00', 612 ) 613 ); 614 615 } 616 617 function testIgnoredStuff() { 618 619 $this->parse( 620 'FREQ=DAILY;BYSECOND=1;BYMINUTE=1;BYYEARDAY=1;BYWEEKNO=1;COUNT=2', 621 '2014-08-02 00:15:00', 622 array( 623 '2014-08-02 00:15:00', 624 '2014-08-03 00:15:00', 625 ) 626 ); 627 628 } 629 630 function testMinusFifthThursday() { 631 632 $this->parse( 633 'FREQ=MONTHLY;BYDAY=-4TH,-5TH;COUNT=4', 634 '2015-01-01 00:15:00', 635 array( 636 '2015-01-01 00:15:00', 637 '2015-01-08 00:15:00', 638 '2015-02-05 00:15:00', 639 '2015-03-05 00:15:00' 640 ) 641 ); 642 643 } 644 645 /** 646 * @expectedException InvalidArgumentException 647 */ 648 function testUnsupportedPart() { 649 650 $this->parse( 651 'FREQ=DAILY;BYWODAN=1', 652 '2014-08-02 00:15:00', 653 array() 654 ); 655 656 } 657 658 function testIteratorFunctions() { 659 660 $parser = new RRuleIterator('FREQ=DAILY', new DateTime('2014-08-02 00:00:13')); 661 $parser->next(); 662 $this->assertEquals( 663 new DateTime('2014-08-03 00:00:13'), 664 $parser->current() 665 ); 666 $this->assertEquals( 667 1, 668 $parser->key() 669 ); 670 671 $parser->rewind(); 672 673 $this->assertEquals( 674 new DateTime('2014-08-02 00:00:13'), 675 $parser->current() 676 ); 677 $this->assertEquals( 678 0, 679 $parser->key() 680 ); 681 682 } 683 684 function parse($rule, $start, $expected, $fastForward = null, $tz = 'UTC') { 685 686 $dt = new DateTime($start, new DateTimeZone($tz)); 687 $parser = new RRuleIterator($rule, $dt); 688 689 if ($fastForward) { 690 $parser->fastForward(new DateTime($fastForward)); 691 } 692 693 $result = array(); 694 while($parser->valid()) { 695 696 $item = $parser->current(); 697 $result[] = $item->format('Y-m-d H:i:s'); 698 699 if ($parser->isInfinite() && count($result) >= count($expected)) { 700 break; 701 } 702 $parser->next(); 703 704 } 705 706 $this->assertEquals( 707 $expected, 708 $result 709 ); 710 711 } 712 713} 714