See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 39 and 401]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 namespace core_calendar; 18 19 defined('MOODLE_INTERNAL') || die(); 20 21 global $CFG; 22 require_once($CFG->dirroot . '/calendar/lib.php'); 23 24 /** 25 * Defines test class to test manage rrule during ical imports. 26 * 27 * @package core_calendar 28 * @category test 29 * @copyright 2014 onwards Ankit Agarwal <ankit.agrr@gmail.com> 30 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 31 */ 32 class rrule_manager_test extends \advanced_testcase { 33 34 /** @var calendar_event a dummy event */ 35 protected $event; 36 37 /** 38 * Set up method. 39 */ 40 protected function setUp(): void { 41 global $DB; 42 $this->resetAfterTest(); 43 44 // Set our timezone based on the timezone in the RFC's samples (US/Eastern). 45 $tz = 'US/Eastern'; 46 $this->setTimezone($tz); 47 $timezone = new \DateTimeZone($tz); 48 // Create our event's DTSTART date based on RFC's samples (most commonly used in RFC is 1997-09-02 09:00:00 EDT). 49 $time = \DateTime::createFromFormat('Ymd\THis', '19970902T090000', $timezone); 50 $timestart = $time->getTimestamp(); 51 52 $user = $this->getDataGenerator()->create_user(); 53 $sub = new \stdClass(); 54 $sub->url = ''; 55 $sub->courseid = 0; 56 $sub->groupid = 0; 57 $sub->userid = $user->id; 58 $sub->pollinterval = 0; 59 $subid = $DB->insert_record('event_subscriptions', $sub, true); 60 61 $event = new \stdClass(); 62 $event->name = 'Event name'; 63 $event->description = ''; 64 $event->timestart = $timestart; 65 $event->timeduration = 3600; 66 $event->uuid = 'uuid'; 67 $event->subscriptionid = $subid; 68 $event->userid = $user->id; 69 $event->groupid = 0; 70 $event->courseid = 0; 71 $event->eventtype = 'user'; 72 $eventobj = \calendar_event::create($event, false); 73 $DB->set_field('event', 'repeatid', $eventobj->id, array('id' => $eventobj->id)); 74 $eventobj->repeatid = $eventobj->id; 75 $this->event = $eventobj; 76 } 77 78 /** 79 * Test parse_rrule() method. 80 */ 81 public function test_parse_rrule() { 82 $rules = [ 83 'FREQ=YEARLY', 84 'COUNT=3', 85 'INTERVAL=4', 86 'BYSECOND=20,40', 87 'BYMINUTE=2,30', 88 'BYHOUR=3,4', 89 'BYDAY=MO,TH', 90 'BYMONTHDAY=20,30', 91 'BYYEARDAY=300,-20', 92 'BYWEEKNO=22,33', 93 'BYMONTH=3,4' 94 ]; 95 $rrule = implode(';', $rules); 96 $mang = new rrule_manager($rrule); 97 $mang->parse_rrule(); 98 99 $bydayrules = [ 100 (object)[ 101 'day' => 'MO', 102 'value' => 0 103 ], 104 (object)[ 105 'day' => 'TH', 106 'value' => 0 107 ], 108 ]; 109 110 $props = [ 111 'freq' => rrule_manager::FREQ_YEARLY, 112 'count' => 3, 113 'interval' => 4, 114 'bysecond' => [20, 40], 115 'byminute' => [2, 30], 116 'byhour' => [3, 4], 117 'byday' => $bydayrules, 118 'bymonthday' => [20, 30], 119 'byyearday' => [300, -20], 120 'byweekno' => [22, 33], 121 'bymonth' => [3, 4], 122 ]; 123 124 $reflectionclass = new \ReflectionClass($mang); 125 foreach ($props as $prop => $expectedval) { 126 $rcprop = $reflectionclass->getProperty($prop); 127 $rcprop->setAccessible(true); 128 $this->assertEquals($expectedval, $rcprop->getValue($mang)); 129 } 130 } 131 132 /** 133 * Test exception is thrown for invalid property. 134 */ 135 public function test_parse_rrule_validation() { 136 $rrule = "RANDOM=PROPERTY;"; 137 $mang = new rrule_manager($rrule); 138 $this->expectException('moodle_exception'); 139 $mang->parse_rrule(); 140 } 141 142 /** 143 * Test exception is thrown for invalid frequency. 144 */ 145 public function test_freq_validation() { 146 $rrule = "FREQ=RANDOMLY;"; 147 $mang = new rrule_manager($rrule); 148 $this->expectException('moodle_exception'); 149 $mang->parse_rrule(); 150 } 151 152 /** 153 * Test parsing of rules with both COUNT and UNTIL parameters. 154 */ 155 public function test_until_count_validation() { 156 $until = $this->event->timestart + DAYSECS * 4; 157 $until = date('Y-m-d', $until); 158 $rrule = "FREQ=DAILY;COUNT=2;UNTIL=$until"; 159 $mang = new rrule_manager($rrule); 160 $this->expectException('moodle_exception'); 161 $mang->parse_rrule(); 162 } 163 164 /** 165 * Test parsing of INTERVAL rule. 166 */ 167 public function test_interval_validation() { 168 $rrule = "INTERVAL=0"; 169 $mang = new rrule_manager($rrule); 170 $this->expectException('moodle_exception'); 171 $mang->parse_rrule(); 172 } 173 174 /** 175 * Test parsing of BYSECOND rule. 176 */ 177 public function test_bysecond_validation() { 178 $rrule = "BYSECOND=30,45,60"; 179 $mang = new rrule_manager($rrule); 180 $this->expectException('moodle_exception'); 181 $mang->parse_rrule(); 182 } 183 184 /** 185 * Test parsing of BYMINUTE rule. 186 */ 187 public function test_byminute_validation() { 188 $rrule = "BYMINUTE=30,45,60"; 189 $mang = new rrule_manager($rrule); 190 $this->expectException('moodle_exception'); 191 $mang->parse_rrule(); 192 } 193 194 /** 195 * Test parsing of BYMINUTE rule. 196 */ 197 public function test_byhour_validation() { 198 $rrule = "BYHOUR=23,45"; 199 $mang = new rrule_manager($rrule); 200 $this->expectException('moodle_exception'); 201 $mang->parse_rrule(); 202 } 203 204 /** 205 * Test parsing of BYDAY rule. 206 */ 207 public function test_byday_validation() { 208 $rrule = "BYDAY=MO,2SE"; 209 $mang = new rrule_manager($rrule); 210 $this->expectException('moodle_exception'); 211 $mang->parse_rrule(); 212 } 213 214 /** 215 * Test parsing of BYDAY rule with prefixes. 216 */ 217 public function test_byday_with_prefix_validation() { 218 // This is acceptable. 219 $rrule = "FREQ=MONTHLY;BYDAY=-1MO,2SA"; 220 $mang = new rrule_manager($rrule); 221 $mang->parse_rrule(); 222 223 // This is also acceptable. 224 $rrule = "FREQ=YEARLY;BYDAY=MO,2SA"; 225 $mang = new rrule_manager($rrule); 226 $mang->parse_rrule(); 227 228 // This is invalid. 229 $rrule = "FREQ=WEEKLY;BYDAY=MO,2SA"; 230 $mang = new rrule_manager($rrule); 231 $this->expectException('moodle_exception'); 232 $mang->parse_rrule(); 233 } 234 235 /** 236 * Test parsing of BYMONTHDAY rule. 237 */ 238 public function test_bymonthday_upper_bound_validation() { 239 $rrule = "BYMONTHDAY=1,32"; 240 $mang = new rrule_manager($rrule); 241 $this->expectException('moodle_exception'); 242 $mang->parse_rrule(); 243 } 244 245 /** 246 * Test parsing of BYMONTHDAY rule. 247 */ 248 public function test_bymonthday_0_validation() { 249 $rrule = "BYMONTHDAY=1,0"; 250 $mang = new rrule_manager($rrule); 251 $this->expectException('moodle_exception'); 252 $mang->parse_rrule(); 253 } 254 255 /** 256 * Test parsing of BYMONTHDAY rule. 257 */ 258 public function test_bymonthday_lower_bound_validation() { 259 $rrule = "BYMONTHDAY=1,-31,-32"; 260 $mang = new rrule_manager($rrule); 261 $this->expectException('moodle_exception'); 262 $mang->parse_rrule(); 263 } 264 265 /** 266 * Test parsing of BYYEARDAY rule. 267 */ 268 public function test_byyearday_upper_bound_validation() { 269 $rrule = "BYYEARDAY=1,366,367"; 270 $mang = new rrule_manager($rrule); 271 $this->expectException('moodle_exception'); 272 $mang->parse_rrule(); 273 } 274 275 /** 276 * Test parsing of BYYEARDAY rule. 277 */ 278 public function test_byyearday_0_validation() { 279 $rrule = "BYYEARDAY=0"; 280 $mang = new rrule_manager($rrule); 281 $this->expectException('moodle_exception'); 282 $mang->parse_rrule(); 283 } 284 285 /** 286 * Test parsing of BYYEARDAY rule. 287 */ 288 public function test_byyearday_lower_bound_validation() { 289 $rrule = "BYYEARDAY=-1,-366,-367"; 290 $mang = new rrule_manager($rrule); 291 $this->expectException('moodle_exception'); 292 $mang->parse_rrule(); 293 } 294 295 /** 296 * Test parsing of BYWEEKNO rule. 297 */ 298 public function test_non_yearly_freq_with_byweekno() { 299 $rrule = "BYWEEKNO=1,53"; 300 $mang = new rrule_manager($rrule); 301 $this->expectException('moodle_exception'); 302 $mang->parse_rrule(); 303 } 304 305 /** 306 * Test parsing of BYWEEKNO rule. 307 */ 308 public function test_byweekno_upper_bound_validation() { 309 $rrule = "FREQ=YEARLY;BYWEEKNO=1,53,54"; 310 $mang = new rrule_manager($rrule); 311 $this->expectException('moodle_exception'); 312 $mang->parse_rrule(); 313 } 314 315 /** 316 * Test parsing of BYWEEKNO rule. 317 */ 318 public function test_byweekno_0_validation() { 319 $rrule = "FREQ=YEARLY;BYWEEKNO=0"; 320 $mang = new rrule_manager($rrule); 321 $this->expectException('moodle_exception'); 322 $mang->parse_rrule(); 323 } 324 325 /** 326 * Test parsing of BYWEEKNO rule. 327 */ 328 public function test_byweekno_lower_bound_validation() { 329 $rrule = "FREQ=YEARLY;BYWEEKNO=-1,-53,-54"; 330 $mang = new rrule_manager($rrule); 331 $this->expectException('moodle_exception'); 332 $mang->parse_rrule(); 333 } 334 335 /** 336 * Test parsing of BYMONTH rule. 337 */ 338 public function test_bymonth_upper_bound_validation() { 339 $rrule = "BYMONTH=1,12,13"; 340 $mang = new rrule_manager($rrule); 341 $this->expectException('moodle_exception'); 342 $mang->parse_rrule(); 343 } 344 345 /** 346 * Test parsing of BYMONTH rule. 347 */ 348 public function test_bymonth_lower_bound_validation() { 349 $rrule = "BYMONTH=0"; 350 $mang = new rrule_manager($rrule); 351 $this->expectException('moodle_exception'); 352 $mang->parse_rrule(); 353 } 354 355 /** 356 * Test parsing of BYSETPOS rule. 357 */ 358 public function test_bysetpos_without_other_byrules() { 359 $rrule = "BYSETPOS=1,366"; 360 $mang = new rrule_manager($rrule); 361 $this->expectException('moodle_exception'); 362 $mang->parse_rrule(); 363 } 364 365 /** 366 * Test parsing of BYSETPOS rule. 367 */ 368 public function test_bysetpos_upper_bound_validation() { 369 $rrule = "BYSETPOS=1,366,367"; 370 $mang = new rrule_manager($rrule); 371 $this->expectException('moodle_exception'); 372 $mang->parse_rrule(); 373 } 374 375 /** 376 * Test parsing of BYSETPOS rule. 377 */ 378 public function test_bysetpos_0_validation() { 379 $rrule = "BYSETPOS=0"; 380 $mang = new rrule_manager($rrule); 381 $this->expectException('moodle_exception'); 382 $mang->parse_rrule(); 383 } 384 385 /** 386 * Test parsing of BYSETPOS rule. 387 */ 388 public function test_bysetpos_lower_bound_validation() { 389 $rrule = "BYSETPOS=-1,-366,-367"; 390 $mang = new rrule_manager($rrule); 391 $this->expectException('moodle_exception'); 392 $mang->parse_rrule(); 393 } 394 395 /** 396 * Test recurrence rules for daily frequency. 397 */ 398 public function test_daily_events() { 399 global $DB; 400 401 $rrule = 'FREQ=DAILY;COUNT=3'; // This should generate 2 child events + 1 parent. 402 $mang = new rrule_manager($rrule); 403 $mang->parse_rrule(); 404 $mang->create_events($this->event); 405 $count = $DB->count_records('event', array('repeatid' => $this->event->id)); 406 $this->assertEquals(3, $count); 407 $result = $DB->record_exists('event', array('repeatid' => $this->event->id, 408 'timestart' => ($this->event->timestart + DAYSECS))); 409 $this->assertTrue($result); 410 $result = $DB->record_exists('event', array('repeatid' => $this->event->id, 411 'timestart' => ($this->event->timestart + 2 * DAYSECS))); 412 $this->assertTrue($result); 413 414 $until = $this->event->timestart + DAYSECS * 2; 415 $until = date('Y-m-d', $until); 416 $rrule = "FREQ=DAILY;UNTIL=$until"; // This should generate 1 child event + 1 parent,since by then until bound would be hit. 417 $mang = new rrule_manager($rrule); 418 $mang->parse_rrule(); 419 $mang->create_events($this->event); 420 $count = $DB->count_records('event', array('repeatid' => $this->event->id)); 421 $this->assertEquals(2, $count); 422 $result = $DB->record_exists('event', array('repeatid' => $this->event->id, 423 'timestart' => ($this->event->timestart + DAYSECS))); 424 $this->assertTrue($result); 425 426 $rrule = 'FREQ=DAILY;COUNT=3;INTERVAL=3'; // This should generate 2 child events + 1 parent, every 3rd day. 427 $mang = new rrule_manager($rrule); 428 $mang->parse_rrule(); 429 $mang->create_events($this->event); 430 $count = $DB->count_records('event', array('repeatid' => $this->event->id)); 431 $this->assertEquals(3, $count); 432 $result = $DB->record_exists('event', array('repeatid' => $this->event->id, 433 'timestart' => ($this->event->timestart + 3 * DAYSECS))); 434 $this->assertTrue($result); 435 $result = $DB->record_exists('event', array('repeatid' => $this->event->id, 436 'timestart' => ($this->event->timestart + 6 * DAYSECS))); 437 $this->assertTrue($result); 438 } 439 440 /** 441 * Every 300 days, forever. 442 */ 443 public function test_every_300_days_forever() { 444 global $DB; 445 446 // Change the start date for forever events to 9am of the current date. 447 $this->change_event_startdate(date('Ymd\T090000')); 448 $startdatetime = new \DateTime(date('Y-m-d H:i:s', $this->event->timestart)); 449 450 $interval = new \DateInterval('P300D'); 451 $untildate = new \DateTime(); 452 $untildate->add(new \DateInterval('P10Y')); 453 $until = $untildate->getTimestamp(); 454 455 // Forever event. This should generate events for time() + 10 year period, every 300 days. 456 $rrule = 'FREQ=DAILY;INTERVAL=300'; 457 $mang = new rrule_manager($rrule); 458 $mang->parse_rrule(); 459 $mang->create_events($this->event); 460 // Get the first 100 samples. This should be enough to verify that we have generated the recurring events correctly. 461 $records = $DB->get_records('event', array('repeatid' => $this->event->id), 'timestart ASC', 0, 100); 462 463 $expecteddate = clone($startdatetime); 464 $first = true; 465 foreach ($records as $record) { 466 $this->assertLessThanOrEqual($until, $record->timestart); 467 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 468 // Go to next iteration. 469 $expecteddate->add($interval); 470 // Check UUID. 471 if ($first) { 472 // The first instance of the event contains the UUID. 473 $this->assertEquals('uuid', $record->uuid); 474 $first = false; 475 } else { 476 // Succeeding instances will not contain the UUID. 477 $this->assertEmpty($record->uuid); 478 } 479 } 480 } 481 482 /** 483 * Test recurrence rules for weekly frequency. 484 */ 485 public function test_weekly_events() { 486 global $DB; 487 488 $rrule = 'FREQ=WEEKLY;COUNT=1'; 489 $mang = new rrule_manager($rrule); 490 $mang->parse_rrule(); 491 $mang->create_events($this->event); 492 $count = $DB->count_records('event', array('repeatid' => $this->event->id)); 493 $this->assertEquals(1, $count); 494 for ($i = 0; $i < $count; $i++) { 495 $result = $DB->record_exists('event', array('repeatid' => $this->event->id, 496 'timestart' => ($this->event->timestart + $i * DAYSECS))); 497 $this->assertTrue($result); 498 } 499 // This much seconds after the start of the day. 500 $offset = $this->event->timestart - mktime(0, 0, 0, date("n", $this->event->timestart), date("j", $this->event->timestart), 501 date("Y", $this->event->timestart)); 502 503 // This should generate 4 weekly Monday events. 504 $until = $this->event->timestart + WEEKSECS * 4; 505 $until = date('Ymd\This\Z', $until); 506 $rrule = "FREQ=WEEKLY;BYDAY=MO;UNTIL=$until"; 507 $mang = new rrule_manager($rrule); 508 $mang->parse_rrule(); 509 $mang->create_events($this->event); 510 $count = $DB->count_records('event', array('repeatid' => $this->event->id)); 511 $this->assertEquals(4, $count); 512 $timestart = $this->event->timestart; 513 for ($i = 0; $i < $count; $i++) { 514 $timestart = strtotime("+$offset seconds next Monday", $timestart); 515 $result = $DB->record_exists('event', array('repeatid' => $this->event->id, 'timestart' => $timestart)); 516 $this->assertTrue($result); 517 } 518 519 $startdatetime = new \DateTime(date('Y-m-d H:i:s', $this->event->timestart)); 520 $startdate = new \DateTime(date('Y-m-d', $this->event->timestart)); 521 522 $offsetinterval = $startdatetime->diff($startdate, true); 523 $interval = new \DateInterval('P3W'); 524 525 // Every 3 weeks on Monday, Wednesday for 2 times. 526 $rrule = 'FREQ=WEEKLY;INTERVAL=3;BYDAY=MO,WE;COUNT=2'; 527 $mang = new rrule_manager($rrule); 528 $mang->parse_rrule(); 529 $mang->create_events($this->event); 530 531 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 532 $this->assertCount(2, $records); 533 534 $expecteddate = clone($startdate); 535 $expecteddate->modify('1997-09-03'); 536 foreach ($records as $record) { 537 $expecteddate->add($offsetinterval); 538 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 539 540 if (date('D', $record->timestart) === 'Mon') { 541 // Go to the fifth day of this month. 542 $expecteddate->modify('next Wednesday'); 543 } else { 544 // Reset to Monday. 545 $expecteddate->modify('last Monday'); 546 // Go to next period. 547 $expecteddate->add($interval); 548 } 549 } 550 } 551 552 /** 553 * Test recurrence rules for weekly frequency for RRULE with BYDAY rule set, recurring forever. 554 */ 555 public function test_weekly_byday_forever() { 556 global $DB; 557 558 // Set the next Monday as the starting date of this event. 559 $startdate = new \DateTime('next Monday'); 560 // Change the start date of the parent event. 561 $startdate = $this->change_event_startdate($startdate->format('Ymd\T090000')); 562 563 // Forever event. This should generate events over time() + 10 year period, every 50 weeks. 564 $rrule = 'FREQ=WEEKLY;BYDAY=MO;INTERVAL=50'; 565 566 $mang = new rrule_manager($rrule); 567 $mang->parse_rrule(); 568 $mang->create_events($this->event); 569 570 $untildate = new \DateTime(); 571 $untildate->add(new \DateInterval('P10Y')); 572 $until = $untildate->getTimestamp(); 573 574 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 575 576 $interval = new \DateInterval('P50W'); 577 578 // First instance of this set of recurring events. 579 $expecteddate = clone($startdate); 580 581 // Iterate over each record and increment the expected date accordingly. 582 foreach ($records as $record) { 583 $eventdateexpected = $expecteddate->format('Y-m-d H:i:s'); 584 $eventdateactual = date('Y-m-d H:i:s', $record->timestart); 585 $this->assertEquals($eventdateexpected, $eventdateactual); 586 587 $expecteddate->add($interval); 588 $this->assertLessThanOrEqual($until, $record->timestart); 589 } 590 } 591 592 /** 593 * Test recurrence rules for monthly frequency for RRULE with COUNT and BYMONTHDAY rules set. 594 */ 595 public function test_monthly_events_with_count_bymonthday() { 596 global $DB; 597 598 $startdatetime = new \DateTime(date('Y-m-d H:i:s', $this->event->timestart)); 599 $interval = new \DateInterval('P1M'); 600 601 $rrule = "FREQ=MONTHLY;COUNT=3;BYMONTHDAY=2"; // This should generate 3 events in total. 602 $mang = new rrule_manager($rrule); 603 $mang->parse_rrule(); 604 $mang->create_events($this->event); 605 $records = $DB->get_records('event', array('repeatid' => $this->event->id), 'timestart ASC'); 606 $this->assertCount(3, $records); 607 608 $expecteddate = clone($startdatetime); 609 foreach ($records as $record) { 610 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 611 // Go to next month. 612 $expecteddate->add($interval); 613 } 614 } 615 616 /** 617 * Test recurrence rules for monthly frequency for RRULE with BYMONTHDAY and UNTIL rules set. 618 */ 619 public function test_monthly_events_with_until_bymonthday() { 620 global $DB; 621 622 // This should generate 10 child event + 1 parent, since by then until bound would be hit. 623 $until = strtotime('+1 day +10 months', $this->event->timestart); 624 $until = date('Ymd\This\Z', $until); 625 $rrule = "FREQ=MONTHLY;BYMONTHDAY=2;UNTIL=$until"; 626 $mang = new rrule_manager($rrule); 627 $mang->parse_rrule(); 628 $mang->create_events($this->event); 629 $count = $DB->count_records('event', ['repeatid' => $this->event->id]); 630 $this->assertEquals(11, $count); 631 for ($i = 0; $i < 11; $i++) { 632 $time = strtotime("+$i month", $this->event->timestart); 633 $result = $DB->record_exists('event', ['repeatid' => $this->event->id, 'timestart' => $time]); 634 $this->assertTrue($result); 635 } 636 } 637 638 /** 639 * Test recurrence rules for monthly frequency for RRULE with BYMONTHDAY and UNTIL rules set. 640 */ 641 public function test_monthly_events_with_until_bymonthday_multi() { 642 global $DB; 643 644 $startdatetime = new \DateTime(date('Y-m-d H:i:s', $this->event->timestart)); 645 $startdate = new \DateTime(date('Y-m-d', $this->event->timestart)); 646 $offsetinterval = $startdatetime->diff($startdate, true); 647 $interval = new \DateInterval('P2M'); 648 $untildate = clone($startdatetime); 649 $untildate->add(new \DateInterval('P10M10D')); 650 $until = $untildate->format('Ymd\This\Z'); 651 652 // This should generate 11 child event + 1 parent, since by then until bound would be hit. 653 $rrule = "FREQ=MONTHLY;INTERVAL=2;BYMONTHDAY=2,5;UNTIL=$until"; 654 655 $mang = new rrule_manager($rrule); 656 $mang->parse_rrule(); 657 $mang->create_events($this->event); 658 659 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 660 $this->assertCount(12, $records); 661 662 $expecteddate = clone($startdate); 663 $expecteddate->add($offsetinterval); 664 foreach ($records as $record) { 665 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 666 667 if (date('j', $record->timestart) == 2) { 668 // Go to the fifth day of this month. 669 $expecteddate->add(new \DateInterval('P3D')); 670 } else { 671 // Reset date to the first day of the month. 672 $expecteddate->modify('first day of this month'); 673 // Go to next month period. 674 $expecteddate->add($interval); 675 // Go to the second day of the next month period. 676 $expecteddate->modify('+1 day'); 677 } 678 } 679 } 680 681 /** 682 * Test recurrence rules for monthly frequency for RRULE with BYMONTHDAY forever. 683 */ 684 public function test_monthly_events_with_bymonthday_forever() { 685 global $DB; 686 687 // Change the start date for forever events to 9am of the 2nd day of the current month and year. 688 $this->change_event_startdate(date('Ym02\T090000')); 689 $startdatetime = new \DateTime(date('Y-m-d H:i:s', $this->event->timestart)); 690 $startdate = new \DateTime(date('Y-m-d', $this->event->timestart)); 691 692 $offsetinterval = $startdatetime->diff($startdate, true); 693 $interval = new \DateInterval('P12M'); 694 695 // Forever event. This should generate events over a 10-year period, on 2nd day of the month, every 12 months. 696 $rrule = "FREQ=MONTHLY;INTERVAL=12;BYMONTHDAY=2"; 697 698 $mang = new rrule_manager($rrule); 699 $untildate = new \DateTime(); 700 $untildate->add(new \DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y')); 701 $until = $untildate->getTimestamp(); 702 703 $mang->parse_rrule(); 704 $mang->create_events($this->event); 705 706 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 707 708 $expecteddate = clone($startdate); 709 $expecteddate->add($offsetinterval); 710 foreach ($records as $record) { 711 $this->assertLessThanOrEqual($until, $record->timestart); 712 713 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 714 715 // Reset date to the first day of the month. 716 $expecteddate->modify('first day of this month'); 717 // Go to next month period. 718 $expecteddate->add($interval); 719 // Go to the second day of the next month period. 720 $expecteddate->modify('+1 day'); 721 } 722 } 723 724 /** 725 * Test recurrence rules for monthly frequency for RRULE with COUNT and BYDAY rules set. 726 */ 727 public function test_monthly_events_with_count_byday() { 728 global $DB; 729 730 $startdatetime = new \DateTime(date('Y-m-d H:i:s', $this->event->timestart)); 731 $startdate = new \DateTime(date('Y-m-d', $this->event->timestart)); 732 733 $offsetinterval = $startdatetime->diff($startdate, true); 734 $interval = new \DateInterval('P1M'); 735 736 $rrule = 'FREQ=MONTHLY;COUNT=3;BYDAY=1MO'; // This should generate 3 events in total, first monday of the month. 737 $mang = new rrule_manager($rrule); 738 $mang->parse_rrule(); 739 $mang->create_events($this->event); 740 741 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 742 743 // First occurrence of this set of recurring events: 06-10-1997. 744 $expecteddate = clone($startdate); 745 $expecteddate->modify('1997-10-06'); 746 $expecteddate->add($offsetinterval); 747 foreach ($records as $record) { 748 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 749 750 // Go to next month period. 751 $expecteddate->add($interval); 752 $expecteddate->modify('first Monday of this month'); 753 $expecteddate->add($offsetinterval); 754 } 755 } 756 757 /** 758 * Test recurrence rules for monthly frequency for RRULE with BYDAY and UNTIL rules set. 759 */ 760 public function test_monthly_events_with_until_byday() { 761 global $DB; 762 763 // This much seconds after the start of the day. 764 $startdatetime = new \DateTime(date('Y-m-d H:i:s', $this->event->timestart)); 765 $startdate = new \DateTime(date('Y-m-d', $this->event->timestart)); 766 $offsetinterval = $startdatetime->diff($startdate, true); 767 768 $untildate = clone($startdatetime); 769 $untildate->add(new \DateInterval('P10M1D')); 770 $until = $untildate->format('Ymd\This\Z'); 771 772 // This rule should generate 9 events in total from first Monday of October 1997 to first Monday of June 1998. 773 $rrule = "FREQ=MONTHLY;BYDAY=1MO;UNTIL=$until"; 774 $mang = new rrule_manager($rrule); 775 $mang->parse_rrule(); 776 $mang->create_events($this->event); 777 778 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 779 $this->assertCount(9, $records); 780 781 $expecteddate = clone($startdate); 782 $expecteddate->modify('first Monday of October 1997'); 783 foreach ($records as $record) { 784 $expecteddate->add($offsetinterval); 785 786 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 787 788 // Go to next month. 789 $expecteddate->modify('first day of next month'); 790 // Go to the first Monday of the next month. 791 $expecteddate->modify('first Monday of this month'); 792 } 793 } 794 795 /** 796 * Test recurrence rules for monthly frequency for RRULE with BYMONTHDAY and UNTIL rules set. 797 */ 798 public function test_monthly_events_with_until_byday_multi() { 799 global $DB; 800 801 $startdatetime = new \DateTime(date('Y-m-d H:i:s', $this->event->timestart)); 802 $startdate = new \DateTime(date('Y-m-d', $this->event->timestart)); 803 804 $offsetinterval = $startdatetime->diff($startdate, true); 805 $interval = new \DateInterval('P2M'); 806 807 $untildate = clone($startdatetime); 808 $untildate->add(new \DateInterval('P10M20D')); 809 $until = $untildate->format('Ymd\This\Z'); 810 811 // This should generate 11 events from 17 Sep 1997 to 15 Jul 1998. 812 $rrule = "FREQ=MONTHLY;INTERVAL=2;BYDAY=1MO,3WE;UNTIL=$until"; 813 $mang = new rrule_manager($rrule); 814 $mang->parse_rrule(); 815 $mang->create_events($this->event); 816 817 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 818 $this->assertCount(11, $records); 819 820 $expecteddate = clone($startdate); 821 $expecteddate->modify('1997-09-17'); 822 foreach ($records as $record) { 823 $expecteddate->add($offsetinterval); 824 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 825 826 if (date('D', $record->timestart) === 'Mon') { 827 // Go to the fifth day of this month. 828 $expecteddate->modify('third Wednesday of this month'); 829 } else { 830 // Go to next month period. 831 $expecteddate->add($interval); 832 $expecteddate->modify('first Monday of this month'); 833 } 834 } 835 } 836 837 /** 838 * Test recurrence rules for monthly frequency for RRULE with BYDAY forever. 839 */ 840 public function test_monthly_events_with_byday_forever() { 841 global $DB; 842 843 // Change the start date for forever events to 9am of the 2nd day of the current month and year. 844 $this->change_event_startdate(date('Ym02\T090000')); 845 846 $startdatetime = new \DateTime(date('Y-m-d H:i:s', $this->event->timestart)); 847 $startdate = new \DateTime(date('Y-m-d', $this->event->timestart)); 848 849 $offsetinterval = $startdatetime->diff($startdate, true); 850 $interval = new \DateInterval('P12M'); 851 852 // Forever event. This should generate events over a 10 year period, on 1st Monday of the month every 12 months. 853 $rrule = "FREQ=MONTHLY;INTERVAL=12;BYDAY=1MO"; 854 855 $mang = new rrule_manager($rrule); 856 $untildate = new \DateTime(); 857 $untildate->add(new \DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y')); 858 $until = $untildate->getTimestamp(); 859 860 $mang->parse_rrule(); 861 $mang->create_events($this->event); 862 863 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 864 $expecteddate = new \DateTime('first Monday of this month'); 865 // Move to the next interval's first Monday if the calculated start date is after this month's first Monday. 866 if ($expecteddate->getTimestamp() < $startdate->getTimestamp()) { 867 $expecteddate->add($interval); 868 $expecteddate->modify('first Monday of this month'); 869 } 870 foreach ($records as $record) { 871 $expecteddate->add($offsetinterval); 872 $this->assertLessThanOrEqual($until, $record->timestart); 873 874 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 875 876 // Go to next month period. 877 $expecteddate->add($interval); 878 // Reset date to the first Monday of the month. 879 $expecteddate->modify('first Monday of this month'); 880 } 881 } 882 883 /** 884 * Test recurrence rules for yearly frequency. 885 */ 886 public function test_yearly_events() { 887 global $DB; 888 889 $startdatetime = new \DateTime(date('Y-m-d H:i:s', $this->event->timestart)); 890 $startdate = new \DateTime(date('Y-m-d', $this->event->timestart)); 891 892 $offsetinterval = $startdatetime->diff($startdate, true); 893 $interval = new \DateInterval('P1Y'); 894 895 $rrule = "FREQ=YEARLY;COUNT=3;BYMONTH=9"; // This should generate 3 events in total. 896 $mang = new rrule_manager($rrule); 897 $mang->parse_rrule(); 898 $mang->create_events($this->event); 899 900 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 901 $this->assertCount(3, $records); 902 903 $expecteddate = clone($startdatetime); 904 foreach ($records as $record) { 905 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 906 907 // Go to next period. 908 $expecteddate->add($interval); 909 } 910 911 // Create a yearly event, until the time limit is hit. 912 $until = strtotime('+20 day +10 years', $this->event->timestart); 913 $until = date('Ymd\THis\Z', $until); 914 $rrule = "FREQ=YEARLY;BYMONTH=9;UNTIL=$until"; 915 $mang = new rrule_manager($rrule); 916 $mang->parse_rrule(); 917 $mang->create_events($this->event); 918 $count = $DB->count_records('event', array('repeatid' => $this->event->id)); 919 $this->assertEquals(11, $count); 920 for ($i = 0, $time = $this->event->timestart; $time < $until; $i++, $yoffset = $i * 2, 921 $time = strtotime("+$yoffset years", $this->event->timestart)) { 922 $result = $DB->record_exists('event', array('repeatid' => $this->event->id, 923 'timestart' => ($time))); 924 $this->assertTrue($result); 925 } 926 927 // This should generate 5 events in total, every second year in the given month of the event. 928 $rrule = "FREQ=YEARLY;BYMONTH=9;INTERVAL=2;COUNT=5"; 929 $mang = new rrule_manager($rrule); 930 $mang->parse_rrule(); 931 $mang->create_events($this->event); 932 $count = $DB->count_records('event', array('repeatid' => $this->event->id)); 933 $this->assertEquals(5, $count); 934 for ($i = 0, $time = $this->event->timestart; $i < 5; $i++, $yoffset = $i * 2, 935 $time = strtotime("+$yoffset years", $this->event->timestart)) { 936 $result = $DB->record_exists('event', array('repeatid' => $this->event->id, 937 'timestart' => ($time))); 938 $this->assertTrue($result); 939 } 940 941 $rrule = "FREQ=YEARLY;COUNT=3;BYMONTH=9;BYDAY=1MO"; // This should generate 3 events in total. 942 $mang = new rrule_manager($rrule); 943 $mang->parse_rrule(); 944 $mang->create_events($this->event); 945 946 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 947 $this->assertCount(3, $records); 948 949 $expecteddate = clone($startdatetime); 950 $expecteddate->modify('first Monday of September 1998'); 951 $expecteddate->add($offsetinterval); 952 foreach ($records as $record) { 953 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 954 955 // Go to next period. 956 $expecteddate->add($interval); 957 $monthyear = $expecteddate->format('F Y'); 958 $expecteddate->modify('first Monday of ' . $monthyear); 959 $expecteddate->add($offsetinterval); 960 } 961 962 // Create a yearly event on the specified month, until the time limit is hit. 963 $untildate = clone($startdatetime); 964 $untildate->add(new \DateInterval('P10Y20D')); 965 $until = $untildate->format('Ymd\THis\Z'); 966 967 $rrule = "FREQ=YEARLY;BYMONTH=9;UNTIL=$until;BYDAY=1MO"; 968 $mang = new rrule_manager($rrule); 969 $mang->parse_rrule(); 970 $mang->create_events($this->event); 971 972 // 10 yearly records every first Monday of September 1998 to first Monday of September 2007. 973 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 974 $this->assertCount(10, $records); 975 976 $expecteddate = clone($startdatetime); 977 $expecteddate->modify('first Monday of September 1998'); 978 $expecteddate->add($offsetinterval); 979 foreach ($records as $record) { 980 $this->assertLessThanOrEqual($untildate->getTimestamp(), $record->timestart); 981 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 982 983 // Go to next period. 984 $expecteddate->add($interval); 985 $monthyear = $expecteddate->format('F Y'); 986 $expecteddate->modify('first Monday of ' . $monthyear); 987 $expecteddate->add($offsetinterval); 988 } 989 990 // This should generate 5 events in total, every second year in the month of September. 991 $rrule = "FREQ=YEARLY;BYMONTH=9;INTERVAL=2;COUNT=5;BYDAY=1MO"; 992 $mang = new rrule_manager($rrule); 993 $mang->parse_rrule(); 994 $mang->create_events($this->event); 995 996 // 5 bi-yearly records every first Monday of September 1998 to first Monday of September 2007. 997 $interval = new \DateInterval('P2Y'); 998 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 999 $this->assertCount(5, $records); 1000 1001 $expecteddate = clone($startdatetime); 1002 $expecteddate->modify('first Monday of September 1999'); 1003 $expecteddate->add($offsetinterval); 1004 foreach ($records as $record) { 1005 $this->assertLessThanOrEqual($untildate->getTimestamp(), $record->timestart); 1006 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 1007 1008 // Go to next period. 1009 $expecteddate->add($interval); 1010 $monthyear = $expecteddate->format('F Y'); 1011 $expecteddate->modify('first Monday of ' . $monthyear); 1012 $expecteddate->add($offsetinterval); 1013 } 1014 } 1015 1016 /** 1017 * Test for rrule with FREQ=YEARLY and INTERVAL=2 with BYMONTH rule set, recurring forever. 1018 */ 1019 public function test_yearly_september_every_two_years_forever() { 1020 global $DB; 1021 1022 // Change the start date for forever events to 9am on the 2nd day of September of the current year. 1023 $this->change_event_startdate(date('Y0902\T090000')); 1024 1025 $rrule = "FREQ=YEARLY;BYMONTH=9;INTERVAL=2"; // Forever event. 1026 $mang = new rrule_manager($rrule); 1027 $untildate = new \DateTime(); 1028 $untildate->add(new \DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y')); 1029 $untiltimestamp = $untildate->getTimestamp(); 1030 $mang->parse_rrule(); 1031 $mang->create_events($this->event); 1032 1033 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 1034 1035 $interval = new \DateInterval('P2Y'); 1036 $expecteddate = new \DateTime(date('Y0902\T090000')); 1037 foreach ($records as $record) { 1038 $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); 1039 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 1040 1041 // Go to the next expected date. 1042 $expecteddate->add($interval); 1043 } 1044 } 1045 1046 /** 1047 * Test for rrule with FREQ=YEARLY with BYMONTH and BYDAY rules set, recurring forever. 1048 */ 1049 public function test_yearly_bymonth_byday_forever() { 1050 global $DB; 1051 1052 // Change the start date for forever events to the first day of September of the current year at 9am. 1053 $this->change_event_startdate(date('Y0901\T090000')); 1054 1055 // Every 2 years on the first Monday of September. 1056 $rrule = "FREQ=YEARLY;BYMONTH=9;INTERVAL=2;BYDAY=1MO"; 1057 $mang = new rrule_manager($rrule); 1058 $mang->parse_rrule(); 1059 $mang->create_events($this->event); 1060 1061 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 1062 1063 $untildate = new \DateTime(); 1064 $untildate->add(new \DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y')); 1065 $untiltimestamp = $untildate->getTimestamp(); 1066 1067 $startdatetime = new \DateTime(date('Y-m-d H:i:s', $this->event->timestart)); 1068 $startdate = new \DateTime(date('Y-m-d', $this->event->timestart)); 1069 1070 $offsetinterval = $startdatetime->diff($startdate, true); 1071 $interval = new \DateInterval('P2Y'); 1072 1073 // First occurrence of this set of events is on the first Monday of September. 1074 $expecteddate = clone($startdatetime); 1075 $expecteddate->modify('first Monday of September'); 1076 $expecteddate->add($offsetinterval); 1077 foreach ($records as $record) { 1078 $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); 1079 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 1080 1081 // Go to next period. 1082 $expecteddate->add($interval); 1083 $monthyear = $expecteddate->format('F Y'); 1084 $expecteddate->modify('first Monday of ' . $monthyear); 1085 $expecteddate->add($offsetinterval); 1086 } 1087 } 1088 1089 /** 1090 * Test for rrule with FREQ=YEARLY recurring forever. 1091 */ 1092 public function test_yearly_forever() { 1093 global $DB; 1094 1095 // Change the start date for forever events to 9am of the current date. 1096 $this->change_event_startdate(date('Ymd\T090000')); 1097 1098 $startdatetime = new \DateTime(date('Y-m-d H:i:s', $this->event->timestart)); 1099 1100 $interval = new \DateInterval('P2Y'); 1101 1102 $rrule = 'FREQ=YEARLY;INTERVAL=2'; // Forever event. 1103 $mang = new rrule_manager($rrule); 1104 $mang->parse_rrule(); 1105 $mang->create_events($this->event); 1106 1107 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 1108 1109 $untildate = new \DateTime(); 1110 $untildate->add(new \DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y')); 1111 $untiltimestamp = $untildate->getTimestamp(); 1112 1113 $expecteddate = clone($startdatetime); 1114 foreach ($records as $record) { 1115 $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); 1116 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 1117 1118 // Go to next period. 1119 $expecteddate->add($interval); 1120 } 1121 } 1122 1123 /******************************************************************************************************************************/ 1124 /* Tests based on the examples from the RFC. */ 1125 /******************************************************************************************************************************/ 1126 1127 /** 1128 * Daily for 10 occurrences: 1129 * 1130 * DTSTART;TZID=US-Eastern:19970902T090000 1131 * RRULE:FREQ=DAILY;COUNT=10 1132 * ==> (1997 9:00 AM EDT)September 2-11 1133 */ 1134 public function test_daily_count() { 1135 global $DB; 1136 1137 $startdatetime = new \DateTime(date('Y-m-d H:i:s', $this->event->timestart)); 1138 $interval = new \DateInterval('P1D'); 1139 1140 $rrule = 'FREQ=DAILY;COUNT=10'; 1141 $mang = new rrule_manager($rrule); 1142 $mang->parse_rrule(); 1143 $mang->create_events($this->event); 1144 1145 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 1146 $this->assertCount(10, $records); 1147 1148 $expecteddate = new \DateTime(date('Y-m-d H:i:s', $startdatetime->getTimestamp())); 1149 foreach ($records as $record) { 1150 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 1151 1152 // Go to next period. 1153 $expecteddate->add($interval); 1154 } 1155 } 1156 1157 /** 1158 * Daily until December 24, 1997: 1159 * 1160 * DTSTART;TZID=US-Eastern:19970902T090000 1161 * RRULE:FREQ=DAILY;UNTIL=19971224T000000Z 1162 * ==> (1997 9:00 AM EDT)September 2-30;October 1-25 1163 * (1997 9:00 AM EST)October 26-31;November 1-30;December 1-23 1164 */ 1165 public function test_daily_until() { 1166 global $DB; 1167 1168 $startdatetime = new \DateTime(date('Y-m-d H:i:s', $this->event->timestart)); 1169 $interval = new \DateInterval('P1D'); 1170 1171 $untildate = new \DateTime('19971224T000000Z'); 1172 $untiltimestamp = $untildate->getTimestamp(); 1173 1174 $rrule = 'FREQ=DAILY;UNTIL=19971224T000000Z'; 1175 $mang = new rrule_manager($rrule); 1176 $mang->parse_rrule(); 1177 $mang->create_events($this->event); 1178 1179 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 1180 // 113 daily events from 02-09-1997 to 23-12-1997. 1181 $this->assertCount(113, $records); 1182 1183 $expecteddate = new \DateTime(date('Y-m-d H:i:s', $startdatetime->getTimestamp())); 1184 foreach ($records as $record) { 1185 $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); 1186 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 1187 // Go to next period. 1188 $expecteddate->add($interval); 1189 } 1190 } 1191 1192 /** 1193 * Every other day - forever: 1194 * 1195 * DTSTART;TZID=US-Eastern:[Current date]T090000 1196 * RRULE:FREQ=DAILY;INTERVAL=2 1197 * 1198 * Sample results (e.g. in the year 1997): 1199 * (1997 9:00 AM EDT)September2,4,6,8...24,26,28,30;October 2,4,6...20,22,24 1200 * (1997 9:00 AM EST)October 26,28,30;November 1,3,5,7...25,27,29;Dec 1,3,... 1201 */ 1202 public function test_every_other_day_forever() { 1203 global $DB; 1204 1205 // Change the start date for forever events to 9am of the current date in US/Eastern time. 1206 $this->change_event_startdate(date('Ymd\T090000'), 'US/Eastern'); 1207 1208 $startdatetime = new \DateTime(date('Y-m-d H:i:s', $this->event->timestart)); 1209 $interval = new \DateInterval('P2D'); 1210 1211 $rrule = 'FREQ=DAILY;INTERVAL=2'; 1212 $mang = new rrule_manager($rrule); 1213 $mang->parse_rrule(); 1214 $mang->create_events($this->event); 1215 1216 // Get the first 100 samples. This should be enough to verify that we have generated the recurring events correctly. 1217 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart', 0, 100); 1218 1219 $untildate = new \DateTime(); 1220 $untildate->add(new \DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y')); 1221 $untiltimestamp = $untildate->getTimestamp(); 1222 1223 $expecteddate = new \DateTime(date('Y-m-d H:i:s', $startdatetime->getTimestamp())); 1224 foreach ($records as $record) { 1225 $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); 1226 1227 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 1228 // Go to next period. 1229 $expecteddate->add($interval); 1230 } 1231 } 1232 1233 /** 1234 * Every 10 days, 5 occurrences: 1235 * 1236 * DTSTART;TZID=US-Eastern:19970902T090000 1237 * RRULE:FREQ=DAILY;INTERVAL=10;COUNT=5 1238 * ==> (1997 9:00 AM EDT)September 2,12,22;October 2,12 1239 */ 1240 public function test_every_10_days_5_count() { 1241 global $DB; 1242 1243 $startdatetime = new \DateTime(date('Y-m-d H:i:s', $this->event->timestart)); 1244 $interval = new \DateInterval('P10D'); 1245 1246 $rrule = 'FREQ=DAILY;INTERVAL=10;COUNT=5'; 1247 $mang = new rrule_manager($rrule); 1248 $mang->parse_rrule(); 1249 $mang->create_events($this->event); 1250 1251 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 1252 $this->assertCount(5, $records); 1253 1254 $expecteddate = new \DateTime(date('Y-m-d H:i:s', $startdatetime->getTimestamp())); 1255 foreach ($records as $record) { 1256 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 1257 // Go to next period. 1258 $expecteddate->add($interval); 1259 } 1260 } 1261 1262 /** 1263 * Everyday in January, for 3 years: 1264 * 1265 * DTSTART;TZID=US-Eastern:19980101T090000 1266 * RRULE:FREQ=YEARLY;UNTIL=20000131T090000Z;BYMONTH=1;BYDAY=SU,MO,TU,WE,TH,FR,SA 1267 * ==> (1998 9:00 AM EDT)January 1-31 1268 * (1999 9:00 AM EDT)January 1-31 1269 * (2000 9:00 AM EDT)January 1-31 1270 */ 1271 public function test_everyday_in_jan_for_3_years_yearly() { 1272 global $DB; 1273 1274 // Change our event's date to 01-01-1998, based on the example from the RFC. 1275 $this->change_event_startdate('19980101T090000', 'US/Eastern'); 1276 1277 $rrule = 'FREQ=YEARLY;UNTIL=20000131T090000Z;BYMONTH=1;BYDAY=SU,MO,TU,WE,TH,FR,SA'; 1278 $mang = new rrule_manager($rrule); 1279 $mang->parse_rrule(); 1280 $mang->create_events($this->event); 1281 1282 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 1283 // 92 events from 01-01-1998 to 03-01-2000. 1284 $this->assertCount(92, $records); 1285 1286 $untildate = new \DateTime('20000131T090000Z'); 1287 $untiltimestamp = $untildate->getTimestamp(); 1288 foreach ($records as $record) { 1289 $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); 1290 1291 // Assert that the event's date is in January. 1292 $this->assertEquals('January', date('F', $record->timestart)); 1293 } 1294 } 1295 1296 /** 1297 * Everyday in January, for 3 years: 1298 * 1299 * DTSTART;TZID=US-Eastern:19980101T090000 1300 * RRULE:FREQ=DAILY;UNTIL=20000131T090000Z;BYMONTH=1 1301 * ==> (1998 9:00 AM EDT)January 1-31 1302 * (1999 9:00 AM EDT)January 1-31 1303 * (2000 9:00 AM EDT)January 1-31 1304 */ 1305 public function test_everyday_in_jan_for_3_years_daily() { 1306 global $DB; 1307 1308 // Change our event's date to 01-01-1998, based on the example from the RFC. 1309 $this->change_event_startdate('19980101T090000', 'US/Eastern'); 1310 1311 $rrule = 'FREQ=DAILY;UNTIL=20000131T090000Z;BYMONTH=1'; 1312 $mang = new rrule_manager($rrule); 1313 $mang->parse_rrule(); 1314 $mang->create_events($this->event); 1315 1316 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 1317 // 92 events from 01-01-1998 to 03-01-2000. 1318 $this->assertCount(92, $records); 1319 1320 $untildate = new \DateTime('20000131T090000Z'); 1321 $untiltimestamp = $untildate->getTimestamp(); 1322 foreach ($records as $record) { 1323 $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); 1324 1325 // Assert that the event's date is in January. 1326 $this->assertEquals('January', date('F', $record->timestart)); 1327 } 1328 } 1329 1330 /** 1331 * Weekly for 10 occurrences 1332 * 1333 * DTSTART;TZID=US-Eastern:19970902T090000 1334 * RRULE:FREQ=WEEKLY;COUNT=10 1335 * ==> (1997 9:00 AM EDT)September 2,9,16,23,30;October 7,14,21 1336 * (1997 9:00 AM EST)October 28;November 4 1337 */ 1338 public function test_weekly_10_count() { 1339 global $DB; 1340 1341 $interval = new \DateInterval('P1W'); 1342 1343 $rrule = 'FREQ=WEEKLY;COUNT=10'; 1344 $mang = new rrule_manager($rrule); 1345 $mang->parse_rrule(); 1346 $mang->create_events($this->event); 1347 1348 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 1349 $this->assertCount(10, $records); 1350 1351 $expecteddate = new \DateTime(date('Y-m-d H:i:s', $this->event->timestart)); 1352 foreach ($records as $record) { 1353 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 1354 // Go to next period. 1355 $expecteddate->add($interval); 1356 } 1357 } 1358 1359 /** 1360 * Weekly until December 24, 1997. 1361 * 1362 * DTSTART;TZID=US-Eastern:19970902T090000 1363 * RRULE:FREQ=WEEKLY;UNTIL=19971224T000000Z 1364 * ==> (1997 9:00 AM EDT)September 2,9,16,23,30;October 7,14,21,28 1365 * (1997 9:00 AM EST)November 4,11,18,25;December 2,9,16,23 1366 */ 1367 public function test_weekly_until_24_dec_1997() { 1368 global $DB; 1369 1370 $interval = new \DateInterval('P1W'); 1371 1372 $rrule = 'FREQ=WEEKLY;UNTIL=19971224T000000Z'; 1373 $mang = new rrule_manager($rrule); 1374 $mang->parse_rrule(); 1375 $mang->create_events($this->event); 1376 1377 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 1378 // 17 iterations from 02-09-1997 13:00 UTC to 23-12-1997 13:00 UTC. 1379 $this->assertCount(17, $records); 1380 1381 $untildate = new \DateTime('19971224T000000Z'); 1382 $untiltimestamp = $untildate->getTimestamp(); 1383 $expecteddate = new \DateTime(date('Y-m-d H:i:s', $this->event->timestart)); 1384 foreach ($records as $record) { 1385 $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); 1386 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 1387 // Go to next period. 1388 $expecteddate->add($interval); 1389 } 1390 } 1391 1392 /** 1393 * Every other week - forever: 1394 * 1395 * DTSTART;TZID=US-Eastern:[Current date]T090000 1396 * RRULE:FREQ=WEEKLY;INTERVAL=2;WKST=SU 1397 * 1398 * Sample results (e.g. in the year 1997): 1399 * (1997 9:00 AM EDT)September 2,16,30;October 14 1400 * (1997 9:00 AM EST)October 28;November 11,25;December 9,23 1401 * (1998 9:00 AM EST)January 6,20;February 1402 * ... 1403 */ 1404 public function test_every_other_week_forever() { 1405 global $DB; 1406 1407 // Change the start date for forever events to 9am of the current date in US/Eastern time. 1408 $this->change_event_startdate(date('Ymd\T090000'), 'US/Eastern'); 1409 1410 $interval = new \DateInterval('P2W'); 1411 1412 $rrule = 'FREQ=WEEKLY;INTERVAL=2;WKST=SU'; 1413 $mang = new rrule_manager($rrule); 1414 $mang->parse_rrule(); 1415 $mang->create_events($this->event); 1416 1417 // Get the first 100 samples. This should be enough to verify that we have generated the recurring events correctly. 1418 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart', 0, 100); 1419 1420 $untildate = new \DateTime(); 1421 $untildate->add(new \DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y')); 1422 $untiltimestamp = $untildate->getTimestamp(); 1423 1424 $expecteddate = new \DateTime(date('Y-m-d H:i:s', $this->event->timestart)); 1425 foreach ($records as $record) { 1426 $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); 1427 1428 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 1429 // Go to next period. 1430 $expecteddate->add($interval); 1431 } 1432 } 1433 1434 /** 1435 * Weekly on Tuesday and Thursday for 5 weeks: 1436 * 1437 * DTSTART;TZID=US-Eastern:19970902T090000 1438 * RRULE:FREQ=WEEKLY;UNTIL=19971007T000000Z;WKST=SU;BYDAY=TU,TH 1439 * ==> (1997 9:00 AM EDT)September 2,4,9,11,16,18,23,25,30;October 2 1440 */ 1441 public function test_weekly_on_tue_thu_for_5_weeks_by_until() { 1442 global $DB; 1443 1444 $rrule = 'FREQ=WEEKLY;UNTIL=19971007T000000Z;WKST=SU;BYDAY=TU,TH'; 1445 $mang = new rrule_manager($rrule); 1446 $mang->parse_rrule(); 1447 $mang->create_events($this->event); 1448 1449 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 1450 // 17 iterations from 02-09-1997 13:00 UTC to 23-12-1997 13:00 UTC. 1451 $this->assertCount(10, $records); 1452 1453 $untildate = new \DateTime('19971007T000000Z'); 1454 $untiltimestamp = $untildate->getTimestamp(); 1455 $expecteddate = new \DateTime(date('Y-m-d H:i:s', $this->event->timestart)); 1456 $startdate = new \DateTime($expecteddate->format('Y-m-d')); 1457 $offset = $expecteddate->diff($startdate, true); 1458 foreach ($records as $record) { 1459 $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); 1460 1461 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 1462 // Go to next period. 1463 if ($expecteddate->format('l') === rrule_manager::DAY_TUESDAY) { 1464 $expecteddate->modify('next Thursday'); 1465 } else { 1466 $expecteddate->modify('next Tuesday'); 1467 } 1468 $expecteddate->add($offset); 1469 } 1470 } 1471 1472 /** 1473 * Weekly on Tuesday and Thursday for 5 weeks: 1474 * 1475 * DTSTART;TZID=US-Eastern:19970902T090000 1476 * RRULE:FREQ=WEEKLY;COUNT=10;WKST=SU;BYDAY=TU,TH 1477 * ==> (1997 9:00 AM EDT)September 2,4,9,11,16,18,23,25,30;October 2 1478 */ 1479 public function test_weekly_on_tue_thu_for_5_weeks_by_count() { 1480 global $DB; 1481 1482 $rrule = 'FREQ=WEEKLY;COUNT=10;WKST=SU;BYDAY=TU,TH'; 1483 $mang = new rrule_manager($rrule); 1484 $mang->parse_rrule(); 1485 $mang->create_events($this->event); 1486 1487 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 1488 // 17 iterations from 02-09-1997 13:00 UTC to 23-12-1997 13:00 UTC. 1489 $this->assertCount(10, $records); 1490 1491 $expecteddate = new \DateTime(date('Y-m-d H:i:s', $this->event->timestart)); 1492 $startdate = new \DateTime($expecteddate->format('Y-m-d')); 1493 $offset = $expecteddate->diff($startdate, true); 1494 foreach ($records as $record) { 1495 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 1496 // Go to next period. 1497 if ($expecteddate->format('l') === rrule_manager::DAY_TUESDAY) { 1498 $expecteddate->modify('next Thursday'); 1499 } else { 1500 $expecteddate->modify('next Tuesday'); 1501 } 1502 $expecteddate->add($offset); 1503 } 1504 } 1505 1506 /** 1507 * Every other week on Monday, Wednesday and Friday until December 24, 1997, but starting on Tuesday, September 2, 1997: 1508 * 1509 * DTSTART;TZID=US-Eastern:19970902T090000 1510 * RRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=19971224T000000Z;WKST=SU;BYDAY=MO,WE,FR 1511 * ==> (1997 9:00 AM EDT)September 3,5,15,17,19,29;October 1,3,13,15,17 1512 * (1997 9:00 AM EST)October 27,29,31;November 10,12,14,24,26,28;December 8,10,12,22 1513 */ 1514 public function test_every_other_week_until_24_dec_1997_byday() { 1515 global $DB; 1516 1517 $rrule = 'FREQ=WEEKLY;INTERVAL=2;UNTIL=19971224T000000Z;WKST=SU;BYDAY=MO,WE,FR'; 1518 $mang = new rrule_manager($rrule); 1519 $mang->parse_rrule(); 1520 $mang->create_events($this->event); 1521 1522 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 1523 // 24 iterations every M-W-F from 03-09-1997 13:00 UTC to 22-12-1997 13:00 UTC. 1524 $this->assertCount(24, $records); 1525 1526 $untildate = new \DateTime('19971224T000000Z'); 1527 $untiltimestamp = $untildate->getTimestamp(); 1528 1529 $startdatetime = new \DateTime(date('Y-m-d H:i:s', $this->event->timestart)); 1530 $startdate = new \DateTime(date('Y-m-d', $this->event->timestart)); 1531 1532 $offsetinterval = $startdatetime->diff($startdate, true); 1533 1534 // First occurrence of this set of events is on 3 September 1999. 1535 $expecteddate = clone($startdatetime); 1536 $expecteddate->modify('next Wednesday'); 1537 $expecteddate->add($offsetinterval); 1538 foreach ($records as $record) { 1539 $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); 1540 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 1541 1542 // Go to next period. 1543 switch ($expecteddate->format('l')) { 1544 case rrule_manager::DAY_MONDAY: 1545 $expecteddate->modify('next Wednesday'); 1546 break; 1547 case rrule_manager::DAY_WEDNESDAY: 1548 $expecteddate->modify('next Friday'); 1549 break; 1550 default: 1551 $expecteddate->modify('next Monday'); 1552 // Increment expected date by 1 week if the next day is Monday. 1553 $expecteddate->add(new \DateInterval('P1W')); 1554 break; 1555 } 1556 $expecteddate->add($offsetinterval); 1557 } 1558 } 1559 1560 /** 1561 * Every other week on Tuesday and Thursday, for 8 occurrences: 1562 * 1563 * DTSTART;TZID=US-Eastern:19970902T090000 1564 * RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=8;WKST=SU;BYDAY=TU,TH 1565 * ==> (1997 9:00 AM EDT)September 2,4,16,18,30;October 2,14,16 1566 */ 1567 public function test_every_other_week_byday_8_count() { 1568 global $DB; 1569 1570 $rrule = 'FREQ=WEEKLY;INTERVAL=2;COUNT=8;WKST=SU;BYDAY=TU,TH'; 1571 $mang = new rrule_manager($rrule); 1572 $mang->parse_rrule(); 1573 $mang->create_events($this->event); 1574 1575 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 1576 // Should correspond to COUNT rule. 1577 $this->assertCount(8, $records); 1578 1579 $startdatetime = new \DateTime(date('Y-m-d H:i:s', $this->event->timestart)); 1580 $startdate = new \DateTime(date('Y-m-d', $this->event->timestart)); 1581 1582 $offsetinterval = $startdatetime->diff($startdate, true); 1583 1584 // First occurrence of this set of events is on 2 September 1999. 1585 $expecteddate = clone($startdatetime); 1586 foreach ($records as $record) { 1587 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 1588 1589 // Go to next period. 1590 switch ($expecteddate->format('l')) { 1591 case rrule_manager::DAY_TUESDAY: 1592 $expecteddate->modify('next Thursday'); 1593 break; 1594 default: 1595 $expecteddate->modify('next Tuesday'); 1596 // Increment expected date by 1 week if the next day is Tuesday. 1597 $expecteddate->add(new \DateInterval('P1W')); 1598 break; 1599 } 1600 $expecteddate->add($offsetinterval); 1601 } 1602 } 1603 1604 /** 1605 * Monthly on the 1st Friday for ten occurrences: 1606 * 1607 * DTSTART;TZID=US-Eastern:19970905T090000 1608 * RRULE:FREQ=MONTHLY;COUNT=10;BYDAY=1FR 1609 * ==> (1997 9:00 AM EDT)September 5;October 3 1610 * (1997 9:00 AM EST)November 7;Dec 5 1611 * (1998 9:00 AM EST)January 2;February 6;March 6;April 3 1612 * (1998 9:00 AM EDT)May 1;June 5 1613 */ 1614 public function test_monthly_every_first_friday_10_count() { 1615 global $DB; 1616 1617 // Change our event's date to 05-09-1997, based on the example from the RFC. 1618 $startdatetime = $this->change_event_startdate('19970905T090000', 'US/Eastern'); 1619 $startdate = new \DateTime(date('Y-m-d', $this->event->timestart)); 1620 $offsetinterval = $startdatetime->diff($startdate, true); 1621 1622 $rrule = 'FREQ=MONTHLY;COUNT=10;BYDAY=1FR'; 1623 $mang = new rrule_manager($rrule); 1624 $mang->parse_rrule(); 1625 $mang->create_events($this->event); 1626 1627 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 1628 // Should correspond to COUNT rule. 1629 $this->assertCount(10, $records); 1630 1631 foreach ($records as $record) { 1632 // Get the first Friday of the record's month. 1633 $recordmonthyear = date('F Y', $record->timestart); 1634 $expecteddate = new \DateTime('first Friday of ' . $recordmonthyear); 1635 // Add the time of the event. 1636 $expecteddate->add($offsetinterval); 1637 1638 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 1639 } 1640 } 1641 1642 /** 1643 * Monthly on the 1st Friday until December 24, 1997: 1644 * 1645 * DTSTART;TZID=US-Eastern:19970905T090000 1646 * RRULE:FREQ=MONTHLY;UNTIL=19971224T000000Z;BYDAY=1FR 1647 * ==> (1997 9:00 AM EDT)September 5;October 3 1648 * (1997 9:00 AM EST)November 7;December 5 1649 */ 1650 public function test_monthly_every_first_friday_until() { 1651 global $DB; 1652 1653 // Change our event's date to 05-09-1997, based on the example from the RFC. 1654 $startdatetime = $this->change_event_startdate('19970905T090000', 'US/Eastern'); 1655 $startdate = new \DateTime(date('Y-m-d', $this->event->timestart)); 1656 $offsetinterval = $startdatetime->diff($startdate, true); 1657 1658 $rrule = 'FREQ=MONTHLY;UNTIL=19971224T000000Z;BYDAY=1FR'; 1659 $mang = new rrule_manager($rrule); 1660 $mang->parse_rrule(); 1661 $mang->create_events($this->event); 1662 1663 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 1664 // Should have 4 events, every first friday of September 1997 to December 1997. 1665 $this->assertCount(4, $records); 1666 1667 foreach ($records as $record) { 1668 // Get the first Friday of the record's month. 1669 $recordmonthyear = date('F Y', $record->timestart); 1670 $expecteddate = new \DateTime('first Friday of ' . $recordmonthyear); 1671 // Add the time of the event. 1672 $expecteddate->add($offsetinterval); 1673 1674 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 1675 } 1676 } 1677 1678 /** 1679 * Every other month on the 1st and last Sunday of the month for 10 occurrences: 1680 * 1681 * DTSTART;TZID=US-Eastern:19970907T090000 1682 * RRULE:FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=1SU,-1SU 1683 * ==> (1997 9:00 AM EDT)September 7,28 1684 * (1997 9:00 AM EST)November 2,30 1685 * (1998 9:00 AM EST)January 4,25;March 1,29 1686 * (1998 9:00 AM EDT)May 3,31 1687 */ 1688 public function test_every_other_month_1st_and_last_sunday_10_count() { 1689 global $DB; 1690 1691 // Change our event's date to 05-09-1997, based on the example from the RFC. 1692 $startdatetime = $this->change_event_startdate('19970907T090000', 'US/Eastern'); 1693 $startdate = new \DateTime(date('Y-m-d', $this->event->timestart)); 1694 $offsetinterval = $startdatetime->diff($startdate, true); 1695 1696 $rrule = 'FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=1SU,-1SU'; 1697 $mang = new rrule_manager($rrule); 1698 $mang->parse_rrule(); 1699 $mang->create_events($this->event); 1700 1701 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 1702 // Should have 10 records based on COUNT rule. 1703 $this->assertCount(10, $records); 1704 1705 // First occurrence is 07-09-1997 which is the first Sunday. 1706 $ordinal = 'first'; 1707 foreach ($records as $record) { 1708 // Get date of the month's first/last Sunday. 1709 $recordmonthyear = date('F Y', $record->timestart); 1710 $expecteddate = new \DateTime($ordinal . ' Sunday of ' . $recordmonthyear); 1711 $expecteddate->add($offsetinterval); 1712 1713 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 1714 if ($ordinal === 'first') { 1715 $ordinal = 'last'; 1716 } else { 1717 $ordinal = 'first'; 1718 } 1719 } 1720 } 1721 1722 /** 1723 * Monthly on the second to last Monday of the month for 6 months: 1724 * 1725 * DTSTART;TZID=US-Eastern:19970922T090000 1726 * RRULE:FREQ=MONTHLY;COUNT=6;BYDAY=-2MO 1727 * ==> (1997 9:00 AM EDT)September 22;October 20 1728 * (1997 9:00 AM EST)November 17;December 22 1729 * (1998 9:00 AM EST)January 19;February 16 1730 */ 1731 public function test_monthly_last_monday_for_6_months() { 1732 global $DB; 1733 1734 // Change our event's date to 05-09-1997, based on the example from the RFC. 1735 $startdatetime = $this->change_event_startdate('19970922T090000', 'US/Eastern'); 1736 $startdate = new \DateTime($startdatetime->format('Y-m-d')); 1737 $offsetinterval = $startdatetime->diff($startdate, true); 1738 1739 $rrule = 'FREQ=MONTHLY;COUNT=6;BYDAY=-2MO'; 1740 $mang = new rrule_manager($rrule); 1741 $mang->parse_rrule(); 1742 $mang->create_events($this->event); 1743 1744 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 1745 // Should have 6 records based on COUNT rule. 1746 $this->assertCount(6, $records); 1747 1748 foreach ($records as $record) { 1749 // Get date of the month's last Monday. 1750 $recordmonthyear = date('F Y', $record->timestart); 1751 $expecteddate = new \DateTime('last Monday of ' . $recordmonthyear); 1752 // Modify to get the second to the last Monday. 1753 $expecteddate->modify('last Monday'); 1754 // Add offset. 1755 $expecteddate->add($offsetinterval); 1756 1757 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 1758 } 1759 } 1760 1761 /** 1762 * Monthly on the third to the last day of the month, forever: 1763 * 1764 * DTSTART;TZID=US-Eastern:[Current year]0928T090000 1765 * RRULE:FREQ=MONTHLY;BYMONTHDAY=-3 1766 * 1767 * Sample results (e.g. in the year 1997): 1768 * (1997 9:00 AM EDT)September 28 1769 * (1997 9:00 AM EST)October 29;November 28;December 29 1770 * (1998 9:00 AM EST)January 29;February 26 1771 * ... 1772 */ 1773 public function test_third_to_the_last_day_of_the_month_forever() { 1774 global $DB; 1775 1776 // Change our event's date to 28 September of the current year, based on the example from the RFC. 1777 $this->change_event_startdate(date('Y0928\T090000'), 'US/Eastern'); 1778 1779 $rrule = 'FREQ=MONTHLY;BYMONTHDAY=-3'; 1780 $mang = new rrule_manager($rrule); 1781 $mang->parse_rrule(); 1782 $mang->create_events($this->event); 1783 1784 // Get the first 100 samples. This should be enough to verify that we have generated the recurring events correctly. 1785 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart', 0, 100); 1786 1787 $untildate = new \DateTime(); 1788 $untildate->add(new \DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y')); 1789 $untiltimestamp = $untildate->getTimestamp(); 1790 1791 $subinterval = new \DateInterval('P2D'); 1792 foreach ($records as $record) { 1793 $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); 1794 1795 // Get date of the third to the last day of the month. 1796 $recordmonthyear = date('F Y', $record->timestart); 1797 $expecteddate = new \DateTime('last day of ' . $recordmonthyear); 1798 // Set time to 9am. 1799 $expecteddate->setTime(9, 0); 1800 // Modify to get the third to the last day of the month. 1801 $expecteddate->sub($subinterval); 1802 1803 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 1804 } 1805 } 1806 1807 /** 1808 * Monthly on the 2nd and 15th of the month for 10 occurrences: 1809 * 1810 * DTSTART;TZID=US-Eastern:19970902T090000 1811 * RRULE:FREQ=MONTHLY;COUNT=10;BYMONTHDAY=2,15 1812 * ==> (1997 9:00 AM EDT)September 2,15;October 2,15 1813 * (1997 9:00 AM EST)November 2,15;December 2,15 1814 * (1998 9:00 AM EST)January 2,15 1815 */ 1816 public function test_every_2nd_and_15th_of_the_month_10_count() { 1817 global $DB; 1818 1819 $startdatetime = new \DateTime(date('Y-m-d H:i:s', $this->event->timestart)); 1820 $startdate = new \DateTime($startdatetime->format('Y-m-d')); 1821 $offsetinterval = $startdatetime->diff($startdate, true); 1822 1823 $rrule = 'FREQ=MONTHLY;COUNT=10;BYMONTHDAY=2,15'; 1824 $mang = new rrule_manager($rrule); 1825 $mang->parse_rrule(); 1826 $mang->create_events($this->event); 1827 1828 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 1829 // Should have 10 records based on COUNT rule. 1830 $this->assertCount(10, $records); 1831 1832 $day = '02'; 1833 foreach ($records as $record) { 1834 // Get the first Friday of the record's month. 1835 $recordmonthyear = date('Y-m', $record->timestart); 1836 1837 // Get date of the month's last Monday. 1838 $expecteddate = new \DateTime("$recordmonthyear-$day"); 1839 // Add offset. 1840 $expecteddate->add($offsetinterval); 1841 if ($day === '02') { 1842 $day = '15'; 1843 } else { 1844 $day = '02'; 1845 } 1846 1847 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 1848 } 1849 } 1850 1851 /** 1852 * Monthly on the first and last day of the month for 10 occurrences: 1853 * 1854 * DTSTART;TZID=US-Eastern:19970930T090000 1855 * RRULE:FREQ=MONTHLY;COUNT=10;BYMONTHDAY=1,-1 1856 * ==> (1997 9:00 AM EDT)September 30;October 1 1857 * (1997 9:00 AM EST)October 31;November 1,30;December 1,31 1858 * (1998 9:00 AM EST)January 1,31;February 1 1859 */ 1860 public function test_every_first_and_last_day_of_the_month_10_count() { 1861 global $DB; 1862 1863 $startdatetime = $this->change_event_startdate('19970930T090000', 'US/Eastern'); 1864 $startdate = new \DateTime($startdatetime->format('Y-m-d')); 1865 $offsetinterval = $startdatetime->diff($startdate, true); 1866 1867 $rrule = 'FREQ=MONTHLY;COUNT=10;BYMONTHDAY=1,-1'; 1868 $mang = new rrule_manager($rrule); 1869 $mang->parse_rrule(); 1870 $mang->create_events($this->event); 1871 1872 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 1873 // Should have 10 records based on COUNT rule. 1874 $this->assertCount(10, $records); 1875 1876 // First occurrence is 30-Sep-1997. 1877 $day = 'last'; 1878 foreach ($records as $record) { 1879 // Get the first Friday of the record's month. 1880 $recordmonthyear = date('F Y', $record->timestart); 1881 1882 // Get date of the month's last Monday. 1883 $expecteddate = new \DateTime("$day day of $recordmonthyear"); 1884 // Add offset. 1885 $expecteddate->add($offsetinterval); 1886 1887 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 1888 1889 if ($day === 'first') { 1890 $day = 'last'; 1891 } else { 1892 $day = 'first'; 1893 } 1894 } 1895 } 1896 1897 /** 1898 * Every 18 months on the 10th thru 15th of the month for 10 occurrences: 1899 * 1900 * DTSTART;TZID=US-Eastern:19970910T090000 1901 * RRULE:FREQ=MONTHLY;INTERVAL=18;COUNT=10;BYMONTHDAY=10,11,12,13,14,15 1902 * ==> (1997 9:00 AM EDT)September 10,11,12,13,14,15 1903 * (1999 9:00 AM EST)March 10,11,12,13 1904 */ 1905 public function test_every_18_months_days_10_to_15_10_count() { 1906 global $DB; 1907 1908 $startdatetime = $this->change_event_startdate('19970910T090000', 'US/Eastern'); 1909 1910 $rrule = 'FREQ=MONTHLY;INTERVAL=18;COUNT=10;BYMONTHDAY=10,11,12,13,14,15'; 1911 $mang = new rrule_manager($rrule); 1912 $mang->parse_rrule(); 1913 $mang->create_events($this->event); 1914 1915 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 1916 // Should have 10 records based on COUNT rule. 1917 $this->assertCount(10, $records); 1918 1919 // First occurrence is 10-Sep-1997. 1920 $expecteddate = clone($startdatetime); 1921 $expecteddate->setTimezone(new \DateTimeZone(get_user_timezone())); 1922 foreach ($records as $record) { 1923 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 1924 1925 // Get next expected date. 1926 if ($expecteddate->format('d') == 15) { 1927 // If 15th, increment by 18 months. 1928 $expecteddate->add(new \DateInterval('P18M')); 1929 // Then go back to the 10th. 1930 $expecteddate->sub(new \DateInterval('P5D')); 1931 } else { 1932 // Otherwise, increment by 1 day. 1933 $expecteddate->add(new \DateInterval('P1D')); 1934 } 1935 } 1936 } 1937 1938 /** 1939 * Every Tuesday, every other month: 1940 * 1941 * DTSTART;TZID=US-Eastern:[Next Tuesday]T090000 1942 * RRULE:FREQ=MONTHLY;INTERVAL=2;BYDAY=TU 1943 * 1944 * Sample results (e.g. in the year 1997): 1945 * (1997 9:00 AM EDT)September 2,9,16,23,30 1946 * (1997 9:00 AM EST)November 4,11,18,25 1947 * (1998 9:00 AM EST)January 6,13,20,27;March 3,10,17,24,31 1948 * ... 1949 */ 1950 public function test_every_tuesday_every_other_month_forever() { 1951 global $DB; 1952 1953 // Change the start date for forever events to 9am of the Tuesday on or before of the current date in US/Eastern time. 1954 $nexttuesday = new \DateTime('next Tuesday'); 1955 $this->change_event_startdate($nexttuesday->format('Ymd\T090000'), 'US/Eastern'); 1956 1957 $rrule = 'FREQ=MONTHLY;INTERVAL=2;BYDAY=TU'; 1958 $mang = new rrule_manager($rrule); 1959 $mang->parse_rrule(); 1960 $mang->create_events($this->event); 1961 1962 // Get the first 100 samples. This should be enough to verify that we have generated the recurring events correctly. 1963 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart', 0, 100); 1964 1965 $untildate = new \DateTime(); 1966 $untildate->add(new \DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y')); 1967 $untiltimestamp = $untildate->getTimestamp(); 1968 1969 $expecteddate = new \DateTime(date('Y-m-d H:i:s', $this->event->timestart)); 1970 $nextmonth = new \DateTime($expecteddate->format('Y-m-d')); 1971 $offset = $expecteddate->diff($nextmonth, true); 1972 $nextmonth->modify('first day of next month'); 1973 foreach ($records as $record) { 1974 $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); 1975 1976 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 1977 1978 // Get next expected date. 1979 $expecteddate->modify('next Tuesday'); 1980 if ($expecteddate->getTimestamp() >= $nextmonth->getTimestamp()) { 1981 // Go to the end of the month. 1982 $expecteddate->modify('last day of this month'); 1983 // Find the next Tuesday. 1984 $expecteddate->modify('next Tuesday'); 1985 1986 // Increment next month by 2 months. 1987 $nextmonth->add(new \DateInterval('P2M')); 1988 } 1989 $expecteddate->add($offset); 1990 } 1991 } 1992 1993 /** 1994 * Yearly in June and July for 10 occurrences: 1995 * 1996 * DTSTART;TZID=US-Eastern:19970610T090000 1997 * RRULE:FREQ=YEARLY;COUNT=10;BYMONTH=6,7 1998 * ==> (1997 9:00 AM EDT)June 10;July 10 1999 * (1998 9:00 AM EDT)June 10;July 10 2000 * (1999 9:00 AM EDT)June 10;July 10 2001 * (2000 9:00 AM EDT)June 10;July 10 2002 * (2001 9:00 AM EDT)June 10;July 10 2003 * Note: Since none of the BYDAY, BYMONTHDAY or BYYEARDAY components are specified, the day is gotten from DTSTART. 2004 */ 2005 public function test_yearly_in_june_july_10_count() { 2006 global $DB; 2007 2008 $startdatetime = $this->change_event_startdate('19970610T090000', 'US/Eastern'); 2009 2010 $rrule = 'FREQ=YEARLY;COUNT=10;BYMONTH=6,7'; 2011 $mang = new rrule_manager($rrule); 2012 $mang->parse_rrule(); 2013 $mang->create_events($this->event); 2014 2015 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 2016 // Should have 10 records based on COUNT rule. 2017 $this->assertCount(10, $records); 2018 2019 $expecteddate = $startdatetime; 2020 $expecteddate->setTimezone(new \DateTimeZone(get_user_timezone())); 2021 $monthinterval = new \DateInterval('P1M'); 2022 $yearinterval = new \DateInterval('P1Y'); 2023 foreach ($records as $record) { 2024 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 2025 2026 // Get next expected date. 2027 if ($expecteddate->format('m') == 6) { 2028 // Go to the month of July. 2029 $expecteddate->add($monthinterval); 2030 } else { 2031 // Go to the month of June next year. 2032 $expecteddate->sub($monthinterval); 2033 $expecteddate->add($yearinterval); 2034 } 2035 } 2036 } 2037 2038 /** 2039 * Every other year on January, February, and March for 10 occurrences: 2040 * 2041 * DTSTART;TZID=US-Eastern:19970310T090000 2042 * RRULE:FREQ=YEARLY;INTERVAL=2;COUNT=10;BYMONTH=1,2,3 2043 * ==> (1997 9:00 AM EST)March 10 2044 * (1999 9:00 AM EST)January 10;February 10;March 10 2045 * (2001 9:00 AM EST)January 10;February 10;March 10 2046 * (2003 9:00 AM EST)January 10;February 10;March 10 2047 */ 2048 public function test_every_other_year_in_june_july_10_count() { 2049 global $DB; 2050 2051 $startdatetime = $this->change_event_startdate('19970310T090000', 'US/Eastern'); 2052 2053 $rrule = 'FREQ=YEARLY;INTERVAL=2;COUNT=10;BYMONTH=1,2,3'; 2054 $mang = new rrule_manager($rrule); 2055 $mang->parse_rrule(); 2056 $mang->create_events($this->event); 2057 2058 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 2059 // Should have 10 records based on COUNT rule. 2060 $this->assertCount(10, $records); 2061 2062 $expecteddate = $startdatetime; 2063 $expecteddate->setTimezone(new \DateTimeZone(get_user_timezone())); 2064 $monthinterval = new \DateInterval('P1M'); 2065 $yearinterval = new \DateInterval('P2Y'); 2066 foreach ($records as $record) { 2067 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 2068 2069 // Get next expected date. 2070 if ($expecteddate->format('m') != 3) { 2071 // Go to the next month. 2072 $expecteddate->add($monthinterval); 2073 } else { 2074 // Go to the month of January next year. 2075 $expecteddate->sub($monthinterval); 2076 $expecteddate->sub($monthinterval); 2077 $expecteddate->add($yearinterval); 2078 } 2079 } 2080 } 2081 2082 /** 2083 * Every 3rd year on the 1st, 100th and 200th day for 10 occurrences: 2084 * 2085 * DTSTART;TZID=US-Eastern:19970101T090000 2086 * RRULE:FREQ=YEARLY;INTERVAL=3;COUNT=10;BYYEARDAY=1,100,200 2087 * ==> (1997 9:00 AM EST)January 1 2088 * (1997 9:00 AM EDT)April 10;July 19 2089 * (2000 9:00 AM EST)January 1 2090 * (2000 9:00 AM EDT)April 9;July 18 2091 * (2003 9:00 AM EST)January 1 2092 * (2003 9:00 AM EDT)April 10;July 19 2093 * (2006 9:00 AM EST)January 1 2094 */ 2095 public function test_every_3_years_1st_100th_200th_days_10_count() { 2096 global $DB; 2097 2098 $startdatetime = $this->change_event_startdate('19970101T090000', 'US/Eastern'); 2099 2100 $rrule = 'FREQ=YEARLY;INTERVAL=3;COUNT=10;BYYEARDAY=1,100,200'; 2101 $mang = new rrule_manager($rrule); 2102 $mang->parse_rrule(); 2103 $mang->create_events($this->event); 2104 2105 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 2106 // Should have 10 records based on COUNT rule. 2107 $this->assertCount(10, $records); 2108 2109 $expecteddate = $startdatetime; 2110 $expecteddate->setTimezone(new \DateTimeZone(get_user_timezone())); 2111 $hundredthdayinterval = new \DateInterval('P99D'); 2112 $twohundredthdayinterval = new \DateInterval('P100D'); 2113 $yearinterval = new \DateInterval('P3Y'); 2114 2115 foreach ($records as $record) { 2116 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 2117 2118 // Get next expected date. 2119 if ($expecteddate->format('z') == 0) { // January 1. 2120 $expecteddate->add($hundredthdayinterval); 2121 } else if ($expecteddate->format('z') == 99) { // 100th day of the year. 2122 $expecteddate->add($twohundredthdayinterval); 2123 } else { // 200th day of the year. 2124 $expecteddate->add($yearinterval); 2125 $expecteddate->modify('January 1'); 2126 } 2127 } 2128 } 2129 2130 /** 2131 * Every 20th Monday of the year, forever: 2132 * 2133 * DTSTART;TZID=US-Eastern:[20th Monday of the current year]T090000 2134 * RRULE:FREQ=YEARLY;BYDAY=20MO 2135 * 2136 * Sample results (e.g. in the year 1997): 2137 * (1997 9:00 AM EDT)May 19 2138 * (1998 9:00 AM EDT)May 18 2139 * (1999 9:00 AM EDT)May 17 2140 * ... 2141 */ 2142 public function test_yearly_every_20th_monday_forever() { 2143 global $DB; 2144 2145 // Change our event's date to the 20th Monday of the current year. 2146 $twentiethmonday = new \DateTime(date('Y-01-01')); 2147 $twentiethmonday->modify('+20 Monday'); 2148 $startdatetime = $this->change_event_startdate($twentiethmonday->format('Ymd\T000000'), 'US/Eastern'); 2149 2150 $interval = new \DateInterval('P1Y'); 2151 2152 $rrule = 'FREQ=YEARLY;BYDAY=20MO'; 2153 $mang = new rrule_manager($rrule); 2154 $mang->parse_rrule(); 2155 $mang->create_events($this->event); 2156 2157 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 2158 2159 $untildate = new \DateTime(); 2160 $untildate->add(new \DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y')); 2161 $untiltimestamp = $untildate->getTimestamp(); 2162 2163 $expecteddate = $startdatetime; 2164 $expecteddate->setTimezone(new \DateTimeZone(get_user_timezone())); 2165 foreach ($records as $record) { 2166 $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); 2167 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 2168 2169 // Go to next period. 2170 $expecteddate->modify('January 1'); 2171 $expecteddate->add($interval); 2172 $expecteddate->modify("+20 Monday"); 2173 } 2174 } 2175 2176 /** 2177 * Monday of week number 20 (where the default start of the week is Monday), forever: 2178 * 2179 * DTSTART;TZID=US-Eastern:[1st day of the 20th week this year]T090000 2180 * RRULE:FREQ=YEARLY;BYWEEKNO=20;BYDAY=MO 2181 * 2182 * Sample results (e.g. in the year 1997): 2183 * (1997 9:00 AM EDT)May 12 2184 * (1998 9:00 AM EDT)May 11 2185 * (1999 9:00 AM EDT)May 17 2186 * ... 2187 */ 2188 public function test_yearly_byweekno_forever() { 2189 global $DB; 2190 2191 // Change our event's date to the start of the 20th week of the current year. 2192 $twentiethweek = new \DateTime(date('Y-01-01')); 2193 $twentiethweek->setISODate($twentiethweek->format('Y'), 20); 2194 $startdatetime = $this->change_event_startdate($twentiethweek->format('Ymd\T090000'), 'US/Eastern'); 2195 2196 $startdate = clone($startdatetime); 2197 $startdate->modify($startdate->format('Y-m-d')); 2198 2199 $offset = $startdatetime->diff($startdate, true); 2200 2201 $interval = new \DateInterval('P1Y'); 2202 2203 $rrule = 'FREQ=YEARLY;BYWEEKNO=20;BYDAY=MO'; 2204 $mang = new rrule_manager($rrule); 2205 $mang->parse_rrule(); 2206 $mang->create_events($this->event); 2207 2208 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 2209 2210 $untildate = new \DateTime(); 2211 $untildate->add(new \DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y')); 2212 $untiltimestamp = $untildate->getTimestamp(); 2213 2214 $expecteddate = new \DateTime(date('Y-m-d H:i:s', $startdatetime->getTimestamp())); 2215 foreach ($records as $record) { 2216 $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); 2217 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 2218 2219 // Go to next period. 2220 $expecteddate->add($interval); 2221 $expecteddate->setISODate($expecteddate->format('Y'), 20); 2222 $expecteddate->add($offset); 2223 } 2224 } 2225 2226 /** 2227 * Every Thursday in March, forever: 2228 * 2229 * DTSTART;TZID=US-Eastern:[First thursday of March of the current year]T090000 2230 * RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=TH 2231 * 2232 * Sample results (e.g. in the year 1997): 2233 * (1997 9:00 AM EST)March 13,20,27 2234 * (1998 9:00 AM EST)March 5,12,19,26 2235 * (1999 9:00 AM EST)March 4,11,18,25 2236 * ... 2237 */ 2238 public function test_every_thursday_in_march_forever() { 2239 global $DB; 2240 2241 // Change our event's date to the first Thursday of March of the current year at 9am US/Eastern time. 2242 $firstthursdayofmarch = new \DateTime('first Thursday of March'); 2243 $startdatetime = $this->change_event_startdate($firstthursdayofmarch->format('Ymd\T090000'), 'US/Eastern'); 2244 2245 $interval = new \DateInterval('P1Y'); 2246 2247 $rrule = 'FREQ=YEARLY;BYMONTH=3;BYDAY=TH'; 2248 $mang = new rrule_manager($rrule); 2249 $mang->parse_rrule(); 2250 $mang->create_events($this->event); 2251 2252 // Get the first 100 samples. This should be enough to verify that we have generated the recurring events correctly. 2253 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart', 0, 100); 2254 2255 $untildate = new \DateTime(); 2256 $untildate->add(new \DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y')); 2257 $untiltimestamp = $untildate->getTimestamp(); 2258 2259 $expecteddate = $startdatetime; 2260 $startdate = new \DateTime($startdatetime->format('Y-m-d')); 2261 $offsetinterval = $startdatetime->diff($startdate, true); 2262 $expecteddate->setTimezone(new \DateTimeZone(get_user_timezone())); 2263 $april1st = new \DateTime('April 1'); 2264 foreach ($records as $record) { 2265 $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); 2266 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 2267 2268 // Go to next period. 2269 $expecteddate->modify('next Thursday'); 2270 if ($expecteddate->getTimestamp() >= $april1st->getTimestamp()) { 2271 // Reset to 1st of March. 2272 $expecteddate->modify('first day of March'); 2273 // Go to next year. 2274 $expecteddate->add($interval); 2275 if ($expecteddate->format('l') !== rrule_manager::DAY_THURSDAY) { 2276 $expecteddate->modify('next Thursday'); 2277 } 2278 // Increment to next year's April 1st. 2279 $april1st->add($interval); 2280 } 2281 $expecteddate->add($offsetinterval); 2282 } 2283 } 2284 2285 /** 2286 * Every Thursday, but only during June, July, and August, forever: 2287 * 2288 * DTSTART;TZID=US-Eastern:[First Thursday of June of the current year]T090000 2289 * RRULE:FREQ=YEARLY;BYDAY=TH;BYMONTH=6,7,8 2290 * 2291 * Sample results (e.g. in the year 1997): 2292 * (1997 9:00 AM EDT)June 5,12,19,26;July 3,10,17,24,31;August 7,14,21,28 2293 * (1998 9:00 AM EDT)June 4,11,18,25;July 2,9,16,23,30;August 6,13,20,27 2294 * (1999 9:00 AM EDT)June 3,10,17,24;July 1,8,15,22,29;August 5,12,19,26 2295 * ... 2296 */ 2297 public function test_every_thursday_june_july_august_forever() { 2298 global $DB; 2299 2300 // Change our event's date to the first Thursday of June in the current year at 9am US/Eastern time. 2301 $firstthursdayofjune = new \DateTime('first Thursday of June'); 2302 $startdatetime = $this->change_event_startdate($firstthursdayofjune->format('Ymd\T090000'), 'US/Eastern'); 2303 2304 $startdate = new \DateTime($startdatetime->format('Y-m-d')); 2305 2306 $offset = $startdatetime->diff($startdate, true); 2307 2308 $interval = new \DateInterval('P1Y'); 2309 2310 $rrule = 'FREQ=YEARLY;BYDAY=TH;BYMONTH=6,7,8'; 2311 $mang = new rrule_manager($rrule); 2312 $mang->parse_rrule(); 2313 $mang->create_events($this->event); 2314 2315 // Get the first 100 samples. This should be enough to verify that we have generated the recurring events correctly. 2316 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart', 0, 100); 2317 2318 $untildate = new \DateTime(); 2319 $untildate->add(new \DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y')); 2320 $untiltimestamp = $untildate->getTimestamp(); 2321 2322 $expecteddate = new \DateTime(date('Y-m-d H:i:s', $startdatetime->getTimestamp())); 2323 $september1st = new \DateTime('September 1'); 2324 foreach ($records as $record) { 2325 $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); 2326 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 2327 2328 // Go to next period. 2329 $expecteddate->modify('next Thursday'); 2330 if ($expecteddate->getTimestamp() >= $september1st->getTimestamp()) { 2331 $expecteddate->add($interval); 2332 $expecteddate->modify('June 1'); 2333 if ($expecteddate->format('l') !== rrule_manager::DAY_THURSDAY) { 2334 $expecteddate->modify('next Thursday'); 2335 } 2336 $september1st->add($interval); 2337 } 2338 $expecteddate->add($offset); 2339 } 2340 } 2341 2342 /** 2343 * Every Friday the 13th, forever: 2344 * 2345 * DTSTART;TZID=US-Eastern:[Current date]T090000 2346 * RRULE:FREQ=MONTHLY;BYDAY=FR;BYMONTHDAY=13 2347 * 2348 * Sample results (e.g. in the year 1997): 2349 * (1998 9:00 AM EST)February 13;March 13;November 13 2350 * (1999 9:00 AM EDT)August 13 2351 * (2000 9:00 AM EDT)October 13 2352 * ... 2353 */ 2354 public function test_friday_the_thirteenth_forever() { 2355 global $DB; 2356 2357 // Change our event's date to the first Thursday of June in the current year at 9am US/Eastern time. 2358 $this->change_event_startdate(date('Ymd\T090000'), 'US/Eastern'); 2359 2360 $rrule = 'FREQ=MONTHLY;BYDAY=FR;BYMONTHDAY=13'; 2361 $mang = new rrule_manager($rrule); 2362 $mang->parse_rrule(); 2363 $mang->create_events($this->event); 2364 2365 // Get the first 100 samples. This should be enough to verify that we have generated the recurring events correctly. 2366 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart', 0, 100); 2367 2368 $untildate = new \DateTime(); 2369 $untildate->add(new \DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y')); 2370 $untiltimestamp = $untildate->getTimestamp(); 2371 2372 foreach ($records as $record) { 2373 $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); 2374 // Assert that the day of the month and the day correspond to Friday the 13th. 2375 $this->assertEquals('Friday 13', date('l d', $record->timestart)); 2376 } 2377 } 2378 2379 /** 2380 * The first Saturday that follows the first Sunday of the month, forever: 2381 * 2382 * DTSTART;TZID=US-Eastern:[The Saturday after the month's first Sunday]T090000 2383 * RRULE:FREQ=MONTHLY;BYDAY=SA;BYMONTHDAY=7,8,9,10,11,12,13 2384 * 2385 * Sample results (e.g. from 13 September 1997): 2386 * (1997 9:00 AM EDT)September 13;October 11 2387 * (1997 9:00 AM EST)November 8;December 13 2388 * (1998 9:00 AM EST)January 10;February 7;March 7 2389 * (1998 9:00 AM EDT)April 11;May 9;June 13... 2390 */ 2391 public function test_first_saturday_following_first_sunday_forever() { 2392 global $DB; 2393 2394 // Change our event's date to the next Saturday after the first Sunday of the the current month at 9am US/Eastern time. 2395 $firstsaturdayafterfirstsunday = new \DateTime('first Sunday of this month'); 2396 $firstsaturdayafterfirstsunday->modify('next Saturday'); 2397 $startdatetime = $this->change_event_startdate($firstsaturdayafterfirstsunday->format('Ymd\T090000'), 'US/Eastern'); 2398 $startdate = new \DateTime($startdatetime->format('Y-m-d')); 2399 $offset = $startdatetime->diff($startdate, true); 2400 2401 $rrule = 'FREQ=MONTHLY;BYDAY=SA;BYMONTHDAY=7,8,9,10,11,12,13'; 2402 $mang = new rrule_manager($rrule); 2403 $mang->parse_rrule(); 2404 $mang->create_events($this->event); 2405 2406 // Get the first 100 samples. This should be enough to verify that we have generated the recurring events correctly. 2407 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart', 0, 100); 2408 2409 $untildate = new \DateTime(); 2410 $untildate->add(new \DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y')); 2411 $untiltimestamp = $untildate->getTimestamp(); 2412 $bymonthdays = [7, 8, 9, 10, 11, 12, 13]; 2413 foreach ($records as $record) { 2414 $recordmonthyear = date('F Y', $record->timestart); 2415 $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); 2416 2417 // Get first Saturday after the first Sunday of the month. 2418 $expecteddate = new \DateTime('first Sunday of ' . $recordmonthyear); 2419 $expecteddate->modify('next Saturday'); 2420 $expecteddate->add($offset); 2421 2422 // Assert the record's date corresponds to the first Saturday of the month. 2423 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 2424 2425 // Assert that the record is either the 7th, 8th, 9th, ... 13th day of the month. 2426 $this->assertContainsEquals(date('j', $record->timestart), $bymonthdays); 2427 } 2428 } 2429 2430 /** 2431 * Every four years, the first Tuesday after a Monday in November, forever (U.S. Presidential Election day): 2432 * 2433 * DTSTART;TZID=US-Eastern:[Most recent election date]T090000 2434 * RRULE:FREQ=YEARLY;INTERVAL=4;BYMONTH=11;BYDAY=TU;BYMONTHDAY=2,3,4,5,6,7,8 2435 * 2436 * Sample results (e.g. from 05 November 1996): 2437 * (1996 9:00 AM EST)November 5 2438 * (2000 9:00 AM EST)November 7 2439 * (2004 9:00 AM EST)November 2 2440 * ... 2441 */ 2442 public function test_every_us_presidential_election_forever() { 2443 global $DB; 2444 2445 // Calculate the most recent election date, starting from 1996 (e.g. today's 2017 so the most recent election was in 2016). 2446 $currentyear = (int) date('Y'); 2447 $electionyear = 1996; 2448 while ($electionyear + 4 < $currentyear) { 2449 $electionyear += 4; 2450 } 2451 $electiondate = new \DateTime('first Monday of November ' . $electionyear); 2452 $electiondate->modify('+1 Tuesday'); 2453 2454 // Use the most recent election date as the starting date of our recurring events. 2455 $startdatetime = $this->change_event_startdate($electiondate->format('Ymd\T090000'), 'US/Eastern'); 2456 $startdate = new \DateTime($startdatetime->format('Y-m-d')); 2457 $offset = $startdatetime->diff($startdate, true); 2458 2459 $rrule = 'FREQ=YEARLY;INTERVAL=4;BYMONTH=11;BYDAY=TU;BYMONTHDAY=2,3,4,5,6,7,8'; 2460 $mang = new rrule_manager($rrule); 2461 $mang->parse_rrule(); 2462 $mang->create_events($this->event); 2463 2464 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 2465 2466 $untildate = new \DateTime(); 2467 $untildate->add(new \DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y')); 2468 $untiltimestamp = $untildate->getTimestamp(); 2469 $bymonthdays = [2, 3, 4, 5, 6, 7, 8]; 2470 foreach ($records as $record) { 2471 $recordmonthyear = date('F Y', $record->timestart); 2472 $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); 2473 2474 // Get first Saturday after the first Sunday of the month. 2475 $expecteddate = new \DateTime('first Monday of ' . $recordmonthyear); 2476 $expecteddate->modify('next Tuesday'); 2477 $expecteddate->add($offset); 2478 2479 // Assert the record's date corresponds to the first Saturday of the month. 2480 $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart)); 2481 2482 // Assert that the record is either the 2nd, 3rd, 4th ... 8th day of the month. 2483 $this->assertContainsEquals(date('j', $record->timestart), $bymonthdays); 2484 } 2485 } 2486 2487 /** 2488 * The 3rd instance into the month of one of Tuesday, Wednesday or Thursday, for the next 3 months: 2489 * 2490 * DTSTART;TZID=US-Eastern:19970904T090000 2491 * RRULE:FREQ=MONTHLY;COUNT=3;BYDAY=TU,WE,TH;BYSETPOS=3 2492 * ==> (1997 9:00 AM EDT)September 4;October 7 2493 * (1997 9:00 AM EST)November 6 2494 */ 2495 public function test_monthly_bysetpos_3_count() { 2496 global $DB; 2497 2498 $this->change_event_startdate('19970904T090000', 'US/Eastern'); 2499 2500 $rrule = 'FREQ=MONTHLY;COUNT=3;BYDAY=TU,WE,TH;BYSETPOS=3'; 2501 $mang = new rrule_manager($rrule); 2502 $mang->parse_rrule(); 2503 $mang->create_events($this->event); 2504 2505 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 2506 $this->assertCount(3, $records); 2507 2508 $expecteddates = [ 2509 (new \DateTime('1997-09-04 09:00:00 EDT'))->getTimestamp(), 2510 (new \DateTime('1997-10-07 09:00:00 EDT'))->getTimestamp(), 2511 (new \DateTime('1997-11-06 09:00:00 EST'))->getTimestamp() 2512 ]; 2513 foreach ($records as $record) { 2514 $this->assertContainsEquals($record->timestart, $expecteddates, 2515 date('Y-m-d H:i:s', $record->timestart) . ' is not found.'); 2516 } 2517 } 2518 2519 /** 2520 * The 2nd to last weekday of the month: 2521 * 2522 * DTSTART;TZID=US-Eastern:19970929T090000 2523 * RRULE:FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-2;COUNT=7 2524 * ==> (1997 9:00 AM EDT)September 29 2525 * (1997 9:00 AM EST)October 30;November 27;December 30 2526 * (1998 9:00 AM EST)January 29;February 26;March 30 2527 * ... 2528 * 2529 * (Original RFC example is set to recur forever. But we just want to verify that the results match the dates listed from 2530 * the RFC example. So just limit the count to 7.) 2531 */ 2532 public function test_second_to_the_last_weekday_of_the_month() { 2533 global $DB; 2534 2535 $this->change_event_startdate('19970929T090000', 'US/Eastern'); 2536 2537 $rrule = 'FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-2;COUNT=7'; 2538 $mang = new rrule_manager($rrule); 2539 $mang->parse_rrule(); 2540 $mang->create_events($this->event); 2541 2542 // Get the first 7 samples. This should be enough to verify that we have generated the recurring events correctly. 2543 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart', 0, 7); 2544 2545 $expecteddates = [ 2546 (new \DateTime('1997-09-29 09:00:00 EDT'))->getTimestamp(), 2547 (new \DateTime('1997-10-30 09:00:00 EST'))->getTimestamp(), 2548 (new \DateTime('1997-11-27 09:00:00 EST'))->getTimestamp(), 2549 (new \DateTime('1997-12-30 09:00:00 EST'))->getTimestamp(), 2550 (new \DateTime('1998-01-29 09:00:00 EST'))->getTimestamp(), 2551 (new \DateTime('1998-02-26 09:00:00 EST'))->getTimestamp(), 2552 (new \DateTime('1998-03-30 09:00:00 EST'))->getTimestamp(), 2553 ]; 2554 2555 $untildate = new \DateTime(); 2556 $untildate->add(new \DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y')); 2557 $untiltimestamp = $untildate->getTimestamp(); 2558 2559 $i = 0; 2560 foreach ($records as $record) { 2561 $this->assertLessThanOrEqual($untiltimestamp, $record->timestart); 2562 2563 // Confirm that the first 7 records correspond to the expected dates listed above. 2564 $this->assertEquals($expecteddates[$i], $record->timestart); 2565 $i++; 2566 } 2567 } 2568 2569 /** 2570 * Every 3 hours from 9:00 AM to 5:00 PM on a specific day: 2571 * 2572 * DTSTART;TZID=US-Eastern:19970902T090000 2573 * RRULE:FREQ=HOURLY;INTERVAL=3;UNTIL=19970902T210000Z 2574 * ==> (September 2, 1997 EDT)09:00,12:00,15:00 2575 */ 2576 public function test_every_3hours_9am_to_5pm() { 2577 global $DB; 2578 2579 $rrule = 'FREQ=HOURLY;INTERVAL=3;UNTIL=19970902T210000Z'; 2580 $mang = new rrule_manager($rrule); 2581 $mang->parse_rrule(); 2582 $mang->create_events($this->event); 2583 2584 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 2585 $this->assertCount(3, $records); 2586 2587 $expecteddates = [ 2588 (new \DateTime('1997-09-02 09:00:00 EDT'))->getTimestamp(), 2589 (new \DateTime('1997-09-02 12:00:00 EDT'))->getTimestamp(), 2590 (new \DateTime('1997-09-02 15:00:00 EDT'))->getTimestamp(), 2591 ]; 2592 foreach ($records as $record) { 2593 $this->assertContainsEquals($record->timestart, $expecteddates, 2594 date('Y-m-d H:i:s', $record->timestart) . ' is not found.'); 2595 } 2596 } 2597 2598 /** 2599 * Every 15 minutes for 6 occurrences: 2600 * 2601 * DTSTART;TZID=US-Eastern:19970902T090000 2602 * RRULE:FREQ=MINUTELY;INTERVAL=15;COUNT=6 2603 * ==> (September 2, 1997 EDT)09:00,09:15,09:30,09:45,10:00,10:15 2604 */ 2605 public function test_every_15minutes_6_count() { 2606 global $DB; 2607 2608 $rrule = 'FREQ=MINUTELY;INTERVAL=15;COUNT=6'; 2609 $mang = new rrule_manager($rrule); 2610 $mang->parse_rrule(); 2611 $mang->create_events($this->event); 2612 2613 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 2614 $this->assertCount(6, $records); 2615 2616 $expecteddates = [ 2617 (new \DateTime('1997-09-02 09:00:00 EDT'))->getTimestamp(), 2618 (new \DateTime('1997-09-02 09:15:00 EDT'))->getTimestamp(), 2619 (new \DateTime('1997-09-02 09:30:00 EDT'))->getTimestamp(), 2620 (new \DateTime('1997-09-02 09:45:00 EDT'))->getTimestamp(), 2621 (new \DateTime('1997-09-02 10:00:00 EDT'))->getTimestamp(), 2622 (new \DateTime('1997-09-02 10:15:00 EDT'))->getTimestamp(), 2623 ]; 2624 foreach ($records as $record) { 2625 $this->assertContainsEquals($record->timestart, $expecteddates, 2626 date('Y-m-d H:i:s', $record->timestart) . ' is not found.'); 2627 } 2628 } 2629 2630 /** 2631 * Every hour and a half for 4 occurrences: 2632 * 2633 * DTSTART;TZID=US-Eastern:19970902T090000 2634 * RRULE:FREQ=MINUTELY;INTERVAL=90;COUNT=4 2635 * ==> (September 2, 1997 EDT)09:00,10:30;12:00;13:30 2636 */ 2637 public function test_every_90minutes_4_count() { 2638 global $DB; 2639 2640 $rrule = 'FREQ=MINUTELY;INTERVAL=90;COUNT=4'; 2641 $mang = new rrule_manager($rrule); 2642 $mang->parse_rrule(); 2643 $mang->create_events($this->event); 2644 2645 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 2646 $this->assertCount(4, $records); 2647 2648 $expecteddates = [ 2649 (new \DateTime('1997-09-02 09:00:00 EDT'))->getTimestamp(), 2650 (new \DateTime('1997-09-02 10:30:00 EDT'))->getTimestamp(), 2651 (new \DateTime('1997-09-02 12:00:00 EDT'))->getTimestamp(), 2652 (new \DateTime('1997-09-02 13:30:00 EDT'))->getTimestamp(), 2653 ]; 2654 foreach ($records as $record) { 2655 $this->assertContainsEquals($record->timestart, $expecteddates, 2656 date('Y-m-d H:i:s', $record->timestart) . ' is not found.'); 2657 } 2658 } 2659 2660 /** 2661 * Every 20 minutes from 9:00 AM to 4:40 PM every day for 100 times: 2662 * 2663 * (Original RFC example is set to everyday forever, but that will just take a lot of time for the test, 2664 * so just limit the count to 50). 2665 * 2666 * DTSTART;TZID=US-Eastern:19970902T090000 2667 * RRULE:FREQ=DAILY;BYHOUR=9,10,11,12,13,14,15,16;BYMINUTE=0,20,40;COUNT=50 2668 * ==> (September 2, 1997 EDT)9:00,9:20,9:40,10:00,10:20,...16:00,16:20,16:40 2669 * (September 3, 1997 EDT)9:00,9:20,9:40,10:00,10:20,...16:00,16:20,16:40 2670 * ... 2671 */ 2672 public function test_every_20minutes_daily_byhour_byminute_50_count() { 2673 global $DB; 2674 2675 $rrule = 'FREQ=DAILY;BYHOUR=9,10,11,12,13,14,15,16;BYMINUTE=0,20,40;COUNT=50'; 2676 $mang = new rrule_manager($rrule); 2677 $mang->parse_rrule(); 2678 $mang->create_events($this->event); 2679 2680 $byminuteinterval = new \DateInterval('PT20M'); 2681 $bydayinterval = new \DateInterval('P1D'); 2682 $date = new \DateTime('1997-09-02 09:00:00 EDT'); 2683 $expecteddates = []; 2684 $count = 50; 2685 for ($i = 0; $i < $count; $i++) { 2686 $expecteddates[] = $date->getTimestamp(); 2687 $date->add($byminuteinterval); 2688 if ($date->format('H') > 16) { 2689 // Go to next day. 2690 $date->add($bydayinterval); 2691 // Reset time to 9am. 2692 $date->setTime(9, 0); 2693 } 2694 } 2695 2696 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 2697 $this->assertCount($count, $records); 2698 2699 foreach ($records as $record) { 2700 $this->assertContainsEquals($record->timestart, $expecteddates, 2701 date('Y-m-d H:i:s', $record->timestart) . ' is not found.'); 2702 } 2703 } 2704 2705 /** 2706 * Every 20 minutes from 9:00 AM to 4:40 PM every day for 100 times: 2707 * 2708 * (Original RFC example is set to everyday forever, but that will just take a lot of time for the test, 2709 * so just limit the count to 50). 2710 * 2711 * DTSTART;TZID=US-Eastern:19970902T090000 2712 * RRULE:FREQ=MINUTELY;INTERVAL=20;BYHOUR=9,10,11,12,13,14,15,16;COUNT=50 2713 * ==> (September 2, 1997 EDT)9:00,9:20,9:40,10:00,10:20,...16:00,16:20,16:40 2714 * (September 3, 1997 EDT)9:00,9:20,9:40,10:00,10:20,...16:00,16:20,16:40 2715 * ... 2716 */ 2717 public function test_every_20minutes_minutely_byhour_50_count() { 2718 global $DB; 2719 2720 $rrule = 'FREQ=MINUTELY;INTERVAL=20;BYHOUR=9,10,11,12,13,14,15,16;COUNT=50'; 2721 $mang = new rrule_manager($rrule); 2722 $mang->parse_rrule(); 2723 $mang->create_events($this->event); 2724 2725 $byminuteinterval = new \DateInterval('PT20M'); 2726 $bydayinterval = new \DateInterval('P1D'); 2727 $date = new \DateTime('1997-09-02 09:00:00'); 2728 $expecteddates = []; 2729 $count = 50; 2730 for ($i = 0; $i < $count; $i++) { 2731 $expecteddates[] = $date->getTimestamp(); 2732 $date->add($byminuteinterval); 2733 if ($date->format('H') > 16) { 2734 // Go to next day. 2735 $date->add($bydayinterval); 2736 // Reset time to 9am. 2737 $date->setTime(9, 0); 2738 } 2739 } 2740 2741 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 2742 $this->assertCount($count, $records); 2743 2744 foreach ($records as $record) { 2745 $this->assertContainsEquals($record->timestart, $expecteddates, 2746 date('Y-m-d H:i:s', $record->timestart) . ' is not found.'); 2747 } 2748 } 2749 2750 /** 2751 * An example where the days generated makes a difference because of WKST: 2752 * 2753 * DTSTART;TZID=US-Eastern:19970805T090000 2754 * RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=MO 2755 * ==> (1997 EDT)Aug 5,10,19,24 2756 */ 2757 public function test_weekly_byday_with_wkst_mo() { 2758 global $DB; 2759 2760 $this->change_event_startdate('19970805T090000', 'US/Eastern'); 2761 2762 $rrule = 'FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=MO'; 2763 $mang = new rrule_manager($rrule); 2764 $mang->parse_rrule(); 2765 $mang->create_events($this->event); 2766 2767 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 2768 $this->assertCount(4, $records); 2769 2770 $expecteddates = [ 2771 (new \DateTime('1997-08-05 09:00:00 EDT'))->getTimestamp(), 2772 (new \DateTime('1997-08-10 09:00:00 EDT'))->getTimestamp(), 2773 (new \DateTime('1997-08-19 09:00:00 EDT'))->getTimestamp(), 2774 (new \DateTime('1997-08-24 09:00:00 EDT'))->getTimestamp(), 2775 ]; 2776 foreach ($records as $record) { 2777 $this->assertContainsEquals($record->timestart, $expecteddates, 2778 date('Y-m-d H:i:s', $record->timestart) . ' is not found.'); 2779 } 2780 } 2781 2782 /** 2783 * An example where the days generated makes a difference because of WKST: 2784 * Changing only WKST from MO to SU, yields different results... 2785 * 2786 * DTSTART;TZID=US-Eastern:19970805T090000 2787 * RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=SU 2788 * ==> (1997 EDT)August 5,17,19,31 2789 */ 2790 public function test_weekly_byday_with_wkst_su() { 2791 global $DB; 2792 2793 $this->change_event_startdate('19970805T090000', 'US/Eastern'); 2794 2795 $rrule = 'FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=SU'; 2796 $mang = new rrule_manager($rrule); 2797 $mang->parse_rrule(); 2798 $mang->create_events($this->event); 2799 2800 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 2801 $this->assertCount(4, $records); 2802 2803 $expecteddates = [ 2804 (new \DateTime('1997-08-05 09:00:00 EDT'))->getTimestamp(), 2805 (new \DateTime('1997-08-17 09:00:00 EDT'))->getTimestamp(), 2806 (new \DateTime('1997-08-19 09:00:00 EDT'))->getTimestamp(), 2807 (new \DateTime('1997-08-31 09:00:00 EDT'))->getTimestamp(), 2808 ]; 2809 2810 foreach ($records as $record) { 2811 $this->assertContainsEquals($record->timestart, $expecteddates, 2812 date('Y-m-d H:i:s', $record->timestart) . ' is not found.'); 2813 } 2814 } 2815 2816 /* 2817 * Other edge case tests. 2818 */ 2819 2820 /** 2821 * Tests for MONTHLY RRULE with BYMONTHDAY set to 31. 2822 * Should not include February, April, June, September and November. 2823 */ 2824 public function test_monthly_bymonthday_31() { 2825 global $DB; 2826 2827 $rrule = 'FREQ=MONTHLY;BYMONTHDAY=31;COUNT=20'; 2828 $mang = new rrule_manager($rrule); 2829 $mang->parse_rrule(); 2830 $mang->create_events($this->event); 2831 2832 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 2833 $this->assertCount(20, $records); 2834 2835 $non31months = ['February', 'April', 'June', 'September', 'November']; 2836 2837 foreach ($records as $record) { 2838 $month = date('F', $record->timestart); 2839 $this->assertNotContains($month, $non31months); 2840 } 2841 } 2842 2843 /** 2844 * Tests for the last day in February. (Leap year test) 2845 */ 2846 public function test_yearly_on_the_last_day_of_february() { 2847 global $DB; 2848 2849 $rrule = 'FREQ=YEARLY;BYMONTH=2;BYMONTHDAY=-1;COUNT=30'; 2850 $mang = new rrule_manager($rrule); 2851 $mang->parse_rrule(); 2852 $mang->create_events($this->event); 2853 2854 $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart'); 2855 $this->assertCount(30, $records); 2856 2857 foreach ($records as $record) { 2858 $date = new \DateTime(date('Y-m-d H:i:s', $record->timestart)); 2859 $year = $date->format('Y'); 2860 $day = $date->format('d'); 2861 if ($year % 4 == 0) { 2862 $this->assertEquals(29, $day); 2863 } else { 2864 $this->assertEquals(28, $day); 2865 } 2866 } 2867 } 2868 2869 /** 2870 * Change the event's timestart (DTSTART) based on the test's needs. 2871 * 2872 * @param string $datestr The date string. In 'Ymd\This' format. e.g. 19990902T090000. 2873 * @param null|string $timezonestr A valid timezone string. e.g. 'US/Eastern'. 2874 * If not provided, the default timezone will be used. 2875 * @return bool|DateTime 2876 */ 2877 protected function change_event_startdate($datestr, $timezonestr = null) { 2878 // Use default timezone if not provided. 2879 if ($timezonestr === null) { 2880 $newdatetime = \DateTime::createFromFormat('Ymd\THis', $datestr); 2881 } else { 2882 $timezone = new \DateTimeZone($timezonestr); 2883 $newdatetime = \DateTime::createFromFormat('Ymd\THis', $datestr, $timezone); 2884 } 2885 2886 // Update the start date of the parent event. 2887 $calevent = \calendar_event::load($this->event->id); 2888 $updatedata = (object)[ 2889 'timestart' => $newdatetime->getTimestamp(), 2890 'repeatid' => $this->event->id 2891 ]; 2892 $calevent->update($updatedata, false); 2893 $this->event->timestart = $calevent->timestart; 2894 2895 return $newdatetime; 2896 } 2897 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body