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