MDL-58723 calendar: Better testing of forever recurring events
authorJun Pataleta <jun@moodle.com>
Wed, 26 Apr 2017 02:42:45 +0000 (10:42 +0800)
committerJun Pataleta <jun@moodle.com>
Tue, 16 May 2017 06:39:53 +0000 (14:39 +0800)
* Change the start date of the parent event to the current date
  for tests that with events that recur forever.
* Limit query results to 100 for events that recur forever that can
  generate more than 100 event records.

calendar/tests/rrule_manager_test.php

index 3c3e7c0..2c050f4 100644 (file)
@@ -451,6 +451,9 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
      */
     public function test_every_300_days_forever() {
         global $DB;
+
+        // Change the start date for forever events to 9am of the current date.
+        $this->change_event_startdate(date('Ymd\T090000'));
         $startdatetime = new DateTime(date('Y-m-d H:i:s', $this->event->timestart));
 
         $interval = new DateInterval('P300D');
@@ -463,7 +466,8 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
         $mang = new rrule_manager($rrule);
         $mang->parse_rrule();
         $mang->create_events($this->event);
-        $records = $DB->get_records('event', array('repeatid' => $this->event->id), 'timestart ASC');
+        // Get the first 100 samples. This should be enough to verify that we have generated the recurring events correctly.
+        $records = $DB->get_records('event', array('repeatid' => $this->event->id), 'timestart ASC', 0, 100);
 
         $expecteddate = clone($startdatetime);
         $first = true;
@@ -552,8 +556,20 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
                 $expecteddate->add($interval);
             }
         }
+    }
+
+    /**
+     * Test recurrence rules for weekly frequency for RRULE with BYDAY rule set, recurring forever.
+     */
+    public function test_weekly_byday_forever() {
+        global $DB;
 
-        // Forever event. This should generate events over time() + 10 year period, every 50th Monday.
+        // Set the next Monday as the starting date of this event.
+        $startdate = new DateTime('next Monday');
+        // Change the start date of the parent event.
+        $startdate = $this->change_event_startdate($startdate->format('Ymd\T090000'));
+
+        // Forever event. This should generate events over time() + 10 year period, every 50 weeks.
         $rrule = 'FREQ=WEEKLY;BYDAY=MO;INTERVAL=50';
 
         $mang = new rrule_manager($rrule);
@@ -564,13 +580,14 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
         $untildate->add(new DateInterval('P10Y'));
         $until = $untildate->getTimestamp();
 
-        $interval = new DateInterval('P50W');
         $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart');
 
-        // First instance of this set of recurring events: Monday, 17-08-1998.
+        $interval = new DateInterval('P50W');
+
+        // First instance of this set of recurring events.
         $expecteddate = clone($startdate);
-        $expecteddate->modify('1998-08-17');
-        $expecteddate->add($offsetinterval);
+
+        // Iterate over each record and increment the expected date accordingly.
         foreach ($records as $record) {
             $eventdateexpected = $expecteddate->format('Y-m-d H:i:s');
             $eventdateactual = date('Y-m-d H:i:s', $record->timestart);
@@ -676,13 +693,15 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
     public function test_monthly_events_with_bymonthday_forever() {
         global $DB;
 
+        // Change the start date for forever events to 9am of the 2nd day of the current month and year.
+        $this->change_event_startdate(date('Ym02\T090000'));
         $startdatetime = new DateTime(date('Y-m-d H:i:s', $this->event->timestart));
         $startdate = new DateTime(date('Y-m-d', $this->event->timestart));
 
         $offsetinterval = $startdatetime->diff($startdate, true);
         $interval = new DateInterval('P12M');
 
-        // Forever event. This should generate events over 10 year period, on 2nd day of every 12th month.
+        // Forever event. This should generate events over a 10-year period, on 2nd day of the month, every 12 months.
         $rrule = "FREQ=MONTHLY;INTERVAL=12;BYMONTHDAY=2";
 
         $mang = new rrule_manager($rrule);
@@ -828,13 +847,16 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
     public function test_monthly_events_with_byday_forever() {
         global $DB;
 
+        // Change the start date for forever events to 9am of the 2nd day of the current month and year.
+        $this->change_event_startdate(date('Ym02\T090000'));
+
         $startdatetime = new DateTime(date('Y-m-d H:i:s', $this->event->timestart));
         $startdate = new DateTime(date('Y-m-d', $this->event->timestart));
 
         $offsetinterval = $startdatetime->diff($startdate, true);
         $interval = new DateInterval('P12M');
 
-        // Forever event. This should generate events over 10 year period, on 2nd day of every 12th month.
+        // Forever event. This should generate events over a 10 year period, on 1st Monday of the month every 12 months.
         $rrule = "FREQ=MONTHLY;INTERVAL=12;BYDAY=1MO";
 
         $mang = new rrule_manager($rrule);
@@ -844,8 +866,12 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
         $mang->create_events($this->event);
 
         $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart');
-
-        $expecteddate = new DateTime('first Monday of September 1998');
+        $expecteddate = new DateTime('first Monday of this month');
+        // Move to the next interval's first Monday if the calculated start date is after this month's first Monday.
+        if ($expecteddate->getTimestamp() < $startdatetime->getTimestamp()) {
+            $expecteddate->add($interval);
+            $expecteddate->modify('first Monday of this month');
+        }
         foreach ($records as $record) {
             $expecteddate->add($offsetinterval);
             $this->assertLessThanOrEqual($until, $record->timestart);
@@ -890,7 +916,7 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
         // Create a yearly event, until the time limit is hit.
         $until = strtotime('+20 day +10 years', $this->event->timestart);
         $until = date('Ymd\THis\Z', $until);
-        $rrule = "FREQ=YEARLY;BYMONTH=9;UNTIL=$until"; // Forever event.
+        $rrule = "FREQ=YEARLY;BYMONTH=9;UNTIL=$until";
         $mang = new rrule_manager($rrule);
         $mang->parse_rrule();
         $mang->create_events($this->event);
@@ -917,18 +943,6 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
             $this->assertTrue($result);
         }
 
-        $rrule = "FREQ=YEARLY;BYMONTH=9;INTERVAL=2"; // Forever event.
-        $mang = new rrule_manager($rrule);
-        $until = time() + (YEARSECS * $mang::TIME_UNLIMITED_YEARS);
-        $mang->parse_rrule();
-        $mang->create_events($this->event);
-        for ($i = 0, $time = $this->event->timestart; $time < $until; $i++, $yoffset = $i * 2,
-            $time = strtotime("+$yoffset years", $this->event->timestart)) {
-            $result = $DB->record_exists('event', array('repeatid' => $this->event->id,
-                    'timestart' => ($time)));
-            $this->assertTrue($result);
-        }
-
         $rrule = "FREQ=YEARLY;COUNT=3;BYMONTH=9;BYDAY=1MO"; // This should generate 3 events in total.
         $mang = new rrule_manager($rrule);
         $mang->parse_rrule();
@@ -1004,12 +1018,45 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
         }
     }
 
+    /**
+     * Test for rrule with FREQ=YEARLY and INTERVAL=2 with BYMONTH rule set, recurring forever.
+     */
+    public function test_yearly_september_every_two_years_forever() {
+        global $DB;
+
+        // Change the start date for forever events to 9am on the month of September of the current year using the current day.
+        $this->change_event_startdate(date('Y09d\T090000'));
+
+        $rrule = "FREQ=YEARLY;BYMONTH=9;INTERVAL=2"; // Forever event.
+        $mang = new rrule_manager($rrule);
+        $untildate = new DateTime();
+        $untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y'));
+        $untiltimestamp = $untildate->getTimestamp();
+        $mang->parse_rrule();
+        $mang->create_events($this->event);
+
+        $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart');
+
+        $interval = new DateInterval('P2Y');
+        $expecteddate = new DateTime(date('Y09d\T090000'));
+        foreach ($records as $record) {
+            $this->assertLessThanOrEqual($untiltimestamp, $record->timestart);
+            $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart));
+
+            // Go to the next expected date.
+            $expecteddate->add($interval);
+        }
+    }
+
     /**
      * Test for rrule with FREQ=YEARLY with BYMONTH and BYDAY rules set, recurring forever.
      */
     public function test_yearly_bymonth_byday_forever() {
         global $DB;
 
+        // Change the start date for forever events to the first day of September of the current year at 9am.
+        $this->change_event_startdate(date('Y0901\T090000'));
+
         // Every 2 years on the first Monday of September.
         $rrule = "FREQ=YEARLY;BYMONTH=9;INTERVAL=2;BYDAY=1MO";
         $mang = new rrule_manager($rrule);
@@ -1028,9 +1075,9 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
         $offsetinterval = $startdatetime->diff($startdate, true);
         $interval = new DateInterval('P2Y');
 
-        // First occurrence of this set of events is on the first Monday of September 1999.
+        // First occurrence of this set of events is on the first Monday of September.
         $expecteddate = clone($startdatetime);
-        $expecteddate->modify('first Monday of September 1999');
+        $expecteddate->modify('first Monday of September');
         $expecteddate->add($offsetinterval);
         foreach ($records as $record) {
             $this->assertLessThanOrEqual($untiltimestamp, $record->timestart);
@@ -1050,6 +1097,9 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
     public function test_yearly_forever() {
         global $DB;
 
+        // Change the start date for forever events to 9am of the current date.
+        $this->change_event_startdate(date('Ymd\T090000'));
+
         $startdatetime = new DateTime(date('Y-m-d H:i:s', $this->event->timestart));
 
         $interval = new DateInterval('P2Y');
@@ -1147,14 +1197,19 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
     /**
      * Every other day - forever:
      *
-     * DTSTART;TZID=US-Eastern:19970902T090000
+     * DTSTART;TZID=US-Eastern:[Current date]T090000
      * RRULE:FREQ=DAILY;INTERVAL=2
-     *   ==> (1997 9:00 AM EDT)September2,4,6,8...24,26,28,30;October 2,4,6...20,22,24
-     *       (1997 9:00 AM EST)October 26,28,30;November 1,3,5,7...25,27,29;Dec 1,3,...
+     *
+     * Sample results (e.g. in the year 1997):
+     *  (1997 9:00 AM EDT)September2,4,6,8...24,26,28,30;October 2,4,6...20,22,24
+     *  (1997 9:00 AM EST)October 26,28,30;November 1,3,5,7...25,27,29;Dec 1,3,...
      */
     public function test_every_other_day_forever() {
         global $DB;
 
+        // Change the start date for forever events to 9am of the current date in US/Eastern time.
+        $this->change_event_startdate(date('Ymd\T090000'), 'US/Eastern');
+
         $startdatetime = new DateTime(date('Y-m-d H:i:s', $this->event->timestart));
         $interval = new DateInterval('P2D');
 
@@ -1163,7 +1218,8 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
         $mang->parse_rrule();
         $mang->create_events($this->event);
 
-        $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart');
+        // Get the first 100 samples. This should be enough to verify that we have generated the recurring events correctly.
+        $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart', 0, 100);
 
         $untildate = new DateTime();
         $untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y'));
@@ -1341,16 +1397,21 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
     /**
      * Every other week - forever:
      *
-     * DTSTART;TZID=US-Eastern:19970902T090000
+     * DTSTART;TZID=US-Eastern:[Current date]T090000
      * RRULE:FREQ=WEEKLY;INTERVAL=2;WKST=SU
-     *   ==> (1997 9:00 AM EDT)September 2,16,30;October 14
-     *       (1997 9:00 AM EST)October 28;November 11,25;December 9,23
-     *       (1998 9:00 AM EST)January 6,20;February
-     *        ...
+     *
+     * Sample results (e.g. in the year 1997):
+     *  (1997 9:00 AM EDT)September 2,16,30;October 14
+     *  (1997 9:00 AM EST)October 28;November 11,25;December 9,23
+     *  (1998 9:00 AM EST)January 6,20;February
+     *  ...
      */
     public function test_every_other_week_forever() {
         global $DB;
 
+        // Change the start date for forever events to 9am of the current date in US/Eastern time.
+        $this->change_event_startdate(date('Ymd\T090000'), 'US/Eastern');
+
         $interval = new DateInterval('P2W');
 
         $rrule = 'FREQ=WEEKLY;INTERVAL=2;WKST=SU';
@@ -1358,7 +1419,8 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
         $mang->parse_rrule();
         $mang->create_events($this->event);
 
-        $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart');
+        // Get the first 100 samples. This should be enough to verify that we have generated the recurring events correctly.
+        $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart', 0, 100);
 
         $untildate = new DateTime();
         $untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y'));
@@ -1704,25 +1766,28 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
     /**
      * Monthly on the third to the last day of the month, forever:
      *
-     * DTSTART;TZID=US-Eastern:19970928T090000
+     * DTSTART;TZID=US-Eastern:[Current year]0928T090000
      * RRULE:FREQ=MONTHLY;BYMONTHDAY=-3
-     *   ==> (1997 9:00 AM EDT)September 28
-     *       (1997 9:00 AM EST)October 29;November 28;December 29
-     *       (1998 9:00 AM EST)January 29;February 26
-     *       ...
+     *
+     * Sample results (e.g. in the year 1997):
+     *  (1997 9:00 AM EDT)September 28
+     *  (1997 9:00 AM EST)October 29;November 28;December 29
+     *  (1998 9:00 AM EST)January 29;February 26
+     *  ...
      */
     public function test_third_to_the_last_day_of_the_month_forever() {
         global $DB;
 
-        // Change our event's date to 05-09-1997, based on the example from the RFC.
-        $this->change_event_startdate('19970928T090000', 'US/Eastern');
+        // Change our event's date to 28 September of the current year, based on the example from the RFC.
+        $this->change_event_startdate(date('Y0928\T090000'), 'US/Eastern');
 
         $rrule = 'FREQ=MONTHLY;BYMONTHDAY=-3';
         $mang = new rrule_manager($rrule);
         $mang->parse_rrule();
         $mang->create_events($this->event);
 
-        $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart');
+        // Get the first 100 samples. This should be enough to verify that we have generated the recurring events correctly.
+        $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart', 0, 100);
 
         $untildate = new DateTime();
         $untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y'));
@@ -1878,22 +1943,29 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
     /**
      * Every Tuesday, every other month:
      *
-     * DTSTART;TZID=US-Eastern:19970902T090000
+     * DTSTART;TZID=US-Eastern:[Next Tuesday]T090000
      * RRULE:FREQ=MONTHLY;INTERVAL=2;BYDAY=TU
-     *   ==> (1997 9:00 AM EDT)September 2,9,16,23,30
-     *       (1997 9:00 AM EST)November 4,11,18,25
-     *       (1998 9:00 AM EST)January 6,13,20,27;March 3,10,17,24,31
-     *       ...
+     *
+     * Sample results (e.g. in the year 1997):
+     *  (1997 9:00 AM EDT)September 2,9,16,23,30
+     *  (1997 9:00 AM EST)November 4,11,18,25
+     *  (1998 9:00 AM EST)January 6,13,20,27;March 3,10,17,24,31
+     *  ...
      */
     public function test_every_tuesday_every_other_month_forever() {
         global $DB;
 
+        // Change the start date for forever events to 9am of the Tuesday on or before of the current date in US/Eastern time.
+        $nexttuesday = new DateTime('next Tuesday');
+        $this->change_event_startdate($nexttuesday->format('Ymd\T090000'), 'US/Eastern');
+
         $rrule = 'FREQ=MONTHLY;INTERVAL=2;BYDAY=TU';
         $mang = new rrule_manager($rrule);
         $mang->parse_rrule();
         $mang->create_events($this->event);
 
-        $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart');
+        // Get the first 100 samples. This should be enough to verify that we have generated the recurring events correctly.
+        $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart', 0, 100);
 
         $untildate = new DateTime();
         $untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y'));
@@ -2063,18 +2135,22 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
     /**
      * Every 20th Monday of the year, forever:
      *
-     * DTSTART;TZID=US-Eastern:19970519T090000
+     * DTSTART;TZID=US-Eastern:[20th Monday of the current year]T090000
      * RRULE:FREQ=YEARLY;BYDAY=20MO
-     *   ==> (1997 9:00 AM EDT)May 19
-     *       (1998 9:00 AM EDT)May 18
-     *       (1999 9:00 AM EDT)May 17
-     *       ...
+     *
+     * Sample results (e.g. in the year 1997):
+     *  (1997 9:00 AM EDT)May 19
+     *  (1998 9:00 AM EDT)May 18
+     *  (1999 9:00 AM EDT)May 17
+     *  ...
      */
     public function test_yearly_every_20th_monday_forever() {
         global $DB;
 
-        // Change our event's date to 19-05-1997, based on the example from the RFC.
-        $startdatetime = $this->change_event_startdate('19970519T090000', 'US/Eastern');
+        // Change our event's date to the 20th Monday of the current year.
+        $twentiethmonday = new DateTime(date('Y-01-01'));
+        $twentiethmonday->modify('+20 Monday');
+        $startdatetime = $this->change_event_startdate($twentiethmonday->format('Ymd\T090000'), 'US/Eastern');
 
         $startdate = new DateTime($startdatetime->format('Y-m-d'));
 
@@ -2110,18 +2186,22 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
     /**
      * Monday of week number 20 (where the default start of the week is Monday), forever:
      *
-     * DTSTART;TZID=US-Eastern:19970512T090000
+     * DTSTART;TZID=US-Eastern:[1st day of the 20th week this year]T090000
      * RRULE:FREQ=YEARLY;BYWEEKNO=20;BYDAY=MO
-     * ==> (1997 9:00 AM EDT)May 12
-     *     (1998 9:00 AM EDT)May 11
-     *     (1999 9:00 AM EDT)May 17
-     *     ...
+     *
+     * Sample results (e.g. in the year 1997):
+     *  (1997 9:00 AM EDT)May 12
+     *  (1998 9:00 AM EDT)May 11
+     *  (1999 9:00 AM EDT)May 17
+     *  ...
      */
     public function test_yearly_byweekno_forever() {
         global $DB;
 
-        // Change our event's date to 12-05-1997, based on the example from the RFC.
-        $startdatetime = $this->change_event_startdate('19970512T090000', 'US/Eastern');
+        // Change our event's date to the start of the 20th week of the current year.
+        $twentiethweek = new DateTime(date('Y-01-01'));
+        $twentiethweek->setISODate($twentiethweek->format('Y'), 20);
+        $startdatetime = $this->change_event_startdate($twentiethweek->format('Ymd\T090000'), 'US/Eastern');
 
         $startdate = clone($startdatetime);
         $startdate->modify($startdate->format('Y-m-d'));
@@ -2156,18 +2236,21 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
     /**
      * Every Thursday in March, forever:
      *
-     * DTSTART;TZID=US-Eastern:19970313T090000
+     * DTSTART;TZID=US-Eastern:[First thursday of March of the current year]T090000
      * RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=TH
-     *   ==> (1997 9:00 AM EST)March 13,20,27
-     *       (1998 9:00 AM EST)March 5,12,19,26
-     *       (1999 9:00 AM EST)March 4,11,18,25
-     *       ...
+     *
+     * Sample results (e.g. in the year 1997):
+     *  (1997 9:00 AM EST)March 13,20,27
+     *  (1998 9:00 AM EST)March 5,12,19,26
+     *  (1999 9:00 AM EST)March 4,11,18,25
+     *  ...
      */
     public function test_every_thursday_in_march_forever() {
         global $DB;
 
-        // Change our event's date to 12-05-1997, based on the example from the RFC.
-        $startdatetime = $this->change_event_startdate('19970313T090000', 'US/Eastern');
+        // Change our event's date to the first Thursday of March of the current year at 9am US/Eastern time.
+        $firstthursdayofmarch = new DateTime('first Thursday of March');
+        $startdatetime = $this->change_event_startdate($firstthursdayofmarch->format('Ymd\T090000'), 'US/Eastern');
 
         $interval = new DateInterval('P1Y');
 
@@ -2176,7 +2259,8 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
         $mang->parse_rrule();
         $mang->create_events($this->event);
 
-        $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart');
+        // Get the first 100 samples. This should be enough to verify that we have generated the recurring events correctly.
+        $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart', 0, 100);
 
         $untildate = new DateTime();
         $untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y'));
@@ -2186,7 +2270,7 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
         $startdate = new DateTime($startdatetime->format('Y-m-d'));
         $offsetinterval = $startdatetime->diff($startdate, true);
         $expecteddate->setTimezone(new DateTimeZone(get_user_timezone()));
-        $april1st = new DateTime('1997-04-01');
+        $april1st = new DateTime('April 1');
         foreach ($records as $record) {
             $this->assertLessThanOrEqual($untiltimestamp, $record->timestart);
             $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart));
@@ -2211,18 +2295,21 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
     /**
      * Every Thursday, but only during June, July, and August, forever:
      *
-     * DTSTART;TZID=US-Eastern:19970605T090000
+     * DTSTART;TZID=US-Eastern:[First Thursday of June of the current year]T090000
      * RRULE:FREQ=YEARLY;BYDAY=TH;BYMONTH=6,7,8
-     *   ==> (1997 9:00 AM EDT)June 5,12,19,26;July 3,10,17,24,31;August 7,14,21,28
-     *       (1998 9:00 AM EDT)June 4,11,18,25;July 2,9,16,23,30;August 6,13,20,27
-     *       (1999 9:00 AM EDT)June 3,10,17,24;July 1,8,15,22,29;August 5,12,19,26
-     *       ...
+     *
+     * Sample results (e.g. in the year 1997):
+     *  (1997 9:00 AM EDT)June 5,12,19,26;July 3,10,17,24,31;August 7,14,21,28
+     *  (1998 9:00 AM EDT)June 4,11,18,25;July 2,9,16,23,30;August 6,13,20,27
+     *  (1999 9:00 AM EDT)June 3,10,17,24;July 1,8,15,22,29;August 5,12,19,26
+     *  ...
      */
     public function test_every_thursday_june_july_august_forever() {
         global $DB;
 
-        // Change our event's date to 05-06-1997, based on the example from the RFC.
-        $startdatetime = $this->change_event_startdate('19970605T090000', 'US/Eastern');
+        // Change our event's date to the first Thursday of June in the current year at 9am US/Eastern time.
+        $firstthursdayofjune = new DateTime('first Thursday of June');
+        $startdatetime = $this->change_event_startdate($firstthursdayofjune->format('Ymd\T090000'), 'US/Eastern');
 
         $startdate = new DateTime($startdatetime->format('Y-m-d'));
 
@@ -2235,14 +2322,15 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
         $mang->parse_rrule();
         $mang->create_events($this->event);
 
-        $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart');
+        // Get the first 100 samples. This should be enough to verify that we have generated the recurring events correctly.
+        $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart', 0, 100);
 
         $untildate = new DateTime();
         $untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y'));
         $untiltimestamp = $untildate->getTimestamp();
 
         $expecteddate = new DateTime(date('Y-m-d H:i:s', $startdatetime->getTimestamp()));
-        $september1st = new DateTime('1997-09-01');
+        $september1st = new DateTime('September 1');
         foreach ($records as $record) {
             $this->assertLessThanOrEqual($untiltimestamp, $record->timestart);
             $this->assertEquals($expecteddate->format('Y-m-d H:i:s'), date('Y-m-d H:i:s', $record->timestart));
@@ -2264,22 +2352,28 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
     /**
      * Every Friday the 13th, forever:
      *
-     * DTSTART;TZID=US-Eastern:19970902T090000
-     * EXDATE;TZID=US-Eastern:19970902T090000
+     * DTSTART;TZID=US-Eastern:[Current date]T090000
      * RRULE:FREQ=MONTHLY;BYDAY=FR;BYMONTHDAY=13
-     *   ==> (1998 9:00 AM EST)February 13;March 13;November 13
-     *       (1999 9:00 AM EDT)August 13
-     *       (2000 9:00 AM EDT)October 13
+     *
+     * Sample results (e.g. in the year 1997):
+     *  (1998 9:00 AM EST)February 13;March 13;November 13
+     *  (1999 9:00 AM EDT)August 13
+     *  (2000 9:00 AM EDT)October 13
+     *  ...
      */
     public function test_friday_the_thirteenth_forever() {
         global $DB;
 
+        // Change our event's date to the first Thursday of June in the current year at 9am US/Eastern time.
+        $this->change_event_startdate(date('Ymd\T090000'), 'US/Eastern');
+
         $rrule = 'FREQ=MONTHLY;BYDAY=FR;BYMONTHDAY=13';
         $mang = new rrule_manager($rrule);
         $mang->parse_rrule();
         $mang->create_events($this->event);
 
-        $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart');
+        // Get the first 100 samples. This should be enough to verify that we have generated the recurring events correctly.
+        $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart', 0, 100);
 
         $untildate = new DateTime();
         $untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y'));
@@ -2295,17 +2389,22 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
     /**
      * The first Saturday that follows the first Sunday of the month, forever:
      *
-     * DTSTART;TZID=US-Eastern:19970913T090000
+     * DTSTART;TZID=US-Eastern:[The Saturday after the month's first Sunday]T090000
      * RRULE:FREQ=MONTHLY;BYDAY=SA;BYMONTHDAY=7,8,9,10,11,12,13
-     *   ==> (1997 9:00 AM EDT)September 13;October 11
-     *       (1997 9:00 AM EST)November 8;December 13
-     *       (1998 9:00 AM EST)January 10;February 7;March 7
-     *       (1998 9:00 AM EDT)April 11;May 9;June 13...
+     *
+     * Sample results (e.g. from 13 September 1997):
+     *  (1997 9:00 AM EDT)September 13;October 11
+     *  (1997 9:00 AM EST)November 8;December 13
+     *  (1998 9:00 AM EST)January 10;February 7;March 7
+     *  (1998 9:00 AM EDT)April 11;May 9;June 13...
      */
     public function test_first_saturday_following_first_sunday_forever() {
         global $DB;
 
-        $startdatetime = $this->change_event_startdate('19970913T090000', 'US/Eastern');
+        // Change our event's date to the next Saturday after the first Sunday of the the current month at 9am US/Eastern time.
+        $firstsaturdayafterfirstsunday = new DateTime('first Sunday of this month');
+        $firstsaturdayafterfirstsunday->modify('next Saturday');
+        $startdatetime = $this->change_event_startdate($firstsaturdayafterfirstsunday->format('Ymd\T090000'), 'US/Eastern');
         $startdate = new DateTime($startdatetime->format('Y-m-d'));
         $offset = $startdatetime->diff($startdate, true);
 
@@ -2314,7 +2413,8 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
         $mang->parse_rrule();
         $mang->create_events($this->event);
 
-        $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart');
+        // Get the first 100 samples. This should be enough to verify that we have generated the recurring events correctly.
+        $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart', 0, 100);
 
         $untildate = new DateTime();
         $untildate->add(new DateInterval('P' . $mang::TIME_UNLIMITED_YEARS . 'Y'));
@@ -2340,17 +2440,29 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
     /**
      * Every four years, the first Tuesday after a Monday in November, forever (U.S. Presidential Election day):
      *
-     * DTSTART;TZID=US-Eastern:19961105T090000
+     * DTSTART;TZID=US-Eastern:[Most recent election date]T090000
      * RRULE:FREQ=YEARLY;INTERVAL=4;BYMONTH=11;BYDAY=TU;BYMONTHDAY=2,3,4,5,6,7,8
-     *   ==> (1996 9:00 AM EST)November 5
-     *       (2000 9:00 AM EST)November 7
-     *       (2004 9:00 AM EST)November 2
-     *       ...
+     *
+     * Sample results (e.g. from 05 November 1996):
+     *  (1996 9:00 AM EST)November 5
+     *  (2000 9:00 AM EST)November 7
+     *  (2004 9:00 AM EST)November 2
+     *  ...
      */
     public function test_every_us_presidential_election_forever() {
         global $DB;
 
-        $startdatetime = $this->change_event_startdate('19961105T090000', 'US/Eastern');
+        // Calculate the most recent election date, starting from 1996 (e.g. today's 2017 so the most recent election was in 2016).
+        $currentyear = (int) date('Y');
+        $electionyear = 1996;
+        while ($electionyear + 4 < $currentyear) {
+            $electionyear += 4;
+        }
+        $electiondate = new DateTime('first Monday of November ' . $electionyear);
+        $electiondate->modify('+1 Tuesday');
+
+        // Use the most recent election date as the starting date of our recurring events.
+        $startdatetime = $this->change_event_startdate($electiondate->format('Ymd\T090000'), 'US/Eastern');
         $startdate = new DateTime($startdatetime->format('Y-m-d'));
         $offset = $startdatetime->diff($startdate, true);
 
@@ -2417,23 +2529,27 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
      * The 2nd to last weekday of the month:
      *
      * DTSTART;TZID=US-Eastern:19970929T090000
-     * RRULE:FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-2
+     * RRULE:FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-2;COUNT=7
      *   ==> (1997 9:00 AM EDT)September 29
      *       (1997 9:00 AM EST)October 30;November 27;December 30
      *       (1998 9:00 AM EST)January 29;February 26;March 30
      *       ...
+     *
+     * (Original RFC example is set to recur forever. But we just want to verify that the results match the dates listed from
+     * the RFC example. So just limit the count to 7.)
      */
-    public function test_second_to_the_last_weekday_of_the_month_forever() {
+    public function test_second_to_the_last_weekday_of_the_month() {
         global $DB;
 
         $this->change_event_startdate('19970929T090000', 'US/Eastern');
 
-        $rrule = 'FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-2';
+        $rrule = 'FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-2;COUNT=7';
         $mang = new rrule_manager($rrule);
         $mang->parse_rrule();
         $mang->create_events($this->event);
 
-        $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart');
+        // Get the first 7 samples. This should be enough to verify that we have generated the recurring events correctly.
+        $records = $DB->get_records('event', ['repeatid' => $this->event->id], 'timestart ASC', 'id, repeatid, timestart', 0, 7);
 
         $expecteddates = [
             (new DateTime('1997-09-29 09:00:00 EDT'))->getTimestamp(),
@@ -2454,10 +2570,8 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
             $this->assertLessThanOrEqual($untiltimestamp, $record->timestart);
 
             // Confirm that the first 7 records correspond to the expected dates listed above.
-            if ($i < 7) {
-                $this->assertEquals($expecteddates[$i], $record->timestart);
-                $i++;
-            }
+            $this->assertEquals($expecteddates[$i], $record->timestart);
+            $i++;
         }
     }
 
@@ -2757,13 +2871,19 @@ class core_calendar_rrule_manager_testcase extends advanced_testcase {
     /**
      * Change the event's timestart (DTSTART) based on the test's needs.
      *
-     * @param string $datestr The date string. In YYYYmmddThhiiss format. e.g. 19990902T090000.
-     * @param string $timezonestr A valid timezone string. e.g. 'US/Eastern'.
+     * @param string $datestr The date string. In 'Ymd\This' format. e.g. 19990902T090000.
+     * @param null|string $timezonestr A valid timezone string. e.g. 'US/Eastern'.
+     *                                 If not provided, the default timezone will be used.
      * @return bool|DateTime
      */
-    protected function change_event_startdate($datestr, $timezonestr) {
-        $timezone = new DateTimeZone($timezonestr);
-        $newdatetime = DateTime::createFromFormat('Ymd\THis', $datestr, $timezone);
+    protected function change_event_startdate($datestr, $timezonestr = null) {
+        // Use default timezone if not provided.
+        if ($timezonestr === null) {
+            $newdatetime = DateTime::createFromFormat('Ymd\THis', $datestr);
+        } else {
+            $timezone = new DateTimeZone($timezonestr);
+            $newdatetime = DateTime::createFromFormat('Ymd\THis', $datestr, $timezone);
+        }
 
         // Update the start date of the parent event.
         $calevent = calendar_event::load($this->event->id);