rrule_loop_limit = 100; // 1PM-2PM Monday-Thursday (only for one week), NZ time $base_cal = new vCalendar("BEGIN:VCALENDAR PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN VERSION:2.0 BEGIN:VEVENT CREATED:20190117T001216Z LAST-MODIFIED:20190117T001233Z DTSTAMP:20190117T001233Z UID:dae6404d-1ce0-42d0-af3b-0d303034197b SUMMARY:New Event RRULE:FREQ=DAILY;UNTIL=20190124T000000Z DTSTART;TZID=Pacific/Auckland:20190121T130000 DTEND;TZID=Pacific/Auckland:20190121T140000 TRANSP:OPAQUE END:VEVENT END:VCALENDAR"); $tuesday_renamed_cal = new vCalendar(" BEGIN:VCALENDAR PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN VERSION:2.0 BEGIN:VEVENT CREATED:20190117T001216Z LAST-MODIFIED:20190117T001805Z DTSTAMP:20190117T001805Z UID:d0d2df67-df7c-4b07-b729-221af3681c09 SUMMARY:New Event RRULE:FREQ=DAILY;UNTIL=20190124T000000Z DTSTART;TZID=Pacific/Auckland:20190121T130000 DTEND;TZID=Pacific/Auckland:20190121T140000 TRANSP:OPAQUE X-MOZ-GENERATION:1 END:VEVENT BEGIN:VEVENT CREATED:20190117T001741Z LAST-MODIFIED:20190117T001805Z DTSTAMP:20190117T001805Z UID:d0d2df67-df7c-4b07-b729-221af3681c09 SUMMARY:Tuesday has been renamed RECURRENCE-ID;TZID=Pacific/Auckland:20190122T130000 DTSTART;TZID=Pacific/Auckland:20190122T130000 DTEND;TZID=Pacific/Auckland:20190122T140000 TRANSP:OPAQUE X-MOZ-GENERATION:1 SEQUENCE:1 END:VEVENT END:VCALENDAR "); $tuesday_renamed_cal_order_swapped = new vCalendar(" BEGIN:VCALENDAR PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN VERSION:2.0 BEGIN:VEVENT CREATED:20190117T001741Z LAST-MODIFIED:20190117T001805Z DTSTAMP:20190117T001805Z UID:d0d2df67-df7c-4b07-b729-221af3681c09 SUMMARY:Tuesday has been renamed RECURRENCE-ID;TZID=Pacific/Auckland:20190122T130000 DTSTART;TZID=Pacific/Auckland:20190122T130000 DTEND;TZID=Pacific/Auckland:20190122T140000 TRANSP:OPAQUE X-MOZ-GENERATION:1 SEQUENCE:1 END:VEVENT BEGIN:VEVENT CREATED:20190117T001216Z LAST-MODIFIED:20190117T001805Z DTSTAMP:20190117T001805Z UID:d0d2df67-df7c-4b07-b729-221af3681c09 SUMMARY:New Event RRULE:FREQ=DAILY;UNTIL=20190124T000000Z DTSTART;TZID=Pacific/Auckland:20190121T130000 DTEND;TZID=Pacific/Auckland:20190121T140000 TRANSP:OPAQUE X-MOZ-GENERATION:1 END:VEVENT END:VCALENDAR "); $count_cal = new vCalendar("BEGIN:VCALENDAR PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN VERSION:2.0 BEGIN:VEVENT CREATED:20190117T001216Z LAST-MODIFIED:20190117T001233Z DTSTAMP:20190117T001233Z UID:dae6404d-1ce0-42d0-af3b-0d303034197b SUMMARY:New Event RRULE:FREQ=WEEKLY;COUNT=4;BYDAY=MO,TU,WE,TH DTSTART;TZID=Pacific/Auckland:20190121T130000 DTEND;TZID=Pacific/Auckland:20190121T140000 TRANSP:OPAQUE END:VEVENT END:VCALENDAR"); /** * A simplified model of get_freebusy, which works off of a passed-in vCalendar * rather than making SQL queries */ function get_freebusyish(vCalendar $cal) { $expansion = expand_event_instances($cal, new RepeatRuleDateTime('2019-01-01 00:00:00'))->GetComponents(['VEVENT' => true]); $result = array(); foreach ($expansion as $k => $instance) { // The same logic used in freebusy-functions (apart from default timezone // handling, which isn't really under test here) $start_date = new RepeatRuleDateTime($instance->GetProperty('DTSTART')); $duration = $instance->GetProperty('DURATION'); $duration = (!isset($duration) ? 'P1D' : $duration->Value()); $end_date = clone($start_date); $end_date->modify($duration); array_push($result, $start_date->UTC() .'/'. $end_date->UTC()); } sort($result); return $result; } final class ExpansionTest extends TestCase { const expected_freebusyish_for_base = [ '20190121T000000Z/20190121T010000Z', '20190122T000000Z/20190122T010000Z', '20190123T000000Z/20190123T010000Z', '20190124T000000Z/20190124T010000Z', ]; /** * @runInSeparateProcess * @preserveGlobalState enabled */ public function testUnmodifiedCal() { global $base_cal; self::assertEquals( self::expected_freebusyish_for_base, get_freebusyish($base_cal) ); } /** * @runInSeparateProcess * @preserveGlobalState enabled */ public function testTueRenamed() { global $tuesday_renamed_cal; self::assertEquals( self::expected_freebusyish_for_base, get_freebusyish($tuesday_renamed_cal) ); } /** * @runInSeparateProcess * @preserveGlobalState enabled */ public function testTueRenamedSwapped() { global $tuesday_renamed_cal_order_swapped; self::assertEquals( self::expected_freebusyish_for_base, get_freebusyish($tuesday_renamed_cal_order_swapped) ); } /** * @runInSeparateProcess * @preserveGlobalState enabled */ public function testCalWithCount() { global $count_cal; self::assertEquals( self::expected_freebusyish_for_base, get_freebusyish($count_cal) ); } }