diff --git a/src/RRule.php b/src/RRule.php index 2ed166d..9e0159e 100755 --- a/src/RRule.php +++ b/src/RRule.php @@ -878,6 +878,7 @@ class RRule implements RRuleInterface return false; } break; + default: throw new \Exception('Unimplemented frequency'); } diff --git a/src/RRuleTrait.php b/src/RRuleTrait.php index b8bd53c..a68c6bc 100755 --- a/src/RRuleTrait.php +++ b/src/RRuleTrait.php @@ -189,9 +189,13 @@ trait RRuleTrait else { $date = new \DateTime($date); } - } catch (\Exception $e) { + } catch (\Exception $e) { // PHP 5.6 throw new \InvalidArgumentException( - "Failed to parse the date" + "Failed to parse the date ({$e->getMessage()})" + ); + } catch (\Throwable $e) { // PHP 7+ + throw new \InvalidArgumentException( + "Failed to parse the date ({$e->getMessage()})" ); } } diff --git a/tests/RRuleTest.php b/tests/RRuleTest.php index b04e593..42a40e7 100755 --- a/tests/RRuleTest.php +++ b/tests/RRuleTest.php @@ -21,6 +21,7 @@ class RRuleTest extends TestCase array(array('FOOBAR' => 'DAILY')), array(array('FREQ' => 'foobar')), + 'Invalid integer frequency' => [['FREQ' => 42]], array(array('FREQ' => 'DAILY', 'INTERVAL' => -1)), array(array('FREQ' => 'DAILY', 'INTERVAL' => 1.5)), array(array('FREQ' => 'DAILY', 'UNTIL' => 'foobar')), @@ -70,6 +71,8 @@ class RRuleTest extends TestCase array(array('FREQ' => 'YEARLY', 'BYWEEKNO' => 0)), array(array('FREQ' => 'YEARLY', 'BYWEEKNO' => 1.5)), + // The BYWEEKNO rule part MUST NOT be used when the FREQ rule part is set to anything other than YEARLY. + 'BYWEEKNO with FREQ not yearly' => [['FREQ' => 'DAILY', 'BYWEEKNO' => 1]], array(array('FREQ' => 'MONTHLY', 'BYHOUR' => -1)), array(array('FREQ' => 'MONTHLY', 'BYHOUR' => 1.5)), @@ -81,7 +84,11 @@ class RRuleTest extends TestCase array(array('FREQ' => 'MONTHLY', 'BYSECOND' => -1)), array(array('FREQ' => 'MONTHLY', 'BYSECOND' => 1.5)), - array(array('FREQ' => 'MONTHLY', 'BYSECOND' => 61)) + array(array('FREQ' => 'MONTHLY', 'BYSECOND' => 61)), + + 'Invalid WKST' => [['FREQ' => 'DAILY', 'WKST' => 'XX']], + + 'Invalid DTSTART (invalid date)' => [['FREQ' => 'DAILY', 'DTSTART' => new stdClass()]] ); } @@ -256,8 +263,12 @@ class RRuleTest extends TestCase date_create('1997-09-02'),date_create('1997-10-02'),date_create('1997-11-02'))), array(array('INTERVAL'=>2),array( date_create('1997-09-02'),date_create('1997-11-02'),date_create('1998-01-02'))), - array(array('INTERVAL'=>18),array( + '1.5 years' => array(array('INTERVAL'=>18),array( date_create('1997-09-02'),date_create('1999-03-02'),date_create('2000-09-02'))), + 'exactly 2 years in December' => [ + ['INTERVAL'=> 24, 'DTSTART' => '1997-12-01'], + [date_create('1997-12-01'),date_create('1999-12-01'),date_create('2001-12-01')] + ], array(array('BYMONTH' => '1,3'),array( date_create('1998-01-02'),date_create('1998-03-02'),date_create('1999-01-02'))), array(array('BYMONTHDAY' => '1,3'),array( @@ -1728,13 +1739,25 @@ class RRuleTest extends TestCase array('FREQ' => 'YEARLY', 'DTSTART' => '1999-09-02', 'INTERVAL' => 2), array('2000-09-02', '2002-09-02') ), + 'byyearday' => [ + ['FREQ' => 'YEARLY', 'DTSTART' => '1999-09-02', 'byyearday' => 1], + ['1999-09-02'] + ], + 'byweekno' => [ + ['FREQ' => 'YEARLY', 'DTSTART' => '2015-07-01', 'BYWEEKNO' => 1], + ['2015-07-01'] + ], array( array('FREQ' => 'MONTHLY', 'DTSTART' => '1999-09-02', 'INTERVAL' => 2), array('1999-10-02', '1999-12-02') ), + 'bymonth' => [ + ['FREQ' => 'MONTHLY', 'DTSTART' => '1999-09-02', 'bymonth' => 1], + ['1999-10-02', '1999-12-02'] + ], array( array('FREQ' => 'WEEKLY', 'DTSTART' => '2015-07-01', 'INTERVAL' => 2), - array('2015-07-02', '2015-07-07 23:59:59', '2015-07-08 00:00:01') + array('2015-07-02', '2015-07-07 23:59:59', '2015-07-08 00:00:01', '2015-07-08') ), array( array('FREQ' => 'DAILY', 'DTSTART' => '2015-07-01', 'INTERVAL' => 2), @@ -1758,6 +1781,7 @@ class RRuleTest extends TestCase ), ); } + /** * @dataProvider notOccurrences */ @@ -1938,6 +1962,26 @@ class RRuleTest extends TestCase $this->assertEquals($result, $occurrence); } + public function testGetNthOccurrenceFromInvalidIndex() + { + $rrule = new RRule(['FREQ' => 'DAILY']); + $this->expectException('InvalidArgumentException'); + $rrule->getNthOccurrenceFrom(date_create('2017-01-09'), []); + } + + public function testGetNthOccurrenceBeforeInvalidIndex() + { + $rrule = new RRule(['FREQ' => 'DAILY']); + $this->expectException('InvalidArgumentException'); + $rrule->getNthOccurrenceBefore(date_create('2017-01-09'), -1); + } + + public function testGetNthOccurrenceAfterInvalidIndex() + { + $rrule = new RRule(['FREQ' => 'DAILY']); + $this->expectException('InvalidArgumentException'); + $rrule->getNthOccurrenceAfter(date_create('2017-01-09'), -1); + } /////////////////////////////////////////////////////////////////////////////// // RFC Strings @@ -2098,6 +2142,8 @@ class RRuleTest extends TestCase } } + + public function testRfcStringParserWithDtStart() { $rrule = new RRule('RRULE:FREQ=YEARLY'); @@ -2344,6 +2390,15 @@ class RRuleTest extends TestCase ), "DTSTART;TZID=Australia/Sydney:20150701T090000\nRRULE:FREQ=SECONDLY;BYMINUTE=0;BYHOUR=0" ), + 'with a value as an array' => [ + array( + 'FREQ' => RRule::SECONDLY, + 'BYMINUTE' => 0, + 'BYHOUR' => [0,1], + 'DTSTART' => date_create('2015-07-01 09:00:00', new DateTimeZone('Australia/Sydney')) + ), + "DTSTART;TZID=Australia/Sydney:20150701T090000\nRRULE:FREQ=SECONDLY;BYMINUTE=0;BYHOUR=0,1" + ], ); } @@ -2356,6 +2411,14 @@ class RRuleTest extends TestCase $this->assertEquals($expected_str, $rule->rfcString()); } + public function testMagicStringMethod() + { + $rule = new RRule('DTSTART;TZID=America/New_York:19970901T090000 + RRULE:FREQ=HOURLY;UNTIL=19971224T000000Z;WKST=SU;BYDAY=MO,WE,FR;BYMONTH=1;BYHOUR=1'); + + $this->assertEquals($rule->rfcString(), (string) $rule); + } + /////////////////////////////////////////////////////////////////////////////// // RFC Factory method @@ -2386,14 +2449,18 @@ class RRuleTest extends TestCase array("\nDTSTART:19970512\nRRULE:FREQ=YEARLY;COUNT=3\n\n", '\RRule\RRule' ), - // no DTSTART - array("RRULE:FREQ=YEARLY;COUNT=3", + 'no DTSTART' => [ + "RRULE:FREQ=YEARLY;COUNT=3", '\RRule\RRule' - ), + ], array( "DTSTART;TZID=America/New_York:19970901T090000\nRRULE:FREQ=DAILY\nEXRULE:FREQ=YEARLY\nEXDATE;TZID=America/New_York:19970902T090000", '\RRule\RSet' ), + 'no rrule' => [ + 'EXRULE:FREQ=DAILY;COUNT=3', + \RRule\RRule::class + ], 'lowercase rrule' => [ "rrule:freq=yearly;count=3", "\RRule\RRule" @@ -2559,6 +2626,26 @@ class RRuleTest extends TestCase /////////////////////////////////////////////////////////////////////////////// // Other tests + public function invalidConstructorParameters() + { + return [ + [new stdClass, null], + [true, null], + [1, null], + [4.2, null], + 'dtstart optional parameter only for string rules' => [['FREQ' => 'DAILY'], new DateTime()] + ]; + } + + /** + * @dataProvider invalidConstructorParameters + */ + public function testConstructorDoesntAcceptInvalidTypes($parts, $dtstart) + { + $this->expectException(\InvalidArgumentException::class); + new RRule($dtstart, $dtstart); + } + public function testIsFinite() { $rrule = new RRule(array( @@ -2687,6 +2774,15 @@ class RRuleTest extends TestCase $this->assertEquals(10, count($rrule)); } + public function testCannotCountInfinite() + { + $rrule = new RRule(array( + 'freq' => 'yearly' + )); + $this->expectException('LogicException'); + count($rrule); + } + public function testOffsetExists() { $rrule = new RRule(array( @@ -2719,6 +2815,30 @@ class RRuleTest extends TestCase $this->assertEquals(null, $rrule['4']); } + public function testOffsetSetUnsupported() + { + $rrule = new RRule(array( + 'freq' => 'daily', + 'count' => 3, + 'byday' => 'TU,TH', + 'dtstart' => '2007-01-01' + )); + $this->expectException('LogicException'); + $rrule[] = 'blah'; + } + + public function testOffsetUnsetUnsupported() + { + $rrule = new RRule(array( + 'freq' => 'daily', + 'count' => 3, + 'byday' => 'TU,TH', + 'dtstart' => '2007-01-01' + )); + $this->expectException('LogicException'); + unset($rrule[0]); + } + public function illegalOffsets() { return array( diff --git a/tests/RSetTest.php b/tests/RSetTest.php index 4026c0e..33bf37a 100755 --- a/tests/RSetTest.php +++ b/tests/RSetTest.php @@ -322,6 +322,18 @@ class RSetTest extends TestCase $this->assertEquals(3, count($rset)); } + public function testCannotCountInfinite() + { + $rset = new RSet(); + $rset->addRRule(array( + 'FREQ' => 'YEARLY', + 'BYDAY' => 'TU, TH', + 'DTSTART' => date_create('1997-09-02 09:00') + )); + $this->expectException('LogicException'); + count($rset); + } + public function testOffsetExists() { $rset = new RSet(); @@ -366,6 +378,37 @@ class RSetTest extends TestCase $this->assertEquals(null, $rset['3']); } + public function testOffsetSetUnsupported() + { + $rset = new RSet(); + $rset->addRRule(array( + 'FREQ' => 'YEARLY', + 'COUNT' => 6, + 'BYDAY' => 'TU, TH', + 'DTSTART' => date_create('1997-09-02 09:00:00') + )); + $rset->addExdate('1997-09-04 09:00:00'); + $rset->addExdate('1997-09-11 09:00:00'); + $rset->addExdate('1997-09-18 09:00:00'); + $this->expectException('LogicException'); + $rset[] = 'blah'; + } + + public function testOffsetUnsetUnsupported() + { + $rset = new RSet(); + $rset->addRRule(array( + 'FREQ' => 'YEARLY', + 'COUNT' => 6, + 'BYDAY' => 'TU, TH', + 'DTSTART' => date_create('1997-09-02 09:00:00') + )); + $rset->addExdate('1997-09-04 09:00:00'); + $rset->addExdate('1997-09-11 09:00:00'); + $rset->addExdate('1997-09-18 09:00:00'); + $this->expectException('LogicException'); + unset($rset[0]); + } public function illegalOffsets() {