diff --git a/src/parser/calendar/data/__tests__/PhutilCalendarRecurrenceRuleTestCase.php b/src/parser/calendar/data/__tests__/PhutilCalendarRecurrenceRuleTestCase.php index 18912b4..02061f6 100644 --- a/src/parser/calendar/data/__tests__/PhutilCalendarRecurrenceRuleTestCase.php +++ b/src/parser/calendar/data/__tests__/PhutilCalendarRecurrenceRuleTestCase.php @@ -1,1166 +1,1555 @@ setStartDateTime($start) ->setFrequency(PhutilCalendarRecurrenceRule::FREQUENCY_DAILY); $set = id(new PhutilCalendarRecurrenceSet()) ->addSource($rrule); $result = $set->getEventsBetween(null, null, 3); $expect = array( PhutilCalendarAbsoluteDateTime::newFromISO8601('20160101T120000Z'), PhutilCalendarAbsoluteDateTime::newFromISO8601('20160102T120000Z'), PhutilCalendarAbsoluteDateTime::newFromISO8601('20160103T120000Z'), ); $this->assertEqual( mpull($expect, 'getISO8601'), mpull($result, 'getISO8601'), pht('Simple daily event.')); $rrule = id(new PhutilCalendarRecurrenceRule()) ->setStartDateTime($start) ->setFrequency(PhutilCalendarRecurrenceRule::FREQUENCY_HOURLY) ->setByHour(array(12, 13)); $set = id(new PhutilCalendarRecurrenceSet()) ->addSource($rrule); $result = $set->getEventsBetween(null, null, 5); $expect = array( PhutilCalendarAbsoluteDateTime::newFromISO8601('20160101T120000Z'), PhutilCalendarAbsoluteDateTime::newFromISO8601('20160101T130000Z'), PhutilCalendarAbsoluteDateTime::newFromISO8601('20160102T120000Z'), PhutilCalendarAbsoluteDateTime::newFromISO8601('20160102T130000Z'), PhutilCalendarAbsoluteDateTime::newFromISO8601('20160103T120000Z'), ); $this->assertEqual( mpull($expect, 'getISO8601'), mpull($result, 'getISO8601'), pht('Hourly event with BYHOUR.')); $rrule = id(new PhutilCalendarRecurrenceRule()) ->setStartDateTime($start) ->setFrequency(PhutilCalendarRecurrenceRule::FREQUENCY_YEARLY); $set = id(new PhutilCalendarRecurrenceSet()) ->addSource($rrule); $result = $set->getEventsBetween(null, null, 2); $expect = array( PhutilCalendarAbsoluteDateTime::newFromISO8601('20160101T120000Z'), PhutilCalendarAbsoluteDateTime::newFromISO8601('20170101T120000Z'), ); $this->assertEqual( mpull($expect, 'getISO8601'), mpull($result, 'getISO8601'), pht('Yearly event.')); // This is an efficiency test for bizarre rules: it defines a secondly // event which only occurs one a year, and generates 3 instances of it. // This implementation should be fast enough that this test doesn't take // a significant amount of time. $rrule = id(new PhutilCalendarRecurrenceRule()) ->setStartDateTime($start) ->setFrequency(PhutilCalendarRecurrenceRule::FREQUENCY_SECONDLY) ->setByMonth(array(1)) ->setByMonthDay(array(1)) ->setByHour(array(12)) ->setByMinute(array(0)) ->setBySecond(array(0)); $set = id(new PhutilCalendarRecurrenceSet()) ->addSource($rrule); $result = $set->getEventsBetween(null, null, 3); $expect = array( PhutilCalendarAbsoluteDateTime::newFromISO8601('20160101T120000Z'), PhutilCalendarAbsoluteDateTime::newFromISO8601('20170101T120000Z'), PhutilCalendarAbsoluteDateTime::newFromISO8601('20180101T120000Z'), ); $this->assertEqual( mpull($expect, 'getISO8601'), mpull($result, 'getISO8601'), pht('Secondly event with many constraints.')); } public function testYearlyRecurrenceRules() { $tests = array(); $expect = array(); $tests[] = array(); $expect[] = array( '19970902', '19980902', '19990902', ); $tests[] = array( 'INTERVAL' => 2, ); $expect[] = array( '19970902', '19990902', '20010902', ); $tests[] = array( 'DTSTART' => '20000229', ); $expect[] = array( '20000229', '20040229', '20080229', ); $tests[] = array( 'BYMONTH' => array(1, 3), ); $expect[] = array( '19980102', '19980302', '19990102', ); $tests[] = array( 'BYMONTHDAY' => array(1, 3), ); $expect[] = array( '19970903', '19971001', '19971003', ); $tests[] = array( 'BYMONTH' => array(1, 3), 'BYMONTHDAY' => array(5, 7), ); $expect[] = array( '19980105', '19980107', '19980305', ); $tests[] = array( 'BYDAY' => array('TU', 'TH'), ); $expect[] = array( '19970902', '19970904', '19970909', ); $tests[] = array( 'BYDAY' => array('SU'), ); $expect[] = array( '19970907', '19970914', '19970921', ); $tests[] = array( 'BYMONTH' => array(1, 3), 'BYDAY' => array('TU', 'TH'), ); $expect[] = array( '19980101', '19980106', '19980108', ); $tests[] = array( 'BYMONTHDAY' => array(1, 3), 'BYDAY' => array('TU', 'TH'), ); $expect[] = array( '19980101', '19980203', '19980303', ); $tests[] = array( 'BYMONTHDAY' => array(1, 3), 'BYDAY' => array('TU', 'TH'), 'BYMONTH' => array(1, 3), ); $expect[] = array( '19980101', '19980303', '20010301', ); $tests[] = array( 'BYDAY' => array('1TU', '-1TH'), ); $expect[] = array( '19971225', '19980106', '19981231', ); // Same test as above, just making sure the optional "+" syntax works. $tests[] = array( 'BYDAY' => array('+1TU', '-1TH'), ); $expect[] = array( '19971225', '19980106', '19981231', ); $tests[] = array( 'BYDAY' => array('3TU', '-3TH'), ); $expect[] = array( '19971211', '19980120', '19981217', ); $tests[] = array( 'BYMONTH' => array(1, 3), 'BYDAY' => array('1TU', '-1TH'), ); $expect[] = array( '19980106', '19980129', '19980303', ); $tests[] = array( 'BYMONTH' => array(1, 3), 'BYDAY' => array('3TU', '-3TH'), ); $expect[] = array( '19980115', '19980120', '19980312', ); $tests[] = array( 'BYYEARDAY' => array(1, 100, 200, 365), 'COUNT' => 4, ); $expect[] = array( '19971231', '19980101', '19980410', '19980719', ); $tests[] = array( 'BYYEARDAY' => array(-365, -266, -166, -1), 'COUNT' => 4, ); $expect[] = array( '19971231', '19980101', '19980410', '19980719', ); $tests[] = array( 'BYYEARDAY' => array(1, 100, 200, 365), 'BYMONTH' => array(4, 7), 'COUNT' => 4, ); $expect[] = array( '19980410', '19980719', '19990410', '19990719', ); $tests[] = array( 'BYYEARDAY' => array(-365, -266, -166, -1), 'BYMONTH' => array(4, 7), 'COUNT' => 4, ); $expect[] = array( '19980410', '19980719', '19990410', '19990719', ); $tests[] = array( 'BYWEEKNO' => array(20), ); $expect[] = array( '19980511', '19980512', '19980513', ); $tests[] = array( 'BYWEEKNO' => array(1), 'BYDAY' => array('MO'), ); $expect[] = array( '19971229', '19990104', '20000103', ); $tests[] = array( 'BYWEEKNO' => array(52), 'BYDAY' => array('SU'), ); $expect[] = array( '19971228', '19981227', '20000102', ); $tests[] = array( 'BYWEEKNO' => array(-1), 'BYDAY' => array('SU'), ); $expect[] = array( '19971228', '19990103', '20000102', ); $tests[] = array( 'BYWEEKNO' => array(53), 'BYDAY' => array('MO'), ); $expect[] = array( '19981228', '20041227', '20091228', ); $tests[] = array( 'BYHOUR' => array(6, 18), ); $expect[] = array( '19970902T060000Z', '19970902T180000Z', '19980902T060000Z', ); $tests[] = array( 'BYMINUTE' => array(15, 30), ); $expect[] = array( '19970902T001500Z', '19970902T003000Z', '19980902T001500Z', ); $tests[] = array( 'BYSECOND' => array(10, 20), ); $expect[] = array( '19970902T000010Z', '19970902T000020Z', '19980902T000010Z', ); $tests[] = array( 'BYHOUR' => array(6, 18), 'BYMINUTE' => array(15, 30), ); $expect[] = array( '19970902T061500Z', '19970902T063000Z', '19970902T181500Z', ); $tests[] = array( 'BYHOUR' => array(6, 18), 'BYSECOND' => array(10, 20), ); $expect[] = array( '19970902T060010Z', '19970902T060020Z', '19970902T180010Z', ); $tests[] = array( 'BYMINUTE' => array(15, 30), 'BYSECOND' => array(10, 20), ); $expect[] = array( '19970902T001510Z', '19970902T001520Z', '19970902T003010Z', ); $tests[] = array( 'BYHOUR' => array(6, 18), 'BYMINUTE' => array(15, 30), 'BYSECOND' => array(10, 20), ); $expect[] = array( '19970902T061510Z', '19970902T061520Z', '19970902T063010Z', ); $tests[] = array( 'BYMONTHDAY' => array(15), 'BYHOUR' => array(6, 18), 'BYSETPOS' => array(3, -3), ); $expect[] = array( '19971115T180000Z', '19980215T060000Z', '19981115T180000Z', ); $this->assertRules( array( 'FREQ' => 'YEARLY', 'COUNT' => 3, 'DTSTART' => '19970902', ), $tests, $expect); } public function testMonthlyRecurrenceRules() { $tests = array(); $expect = array(); $tests[] = array(); $expect[] = array( '19970902', '19971002', '19971102', ); $tests[] = array( 'INTERVAL' => 2, ); $expect[] = array( '19970902', '19971102', '19980102', ); $tests[] = array( 'INTERVAL' => 18, ); $expect[] = array( '19970902', '19990302', '20000902', ); $tests[] = array( 'BYMONTH' => array(1, 3), ); $expect[] = array( '19980102', '19980302', '19990102', ); $tests[] = array( 'BYMONTHDAY' => array(1, 3), ); $expect[] = array( '19970903', '19971001', '19971003', ); $tests[] = array( 'BYMONTHDAY' => array(5, 7), 'BYMONTH' => array(1, 3), ); $expect[] = array( '19980105', '19980107', '19980305', ); $tests[] = array( 'BYDAY' => array('TU', 'TH'), ); $expect[] = array( '19970902', '19970904', '19970909', ); $tests[] = array( 'BYDAY' => array('3MO'), ); $expect[] = array( '19970915', '19971020', '19971117', ); $tests[] = array( 'BYDAY' => array('1TU', '-1TH'), ); $expect[] = array( '19970902', '19970925', '19971007', ); $tests[] = array( 'BYDAY' => array('3TU', '-3TH'), ); $expect[] = array( '19970911', '19970916', '19971016', ); $tests[] = array( 'BYDAY' => array('TU', 'TH'), 'BYMONTH' => array(1, 3), ); $expect[] = array( '19980101', '19980106', '19980108', ); $tests[] = array( 'BYMONTH' => array(1, 3), 'BYDAY' => array('1TU', '-1TH'), ); $expect[] = array( '19980106', '19980129', '19980303', ); $tests[] = array( 'BYMONTH' => array(1, 3), 'BYDAY' => array('3TU', '-3TH'), ); $expect[] = array( '19980115', '19980120', '19980312', ); $tests[] = array( 'BYMONTHDAY' => array(1, 3), 'BYDAY' => array('TU', 'TH'), ); $expect[] = array( '19980101', '19980203', '19980303', ); $tests[] = array( 'BYMONTH' => array(1, 3), 'BYMONTHDAY' => array(1, 3), 'BYDAY' => array('TU', 'TH'), ); $expect[] = array( '19980101', '19980303', '20010301', ); $tests[] = array( 'BYDAY' => array('MO', 'TU', 'WE', 'TH', 'FR'), 'BYSETPOS' => array(-1), ); $expect[] = array( '19970930', '19971031', '19971128', ); $tests[] = array( 'BYDAY' => array('1MO', '1TU', '1WE', '1TH', '1FR', '-1FR'), 'BYMONTHDAY' => array(1, -1, -2), ); $expect[] = array( '19971001', '19971031', '19971201', ); $tests[] = array( 'BYDAY' => array('1MO', '1TU', '1WE', '1TH', 'FR'), 'BYMONTHDAY' => array(1, -1, -2), ); $expect[] = array( '19971001', '19971031', '19971201', ); $tests[] = array( 'BYHOUR' => array(6, 18), ); $expect[] = array( '19970902T060000Z', '19970902T180000Z', '19971002T060000Z', ); $tests[] = array( 'BYMINUTE' => array(6, 18), ); $expect[] = array( '19970902T000600Z', '19970902T001800Z', '19971002T000600Z', ); $tests[] = array( 'BYSECOND' => array(6, 18), ); $expect[] = array( '19970902T000006Z', '19970902T000018Z', '19971002T000006Z', ); $tests[] = array( 'BYMONTHDAY' => array(13, 17), 'BYHOUR' => array(6, 18), 'BYSETPOS' => array(3, -3), ); $expect[] = array( '19970913T180000Z', '19970917T060000Z', '19971013T180000Z', ); $tests[] = array( 'BYMONTHDAY' => array(13, 17), 'BYHOUR' => array(6, 18), 'BYSETPOS' => array(3, 3, -3), ); $expect[] = array( '19970913T180000Z', '19970917T060000Z', '19971013T180000Z', ); $tests[] = array( 'BYMONTHDAY' => array(13, 17), 'BYHOUR' => array(6, 18), 'BYSETPOS' => array(4, -1), ); $expect[] = array( '19970917T180000Z', '19971017T180000Z', '19971117T180000Z', ); $this->assertRules( array( 'FREQ' => 'MONTHLY', 'COUNT' => 3, 'DTSTART' => '19970902', ), $tests, $expect); } public function testDailyRecurrenceRules() { $tests = array(); $expect = array(); $tests[] = array(); $expect[] = array( '19970902', '19970903', '19970904', ); $tests[] = array( 'INTERVAL' => 2, ); $expect[] = array( '19970902', '19970904', '19970906', ); $tests[] = array( 'INTERVAL' => 92, ); $expect[] = array( '19970902', '19971203', '19980305', ); $tests[] = array( 'BYMONTH' => array(1, 3), ); $expect[] = array( '19980101', '19980102', '19980103', ); // This is testing that INTERVAL is respected in the presence of a BYMONTH // filter which skips some months. $tests[] = array( 'BYMONTH' => array(12), 'INTERVAL' => 17, ); $expect[] = array( '19971213', '19971230', '19981205', ); $tests[] = array( 'BYMONTHDAY' => array(1, 3), ); $expect[] = array( '19970903', '19971001', '19971003', ); $tests[] = array( 'BYMONTH' => array(1, 3), 'BYMONTHDAY' => array(5, 7), ); $expect[] = array( '19980105', '19980107', '19980305', ); $tests[] = array( 'BYDAY' => array('TU', 'TH'), ); $expect[] = array( '19970902', '19970904', '19970909', ); $tests[] = array( 'BYMONTH' => array(1, 3), 'BYDAY' => array('TU', 'TH'), ); $expect[] = array( '19980101', '19980106', '19980108', ); $tests[] = array( 'BYMONTHDAY' => array(1, 3), 'BYDAY' => array('TU', 'TH'), ); $expect[] = array( '19980101', '19980203', '19980303', ); $tests[] = array( 'BYMONTH' => array(1, 3), 'BYMONTHDAY' => array(1, 3), 'BYDAY' => array('TU', 'TH'), ); $expect[] = array( '19980101', '19980303', '20010301', ); $tests[] = array( 'BYHOUR' => array(6, 18), 'BYMINUTE' => array(15, 45), 'BYSETPOS' => array(3, -3), 'DTSTART' => '19970902T090000Z', ); $expect[] = array( '19970902T181500Z', '19970903T064500Z', '19970903T181500Z', ); $this->assertRules( array( 'FREQ' => 'DAILY', 'COUNT' => 3, 'DTSTART' => '19970902', ), $tests, $expect); } public function testHourlyRecurrenceRules() { $tests = array(); $expect = array(); $tests[] = array(); $expect[] = array( '19970902T090000Z', '19970902T100000Z', '19970902T110000Z', ); $tests[] = array( 'INTERVAL' => 2, ); $expect[] = array( '19970902T090000Z', '19970902T110000Z', '19970902T130000Z', ); $tests[] = array( 'INTERVAL' => 769, ); $expect[] = array( '19970902T090000Z', '19971004T100000Z', '19971105T110000Z', ); $tests[] = array( 'BYMONTH' => array(1, 3), ); $expect[] = array( '19980101T000000Z', '19980101T010000Z', '19980101T020000Z', ); $tests[] = array( 'BYMONTHDAY' => array(1, 3), ); $expect[] = array( '19970903T000000Z', '19970903T010000Z', '19970903T020000Z', ); $tests[] = array( 'BYMONTH' => array(1, 3), 'BYMONTHDAY' => array(5, 7), ); $expect[] = array( '19980105T000000Z', '19980105T010000Z', '19980105T020000Z', ); $tests[] = array( 'BYDAY' => array('TU', 'TH'), ); $expect[] = array( '19970902T090000Z', '19970902T100000Z', '19970902T110000Z', ); $tests[] = array( 'BYMONTH' => array(1, 3), 'BYDAY' => array('TU', 'TH'), ); $expect[] = array( '19980101T000000Z', '19980101T010000Z', '19980101T020000Z', ); $tests[] = array( 'BYMONTHDAY' => array(1, 3), 'BYDAY' => array('TU', 'TH'), ); $expect[] = array( '19980101T000000Z', '19980101T010000Z', '19980101T020000Z', ); $tests[] = array( 'BYMONTHDAY' => array(1, 3), 'BYMONTH' => array(1, 3), 'BYDAY' => array('TU', 'TH'), ); $expect[] = array( '19980101T000000Z', '19980101T010000Z', '19980101T020000Z', ); $tests[] = array( 'COUNT' => 4, 'BYYEARDAY' => array(1, 100, 200, 365), ); $expect[] = array( '19971231T000000Z', '19971231T010000Z', '19971231T020000Z', '19971231T030000Z', ); $tests[] = array( 'COUNT' => 4, 'BYYEARDAY' => array(-365, -266, -166, -1), ); $expect[] = array( '19971231T000000Z', '19971231T010000Z', '19971231T020000Z', '19971231T030000Z', ); $tests[] = array( 'COUNT' => 4, 'BYMONTH' => array(4, 7), 'BYYEARDAY' => array(1, 100, 200, 365), ); $expect[] = array( '19980410T000000Z', '19980410T010000Z', '19980410T020000Z', '19980410T030000Z', ); $tests[] = array( 'COUNT' => 4, 'BYMONTH' => array(4, 7), 'BYYEARDAY' => array(-365, -266, -166, -1), ); $expect[] = array( '19980410T000000Z', '19980410T010000Z', '19980410T020000Z', '19980410T030000Z', ); $tests[] = array( 'BYHOUR' => array(6, 18), ); $expect[] = array( '19970902T180000Z', '19970903T060000Z', '19970903T180000Z', ); $tests[] = array( 'BYMINUTE' => array(15, 45), 'BYSECOND' => array(15, 45), 'BYSETPOS' => array(3, -3), ); $expect[] = array( '19970902T091545Z', '19970902T094515Z', '19970902T101545Z', ); $this->assertRules( array( 'FREQ' => 'HOURLY', 'COUNT' => 3, 'DTSTART' => '19970902T090000Z', ), $tests, $expect); } public function testMinutelyRecurrenceRules() { $tests = array(); $expect = array(); $tests[] = array( ); $expect[] = array( '19970902T090000Z', '19970902T090100Z', '19970902T090200Z', ); $tests[] = array( 'INTERVAL' => 2, ); $expect[] = array( '19970902T090000Z', '19970902T090200Z', '19970902T090400Z', ); $tests[] = array( 'BYHOUR' => array(6, 18), 'BYMINUTE' => array(6, 18), 'BYSECOND' => array(6, 18), ); $expect[] = array( '19970902T180606Z', '19970902T180618Z', '19970902T181806Z', ); $tests[] = array( 'BYSECOND' => array(15, 30, 45), 'BYSETPOS' => array(3, -3), ); $expect[] = array( '19970902T090015Z', '19970902T090045Z', '19970902T090115Z', ); $this->assertRules( array( 'FREQ' => 'MINUTELY', 'COUNT' => 3, 'DTSTART' => '19970902T090000Z', ), $tests, $expect); } public function testSecondlyRecurrenceRules() { $tests = array(); $expect = array(); $tests[] = array(); $expect[] = array( '19970902T090000Z', '19970902T090001Z', '19970902T090002Z', ); $tests[] = array( 'INTERVAL' => 2, ); $expect[] = array( '19970902T090000Z', '19970902T090002Z', '19970902T090004Z', ); $tests[] = array( 'INTERVAL' => 90061, ); $expect[] = array( '19970902T090000Z', '19970903T100101Z', '19970904T110202Z', ); $tests[] = array( 'BYSECOND' => array(0), 'BYMINUTE' => array(1), 'DTSTART' => '20100322T120100Z', ); $expect[] = array( '20100322T120100Z', '20100322T130100Z', '20100322T140100Z', ); $this->assertRules( array( 'FREQ' => 'SECONDLY', 'COUNT' => 3, 'DTSTART' => '19970902T090000Z', ), $tests, $expect); } + public function testRFC5545RecurrenceRules() { + // These tests are derived from the examples in RFC5545. + $tests = array(); + $expect = array(); + + $tests[] = array( + 'FREQ' => 'DAILY', + 'COUNT' => 10, + 'DTSTART' => '19970902T090000Z', + ); + $expect[] = array( + '19970902T090000Z', + '19970903T090000Z', + '19970904T090000Z', + '19970905T090000Z', + '19970906T090000Z', + '19970907T090000Z', + '19970908T090000Z', + '19970909T090000Z', + '19970910T090000Z', + '19970911T090000Z', + ); + + $tests[] = array( + 'FREQ' => 'DAILY', + 'INTERVAL' => 2, + 'DTSTART' => '19970902T090000Z', + 'COUNT' => 5, + ); + $expect[] = array( + '19970902T090000Z', + '19970904T090000Z', + '19970906T090000Z', + '19970908T090000Z', + '19970910T090000Z', + ); + + $tests[] = array( + 'FREQ' => 'YEARLY', + 'BYMONTH' => array(1), + 'BYDAY' => array('MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU'), + 'DTSTART' => '19970902T090000Z', + 'COUNT' => 3, + ); + $expect[] = array( + '19980101T090000Z', + '19980102T090000Z', + '19980103T090000Z', + ); + + $tests[] = array( + 'FREQ' => 'MONTHLY', + 'COUNT' => 3, + 'BYDAY' => array('1FR'), + 'DTSTART' => '19970902T090000Z', + ); + $expect[] = array( + '19970905T090000Z', + '19971003T090000Z', + '19971107T090000Z', + ); + + $tests[] = array( + 'FREQ' => 'MONTHLY', + 'INTERVAL' => 2, + 'COUNT' => 5, + 'BYDAY' => array('1SU', '-1SU'), + 'DTSTART' => '19970902T090000Z', + ); + $expect[] = array( + '19970907T090000Z', + '19970928T090000Z', + '19971102T090000Z', + '19971130T090000Z', + '19980104T090000Z', + ); + + $tests[] = array( + 'FREQ' => 'MONTHLY', + 'COUNT' => 6, + 'BYDAY' => array('-2MO'), + 'DTSTART' => '19970902T090000Z', + ); + $expect[] = array( + '19970922T090000Z', + '19971020T090000Z', + '19971117T090000Z', + '19971222T090000Z', + '19980119T090000Z', + '19980216T090000Z', + ); + + $tests[] = array( + 'FREQ' => 'MONTHLY', + 'COUNT' => 6, + 'BYMONTHDAY' => array(-3), + 'DTSTART' => '19970902T090000Z', + ); + $expect[] = array( + '19970928T090000Z', + '19971029T090000Z', + '19971128T090000Z', + '19971229T090000Z', + '19980129T090000Z', + '19980226T090000Z', + ); + + $tests[] = array( + 'FREQ' => 'MONTHLY', + 'COUNT' => 5, + 'BYMONTHDAY' => array(2, 15), + 'DTSTART' => '19970902T090000Z', + ); + $expect[] = array( + '19970902T090000Z', + '19970915T090000Z', + '19971002T090000Z', + '19971015T090000Z', + '19971102T090000Z', + ); + + $tests[] = array( + 'FREQ' => 'MONTHLY', + 'COUNT' => 5, + 'BYMONTHDAY' => array(-1, 1), + 'DTSTART' => '19970902T090000Z', + ); + $expect[] = array( + '19970930T090000Z', + '19971001T090000Z', + '19971031T090000Z', + '19971101T090000Z', + '19971130T090000Z', + ); + + $tests[] = array( + 'FREQ' => 'MONTHLY', + 'COUNT' => 7, + 'INTERVAL' => 18, + 'BYMONTHDAY' => array(10, 11, 12, 13, 14, 15), + 'DTSTART' => '19970902T090000Z', + ); + $expect[] = array( + '19970910T090000Z', + '19970911T090000Z', + '19970912T090000Z', + '19970913T090000Z', + '19970914T090000Z', + '19970915T090000Z', + '19990310T090000Z', + ); + + $tests[] = array( + 'FREQ' => 'MONTHLY', + 'COUNT' => 6, + 'INTERVAL' => 2, + 'BYDAY' => array('TU'), + 'DTSTART' => '19970902T090000Z', + ); + $expect[] = array( + '19970902T090000Z', + '19970909T090000Z', + '19970916T090000Z', + '19970923T090000Z', + '19970930T090000Z', + '19971104T090000Z', + ); + + $tests[] = array( + 'FREQ' => 'YEARLY', + 'COUNT' => 10, + 'BYMONTH' => array(6, 7), + 'DTSTART' => '19970610T090000Z', + ); + $expect[] = array( + '19970610T090000Z', + '19970710T090000Z', + '19980610T090000Z', + '19980710T090000Z', + '19990610T090000Z', + '19990710T090000Z', + '20000610T090000Z', + '20000710T090000Z', + '20010610T090000Z', + '20010710T090000Z', + ); + + $tests[] = array( + 'FREQ' => 'YEARLY', + 'COUNT' => 4, + 'INTERVAL' => 3, + 'BYYEARDAY' => array(1, 100, 200), + 'DTSTART' => '19970101T090000Z', + ); + $expect[] = array( + '19970101T090000Z', + '19970410T090000Z', + '19970719T090000Z', + '20000101T090000Z', + ); + + $tests[] = array( + 'FREQ' => 'YEARLY', + 'COUNT' => 3, + 'BYDAY' => array('20MO'), + 'DTSTART' => '19970519T090000Z', + ); + $expect[] = array( + '19970519T090000Z', + '19980518T090000Z', + '19990517T090000Z', + ); + + $tests[] = array( + 'FREQ' => 'YEARLY', + 'COUNT' => 3, + 'BYWEEKNO' => array(20), + 'BYDAY' => array('MO'), + 'DTSTART' => '19970512T090000Z', + ); + $expect[] = array( + '19970512T090000Z', + '19980511T090000Z', + '19990517T090000Z', + ); + + $tests[] = array( + 'FREQ' => 'YEARLY', + 'BYDAY' => array('TH'), + 'BYMONTH' => array(3), + 'DTSTART' => '19970313T090000Z', + 'COUNT' => 5, + ); + $expect[] = array( + '19970313T090000Z', + '19970320T090000Z', + '19970327T090000Z', + '19980305T090000Z', + '19980312T090000Z', + ); + + $tests[] = array( + 'FREQ' => 'YEARLY', + 'BYDAY' => array('TH'), + 'BYMONTH' => array(6, 7, 8), + 'DTSTART' => '19970101T090000Z', + 'COUNT' => 15, + ); + $expect[] = array( + '19970605T090000Z', + '19970612T090000Z', + '19970619T090000Z', + '19970626T090000Z', + '19970703T090000Z', + '19970710T090000Z', + '19970717T090000Z', + '19970724T090000Z', + '19970731T090000Z', + '19970807T090000Z', + '19970814T090000Z', + '19970821T090000Z', + '19970828T090000Z', + '19980604T090000Z', + '19980611T090000Z', + ); + + $tests[] = array( + 'FREQ' => 'YEARLY', + 'BYDAY' => array('FR'), + 'BYMONTHDAY' => array(13), + 'COUNT' => 4, + 'DTSTART' => '19970902T090000Z', + ); + $expect[] = array( + '19980213T090000Z', + '19980313T090000Z', + '19981113T090000Z', + '19990813T090000Z', + ); + + $tests[] = array( + 'FREQ' => 'MONTHLY', + 'BYDAY' => array('SA'), + 'BYMONTHDAY' => array(7, 8, 9, 10, 11, 12, 13), + 'COUNT' => 10, + 'DTSTART' => '19970902T090000Z', + ); + $expect[] = array( + '19970913T090000Z', + '19971011T090000Z', + '19971108T090000Z', + '19971213T090000Z', + '19980110T090000Z', + '19980207T090000Z', + '19980307T090000Z', + '19980411T090000Z', + '19980509T090000Z', + '19980613T090000Z', + ); + + $tests[] = array( + 'FREQ' => 'YEARLY', + 'INTERVAL' => 4, + 'BYMONTH' => array(11), + 'BYDAY' => array('TU'), + 'BYMONTHDAY' => array(2, 3, 4, 5, 6, 7, 8), + 'COUNT' => 6, + 'DTSTART' => '19961105T090000Z', + ); + $expect[] = array( + '19961105T090000Z', + '20001107T090000Z', + '20041102T090000Z', + '20081104T090000Z', + '20121106T090000Z', + '20161108T090000Z', + ); + + $tests[] = array( + 'FREQ' => 'MONTHLY', + 'BYDAY' => array('TU', 'WE', 'TH'), + 'BYSETPOS' => array(3), + 'COUNT' => 3, + 'DTSTART' => '19970904T090000Z', + ); + $expect[] = array( + '19970904T090000Z', + '19971007T090000Z', + '19971106T090000Z', + ); + + $tests[] = array( + 'FREQ' => 'MONTHLY', + 'BYDAY' => array('MO', 'TU', 'WE', 'TH', 'FR'), + 'BYSETPOS' => array(-2), + 'COUNT' => 3, + 'DTSTART' => '19970929T090000Z', + ); + $expect[] = array( + '19970929T090000Z', + '19971030T090000Z', + '19971127T090000Z', + ); + + $tests[] = array( + 'FREQ' => 'HOURLY', + 'INTERVAL' => 3, + 'DTSTART' => '19970929T090000Z', + 'COUNT' => 3, + ); + $expect[] = array( + '19970929T090000Z', + '19970929T120000Z', + '19970929T150000Z', + ); + + $tests[] = array( + 'FREQ' => 'MINUTELY', + 'INTERVAL' => 15, + 'COUNT' => 6, + 'DTSTART' => '19970902T090000Z', + ); + $expect[] = array( + '19970902T090000Z', + '19970902T091500Z', + '19970902T093000Z', + '19970902T094500Z', + '19970902T100000Z', + '19970902T101500Z', + ); + + $tests[] = array( + 'FREQ' => 'MINUTELY', + 'INTERVAL' => 90, + 'COUNT' => 4, + 'DTSTART' => '19970902T090000Z', + ); + $expect[] = array( + '19970902T090000Z', + '19970902T103000Z', + '19970902T120000Z', + '19970902T133000Z', + ); + + + $this->assertRules(array(), $tests, $expect); + } + + private function assertRules(array $defaults, array $tests, array $expect) { foreach ($tests as $key => $test) { $options = $test + $defaults; $start = PhutilCalendarAbsoluteDateTime::newFromISO8601( $options['DTSTART']); $rrule = id(new PhutilCalendarRecurrenceRule()) ->setStartDateTime($start) ->setFrequency($options['FREQ']); $interval = idx($options, 'INTERVAL'); if ($interval) { $rrule->setInterval($interval); } $by_day = idx($options, 'BYDAY'); if ($by_day) { $rrule->setByDay($by_day); } $by_month = idx($options, 'BYMONTH'); if ($by_month) { $rrule->setByMonth($by_month); } $by_monthday = idx($options, 'BYMONTHDAY'); if ($by_monthday) { $rrule->setByMonthDay($by_monthday); } $by_yearday = idx($options, 'BYYEARDAY'); if ($by_yearday) { $rrule->setByYearDay($by_yearday); } $by_weekno = idx($options, 'BYWEEKNO'); if ($by_weekno) { $rrule->setByWeekNumber($by_weekno); } $by_hour = idx($options, 'BYHOUR'); if ($by_hour) { $rrule->setByHour($by_hour); } $by_minute = idx($options, 'BYMINUTE'); if ($by_minute) { $rrule->setByMinute($by_minute); } $by_second = idx($options, 'BYSECOND'); if ($by_second) { $rrule->setBySecond($by_second); } $by_setpos = idx($options, 'BYSETPOS'); if ($by_setpos) { $rrule->setBySetPosition($by_setpos); } $set = id(new PhutilCalendarRecurrenceSet()) ->addSource($rrule); $result = $set->getEventsBetween(null, null, $options['COUNT']); $this->assertEqual( $expect[$key], mpull($result, 'getISO8601')); } } }