From 3d72c9efaf12aa72931031ee463e7d22fc8e13f1 Mon Sep 17 00:00:00 2001 From: rlanvin Date: Sun, 13 Jan 2019 13:40:21 +0000 Subject: [PATCH] Add helpers methods Ref #60 --- CHANGELOG.md | 6 ++ README.md | 15 --- src/RRule.php | 108 +--------------------- src/RRuleInterface.php | 49 +++++++++- src/RRuleTrait.php | 203 +++++++++++++++++++++++++++++++++++++++++ src/RSet.php | 46 +--------- tests/RRuleTest.php | 122 ++++++++++++++++++++++++- tests/RSetTest.php | 110 +++++++++++++++++++--- 8 files changed, 477 insertions(+), 182 deletions(-) create mode 100755 src/RRuleTrait.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 4abc5ea..947015b 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ ### Added - New option `custom_path` to `humanReadable()` to use custom translation files [#56](https://github.com/rlanvin/php-rrule/issues/56) +- New helpers methods [#60](https://github.com/rlanvin/php-rrule/issues/60) + - `getOccurrencesBefore` + - `getOccurrencesAfter` + - `getNthOccurrencesBefore` + - `getNthOccurrencesAfter` + - `getNthOccurrencesFrom` ## [1.6.3] - 2019-01-13 diff --git a/README.md b/README.md index c4e68f5..8d99e8f 100755 --- a/README.md +++ b/README.md @@ -41,27 +41,12 @@ The recommended way is to install the lib [through Composer](http://getcomposer. Simply run `composer require rlanvin/php-rrule` for it to be automatically installed and included in your `composer.json`. -Alternatively, just add this to your `composer.json` file and then run `composer install` (you can replace `2.*` by any version selector, or even `dev-master` for the latest development version). - -```JSON -{ - "require": { - "rlanvin/php-rrule": "2.*" - } -} -``` - Now you can use the autoloader, and you will have access to the library: ```php require 'vendor/autoload.php'; ``` -### Alternative method (not recommended) - -- Download [the latest release](https://github.com/rlanvin/php-rrule/releases/latest) -- Put the files in a folder that is autoloaded, or `include` or `require` them - ## Documentation Complete documentation is available in [the wiki](https://github.com/rlanvin/php-rrule/wiki). diff --git a/src/RRule.php b/src/RRule.php index 28e1dd9..c21e61c 100755 --- a/src/RRule.php +++ b/src/RRule.php @@ -91,6 +91,8 @@ function is_leap_year($year) */ class RRule implements RRuleInterface { + use RRuleTrait; + const SECONDLY = 7; const MINUTELY = 6; const HOURLY = 5; @@ -734,80 +736,6 @@ class RRule implements RRuleInterface return ! $this->count && ! $this->until; } - /** - * Return all the occurrences in an array of \DateTime. - * - * @param int $limit Limit the resultset to n occurrences (0, null or false = everything) - * @return array An array of \DateTime objects - */ - public function getOccurrences($limit = null) - { - if ( ! $limit && $this->isInfinite() ) { - throw new \LogicException('Cannot get all occurrences of an infinite recurrence rule.'); - } - - // cached version already computed - $iterator = $this; - if ( $this->total !== null ) { - $iterator = $this->cache; - } - - $res = array(); - $n = 0; - foreach ( $iterator as $occurrence ) { - $res[] = clone $occurrence; // we have to clone because DateTime is not immutable - $n += 1; - if ( $limit && $n >= $limit ) { - break; - } - } - return $res; - } - - /** - * Return all the ocurrences after a date, before a date, or between two dates. - * - * @param mixed $begin Can be null to return all occurrences before $end - * @param mixed $end Can be null to return all occurrences after $begin - * @param int $limit Limit the resultset to n occurrences (0, null or false = everything) - * @return array An array of \DateTime objects - */ - public function getOccurrencesBetween($begin, $end, $limit = null) - { - if ( $begin !== null ) { - $begin = self::parseDate($begin); - } - - if ( $end !== null ) { - $end = self::parseDate($end); - } - elseif ( ! $limit && $this->isInfinite() ) { - throw new \LogicException('Cannot get all occurrences of an infinite recurrence rule.'); - } - - $iterator = $this; - if ( $this->total !== null ) { - $iterator = $this->cache; - } - - $res = array(); - $n = 0; - foreach ( $iterator as $occurrence ) { - if ( $begin !== null && $occurrence < $begin ) { - continue; - } - if ( $end !== null && $occurrence > $end ) { - break; - } - $res[] = clone $occurrence; - $n += 1; - if ( $limit && $n >= $limit ) { - break; - } - } - return $res; - } - /** * Return true if $date is an occurrence. * @@ -1130,38 +1058,6 @@ class RRule implements RRuleInterface // Internal methods // where all the magic happens - /** - * Convert any date into a DateTime object. - * - * @param mixed $date - * @return \DateTime - * - * @throws \InvalidArgumentException on error - */ - static public function parseDate($date) - { - // DateTimeInterface is only on PHP 5.5+, and includes DateTimeImmutable - if ( ! $date instanceof \DateTime && ! $date instanceof \DateTimeInterface ) { - try { - if ( is_integer($date) ) { - $date = \DateTime::createFromFormat('U',$date); - $date->setTimezone(new \DateTimeZone('UTC')); // default is +00:00 (see issue #15) - } - else { - $date = new \DateTime($date); - } - } catch (\Exception $e) { - throw new \InvalidArgumentException( - "Failed to parse the date" - ); - } - } - else { - $date = clone $date; // avoid reference problems - } - return $date; - } - /** * Return an array of days of the year (numbered from 0 to 365) * of the current timeframe (year, month, week, day) containing the current date diff --git a/src/RRuleInterface.php b/src/RRuleInterface.php index 0c8af1b..1cd0a8f 100755 --- a/src/RRuleInterface.php +++ b/src/RRuleInterface.php @@ -29,11 +29,58 @@ interface RRuleInterface extends \ArrayAccess, \Countable, \IteratorAggregate * * @param mixed $begin Can be null to return all occurrences before $end * @param mixed $end Can be null to return all occurrences after $begin - * @param int $limit Limit the resultset to n occurrences (0, null or false = everything) + * @param int|null $limit Limit the resultset to n occurrences (0, null or false = everything) * @return array An array of \DateTime objects */ public function getOccurrencesBetween($begin, $end, $limit = null); + /** + * Return all the occurrences after a date. + * + * @param mixed $date + * @param bool $inclusive Whether or not to include $date (if date is an occurrence) + * @param int|null $limit Limit the resultset to n occurrences (0, null or false = everything) + * @return array + */ + public function getOccurrencesAfter($date, $inclusive = false, $limit = null); + + /** + * Return the Nth occurrences after a date (non inclusive) + * + * @param mixed $date + * @param int $index The index (starts at 1) + * @return DateTimeInterface|null + */ + public function getNthOccurrenceAfter($date, $index); + + /** + * Return all the occurrences before a date. + * + * @param mixed $date + * @param bool $inclusive Whether or not to include $date (if date is an occurrence) + * @param int|null $limit Limit the resultset to n occurrences (0, null or false = everything) + * @return array + */ + public function getOccurrencesBefore($date, $inclusive = false, $limit = null); + + /** + * Return the Nth occurrences before a date (non inclusive) + * + * @param mixed $date + * @param int $index The index (starts at 1) + * @return DateTimeInterface|null + */ + public function getNthOccurrenceBefore($date, $index); + + /** + * Return the Nth occurrences before or after a date. + * + * @param mixed $date + * @param int $index 0 returns the date, positive integer returns index in the future, negative in the past + * @return DateTimeInterface|null + */ + public function getNthOccurrenceFrom($date, $index); + /** * Return true if $date is an occurrence. * diff --git a/src/RRuleTrait.php b/src/RRuleTrait.php new file mode 100755 index 0000000..6603bc8 --- /dev/null +++ b/src/RRuleTrait.php @@ -0,0 +1,203 @@ + + * @link https://github.com/rlanvin/php-rrule + */ + +namespace RRule; + +/** + * Implement the common methods of the RRuleInterface used by RRule and RSet + * + * @internal + */ +trait RRuleTrait +{ + /** + * Return all the occurrences in an array of \DateTime. + * + * @param int $limit Limit the resultset to n occurrences (0, null or false = everything) + * @return array An array of \DateTime objects + */ + public function getOccurrences($limit = null) + { + if ( ! $limit && $this->isInfinite() ) { + throw new \LogicException('Cannot get all occurrences of an infinite recurrence rule.'); + } + if ( $limit !== null && $limit !== false && $limit < 0 ) { + throw new \InvalidArgumentException('$limit cannot be negative'); + } + + // cached version already computed + $iterator = $this; + if ( $this->total !== null ) { + $iterator = $this->cache; + } + + $res = array(); + $n = 0; + foreach ( $iterator as $occurrence ) { + $res[] = clone $occurrence; // we have to clone because DateTime is not immutable + $n += 1; + if ( $limit && $n >= $limit ) { + break; + } + } + return $res; + } + + /** + * Return all the ocurrences after a date, before a date, or between two dates. + * + * @param mixed $begin Can be null to return all occurrences before $end + * @param mixed $end Can be null to return all occurrences after $begin + * @param int $limit Limit the resultset to n occurrences (0, null or false = everything) + * @return array An array of \DateTime objects + */ + public function getOccurrencesBetween($begin, $end, $limit = null) + { + if ( $begin !== null ) { + $begin = self::parseDate($begin); + } + + if ( $end !== null ) { + $end = self::parseDate($end); + } + elseif ( ! $limit && $this->isInfinite() ) { + throw new \LogicException('Cannot get all occurrences of an infinite recurrence rule.'); + } + + if ( $limit !== null && $limit !== false && $limit < 0 ) { + throw new \InvalidArgumentException('$limit cannot be negative'); + } + + $iterator = $this; + if ( $this->total !== null ) { + $iterator = $this->cache; + } + + $res = array(); + $n = 0; + foreach ( $iterator as $occurrence ) { + if ( $begin !== null && $occurrence < $begin ) { + continue; + } + if ( $end !== null && $occurrence > $end ) { + break; + } + $res[] = clone $occurrence; + $n += 1; + if ( $limit && $n >= $limit ) { + break; + } + } + return $res; + } + + public function getOccurrencesAfter($date, $inclusive = false, $limit = null) + { + if ( $inclusive || ! $this->occursAt($date) ) { + return $this->getOccurrencesBetween($date, null, $limit); + } + + $limit += 1; + $occurrences = $this->getOccurrencesBetween($date, null, $limit); + return array_slice($occurrences, 1); + } + + public function getNthOccurrenceAfter($date, $index) + { + if ( $index <= 0 ) { + throw new \InvalidArgumentException("Index must be a positive integer"); + } + + $occurrences = $this->getOccurrencesAfter($date, false, $index); + + return isset($occurrences[$index-1]) ? $occurrences[$index-1] : null; + } + + public function getOccurrencesBefore($date, $inclusive = false, $limit = null) + { + // we need to get everything + $occurrences = $this->getOccurrencesBetween(null, $date); + + if ( ! $inclusive && $this->occursAt($date) ) { + array_pop($occurrences); + } + + // the limit is counted from $date + if ( $limit ) { + $occurrences = array_slice($occurrences, -1 * $limit); + } + + return $occurrences; + } + + public function getNthOccurrenceBefore($date, $index) + { + if ( $index <= 0 ) { + throw new \InvalidArgumentException("Index must be a positive integer"); + } + + $occurrences = $this->getOccurrencesBefore($date, false, $index); + + if ( sizeof($occurrences) < $index ) { + return null; + } + + return $occurrences[0]; + } + + public function getNthOccurrenceFrom($date, $index) + { + if ( ! is_numeric($index) ) { + throw new \InvalidArgumentException('Malformed index (must be a numeric)'); + } + + if ( $index == 0 ) { + return $this->occursAt($date) ? self::parseDate($date) : null; + } + elseif ( $index > 0 ) { + return $this->getNthOccurrenceAfter($date, $index); + } + else { + return $this->getNthOccurrenceBefore($date, -1*$index); + } + } + /** + * Convert any date into a DateTime object. + * + * @param mixed $date + * @return \DateTime + * + * @throws \InvalidArgumentException on error + */ + static public function parseDate($date) + { + // DateTimeInterface is only on PHP 5.5+, and includes DateTimeImmutable + if ( ! $date instanceof \DateTime && ! $date instanceof \DateTimeInterface ) { + try { + if ( is_integer($date) ) { + $date = \DateTime::createFromFormat('U',$date); + $date->setTimezone(new \DateTimeZone('UTC')); // default is +00:00 (see issue #15) + } + else { + $date = new \DateTime($date); + } + } catch (\Exception $e) { + throw new \InvalidArgumentException( + "Failed to parse the date" + ); + } + } + else { + $date = clone $date; // avoid reference problems + } + return $date; + } +} \ No newline at end of file diff --git a/src/RSet.php b/src/RSet.php index c47a075..d8b9a90 100755 --- a/src/RSet.php +++ b/src/RSet.php @@ -16,6 +16,8 @@ namespace RRule; */ class RSet implements RRuleInterface { + use RRuleTrait; + /** * @var array List of RDATE (single dates) */ @@ -348,50 +350,6 @@ class RSet implements RRuleInterface return $res; } - /** - * Return all the ocurrences after a date, before a date, or between two dates. - * - * @param mixed $begin Can be null to return all occurrences before $end - * @param mixed $end Can be null to return all occurrences after $begin - * @param int $limit Limit the resultset to n occurrences (0, null or false = everything) - * @return array An array of \DateTime objects - */ - public function getOccurrencesBetween($begin, $end, $limit = null) - { - if ( $begin !== null ) { - $begin = RRule::parseDate($begin); - } - - if ( $end !== null ) { - $end = RRule::parseDate($end); - } - elseif ( ! $limit && $this->isInfinite() ) { - throw new \LogicException('Cannot get all occurrences of an infinite recurrence rule.'); - } - - $iterator = $this; - if ( $this->total !== null ) { - $iterator = $this->cache; - } - - $res = array(); - $n = 0; - foreach ( $iterator as $occurrence ) { - if ( $begin !== null && $occurrence < $begin ) { - continue; - } - if ( $end !== null && $occurrence > $end ) { - break; - } - $res[] = clone $occurrence; - $n += 1; - if ( $limit && $n >= $limit ) { - break; - } - } - return $res; - } - /** * Return true if $date is an occurrence. * diff --git a/tests/RRuleTest.php b/tests/RRuleTest.php index 9f90241..d21b15e 100755 --- a/tests/RRuleTest.php +++ b/tests/RRuleTest.php @@ -1777,17 +1777,46 @@ class RRuleTest extends TestCase $rrule->getOccurrences(); } - public function testGetOccurrencesBetween() + public function testGetOccurrencesNegativeLimit() { + $this->expectException(\InvalidArgumentException::class); $rrule = new RRule(array( 'FREQ' => 'DAILY', 'DTSTART' => '2017-01-01' )); + $rrule->getOccurrences(-1); + } - $this->assertCount(1, $rrule->getOccurrencesBetween('2017-01-01', null, 1)); - $this->assertCount(1, $rrule->getOccurrencesBetween('2017-02-01', '2017-12-31', 1)); - $this->assertEquals(array(date_create('2017-02-01')), $rrule->getOccurrencesBetween('2017-02-01', '2017-12-31', 1)); - $this->assertCount(5, $rrule->getOccurrencesBetween('2017-01-01', null, 5)); + public function occurrencesBetween() + { + return [ + ["DTSTART:20170101\nRRULE:FREQ=DAILY", '2017-01-01', null, 1, [date_create('2017-01-01')]], + ["DTSTART:20170101\nRRULE:FREQ=DAILY", '2017-02-01', '2017-12-31', 1, [date_create('2017-02-01')]], + ["DTSTART:20170101\nRRULE:FREQ=DAILY", '2017-01-01', null, 5, [ + date_create('2017-01-01'), + date_create('2017-01-02'), + date_create('2017-01-03'), + date_create('2017-01-04'), + date_create('2017-01-05'), + ]], + ["DTSTART:20170101\nRRULE:FREQ=DAILY", '2017-01-01', '2017-01-05', null, [ + date_create('2017-01-01'), + date_create('2017-01-02'), + date_create('2017-01-03'), + date_create('2017-01-04'), + date_create('2017-01-05'), + ]], + ]; + } + + /** + * @dataProvider occurrencesBetween + */ + public function testGetOccurrencesBetween($rule, $begin, $end, $limit, $expected) + { + $rrule = new RRule($rule); + + $this->assertEquals($expected, $rrule->getOccurrencesBetween($begin, $end, $limit)); } /** @@ -1803,6 +1832,89 @@ class RRuleTest extends TestCase $rrule->getOccurrencesBetween('2017-01-01', null); } + public function testGetOccurrencesBetweenNegativeLimit() + { + $this->expectException(\InvalidArgumentException::class); + $rrule = new RRule(array( + 'FREQ' => 'DAILY', + 'DTSTART' => '2017-01-01' + )); + $rrule->getOccurrencesBetween('2017-01-01', '2018-01-01', -1); + } + + public function occurrencesAfter() + { + return [ + ["DTSTART:20170101\nRRULE:FREQ=DAILY", '2017-02-01', false, 2, [date_create('2017-02-02'),date_create('2017-02-03')]], + ["DTSTART:20170101\nRRULE:FREQ=DAILY", '2017-02-01', true, 2, [date_create('2017-02-01'),date_create('2017-02-02')]], + ["DTSTART:20170101\nRRULE:FREQ=DAILY;INTERVAL=2", '2017-01-02', true, 2, [date_create('2017-01-03'),date_create('2017-01-05')]], + ["DTSTART:20170101\nRRULE:FREQ=DAILY;INTERVAL=2", '2017-01-02', false, 2, [date_create('2017-01-03'),date_create('2017-01-05')]], + ]; + } + + /** + * @dataProvider occurrencesAfter + */ + public function testGetOccurrencesAfter($rrule, $date, $inclusive, $limit, $expected) + { + $rrule = new RRule($rrule); + $occurrences = $rrule->getOccurrencesAfter($date, $inclusive, $limit); + $this->assertEquals($expected, $occurrences); + } + + public function occurrencesBefore() + { + return [ + ["DTSTART:20170101\nRRULE:FREQ=DAILY", '2017-02-01', true, 2, [date_create('2017-01-31'),date_create('2017-02-01')]], + ["DTSTART:20170101\nRRULE:FREQ=DAILY", '2017-02-01', false, 2, [date_create('2017-01-30'),date_create('2017-01-31')]], + ["DTSTART:20170101\nRRULE:FREQ=DAILY", '2017-01-02', false, null, [date_create('2017-01-01')]], + ["DTSTART:20170101\nRRULE:FREQ=DAILY", '2017-01-02', false, 5, [date_create('2017-01-01')]], + ["DTSTART:20170101\nRRULE:FREQ=DAILY;INTERVAL=2", '2017-01-04', true, 2, [date_create('2017-01-01'),date_create('2017-01-03')]], + ["DTSTART:20170101\nRRULE:FREQ=DAILY;INTERVAL=2", '2017-01-04', false, 2, [date_create('2017-01-01'),date_create('2017-01-03')]], + ["DTSTART:20170101\nRRULE:FREQ=DAILY;INTERVAL=2", '2017-01-02', false, null, [date_create('2017-01-01')]], + ["DTSTART:20170101\nRRULE:FREQ=DAILY;INTERVAL=2", '2017-01-02', false, 5, [date_create('2017-01-01')]], + ]; + } + /** + * @dataProvider occurrencesBefore + */ + public function testGetOccurrencesBefore($rrule, $date, $inclusive, $limit, $expected) + { + $rrule = new RRule($rrule); + $occurrences = $rrule->getOccurrencesBefore($date, $inclusive, $limit); + $this->assertEquals($expected, $occurrences); + } + + public function nthOccurrences() + { + return [ + ["DTSTART:20170101\nRRULE:FREQ=DAILY", '2017-01-01', 0, date_create('2017-01-01')], + ["DTSTART:20170101\nRRULE:FREQ=DAILY", '2017-01-01', 1, date_create('2017-01-02')], + ["DTSTART:20170101\nRRULE:FREQ=DAILY;INTERVAL=2", '2017-01-01', 2, date_create('2017-01-05')], + ["DTSTART:20170101\nRRULE:FREQ=DAILY;INTERVAL=2", '2017-01-02', 0, null], + ["DTSTART:20170101\nRRULE:FREQ=DAILY", '2017-01-01', -1, null], + ["DTSTART:20170101\nRRULE:FREQ=DAILY", '2017-01-10', -1, date_create('2017-01-09')], + ["DTSTART:20170101\nRRULE:FREQ=DAILY", '2017-01-10', -2, date_create('2017-01-08')], + ["DTSTART:20170101\nRRULE:FREQ=DAILY;INTERVAL=2", '2017-01-11', -2, date_create('2017-01-07')], + ["DTSTART:20170101\nRRULE:FREQ=DAILY;INTERVAL=2", '2017-01-10', -2, date_create('2017-01-07')], + + ["DTSTART:20170101\nRRULE:FREQ=DAILY;COUNT=2", '2017-01-01', 3, null], + ["DTSTART:20170101\nRRULE:FREQ=DAILY;UNTIL=20170102", '2017-01-01', 3, null], + + ]; + } + + /** + * @dataProvider nthOccurrences + */ + public function testGetNthOccurrenceFrom($rrule, $date, $index, $result) + { + $rrule = new RRule($rrule); + $occurrence = $rrule->getNthOccurrenceFrom($date, $index); + $this->assertEquals($result, $occurrence); + } + + /////////////////////////////////////////////////////////////////////////////// // RFC Strings diff --git a/tests/RSetTest.php b/tests/RSetTest.php index be41623..5a4112f 100755 --- a/tests/RSetTest.php +++ b/tests/RSetTest.php @@ -468,19 +468,34 @@ class RSetTest extends TestCase ))); $rset->getOccurrences(); } - - public function testGetOccurrencesBetween() + public function occurrencesBetween() { - $rset = new RSet(); - $rset->addRRule(new RRule(array( - 'FREQ' => 'DAILY', - 'DTSTART' => '2017-01-01' - ))); + return [ + [(new RSet())->addRRule("DTSTART:20170101\nRRULE:FREQ=DAILY"), '2017-01-01', null, 1, [date_create('2017-01-01')]], + [(new RSet())->addRRule("DTSTART:20170101\nRRULE:FREQ=DAILY"), '2017-02-01', '2017-12-31', 1, [date_create('2017-02-01')]], + [(new RSet())->addRRule("DTSTART:20170101\nRRULE:FREQ=DAILY"), '2017-01-01', null, 5, [ + date_create('2017-01-01'), + date_create('2017-01-02'), + date_create('2017-01-03'), + date_create('2017-01-04'), + date_create('2017-01-05'), + ]], + [(new RSet())->addRRule("DTSTART:20170101\nRRULE:FREQ=DAILY"), '2017-01-01', '2017-01-05', null, [ + date_create('2017-01-01'), + date_create('2017-01-02'), + date_create('2017-01-03'), + date_create('2017-01-04'), + date_create('2017-01-05'), + ]], + ]; + } - $this->assertCount(1, $rset->getOccurrencesBetween('2017-01-01', null, 1)); - $this->assertCount(1, $rset->getOccurrencesBetween('2017-02-01', '2017-12-31', 1)); - $this->assertEquals(array(date_create('2017-02-01')), $rset->getOccurrencesBetween('2017-02-01', '2017-12-31', 1)); - $this->assertCount(5, $rset->getOccurrencesBetween('2017-01-01', null, 5)); + /** + * @dataProvider occurrencesBetween + */ + public function testGetOccurrencesBetween(RSet $rset, $begin, $end, $limit, $expected) + { + $this->assertEquals($expected, $rset->getOccurrencesBetween($begin, $end, $limit)); } /** @@ -497,6 +512,79 @@ class RSetTest extends TestCase $rset->getOccurrencesBetween('2017-01-01', null); } + public function occurrencesAfter() + { + return [ + [ + (new RSet()) + ->addRRule("DTSTART:20170101\nRRULE:FREQ=DAILY;INTERVAL=2") + ->addRRule("DTSTART:20170102\nRRULE:FREQ=DAILY;INTERVAL=2") + , '2017-02-01', false, 2, [date_create('2017-02-02'),date_create('2017-02-03')]], + [(new RSet())->addRRule("DTSTART:20170101\nRRULE:FREQ=DAILY"), '2017-02-01', true, 2, [date_create('2017-02-01'),date_create('2017-02-02')]], + [(new RSet())->addRRule("DTSTART:20170101\nRRULE:FREQ=DAILY;INTERVAL=2"), '2017-01-02', true, 2, [date_create('2017-01-03'),date_create('2017-01-05')]], + [(new RSet())->addRRule("DTSTART:20170101\nRRULE:FREQ=DAILY;INTERVAL=2"), '2017-01-02', false, 2, [date_create('2017-01-03'),date_create('2017-01-05')]], + ]; + } + + /** + * @dataProvider occurrencesAfter + */ + public function testGetOccurrencesAfter(RSet $rset, $date, $inclusive, $limit, $expected) + { + $occurrences = $rset->getOccurrencesAfter($date, $inclusive, $limit); + $this->assertEquals($expected, $occurrences); + } + + public function occurrencesBefore() + { + return [ + [(new RSet())->addRRule("DTSTART:20170101\nRRULE:FREQ=DAILY"), '2017-02-01', true, 2, [date_create('2017-01-31'),date_create('2017-02-01')]], + [(new RSet())->addRRule("DTSTART:20170101\nRRULE:FREQ=DAILY"), '2017-02-01', false, 2, [date_create('2017-01-30'),date_create('2017-01-31')]], + [(new RSet())->addRRule("DTSTART:20170101\nRRULE:FREQ=DAILY"), '2017-01-02', false, null, [date_create('2017-01-01')]], + [(new RSet())->addRRule("DTSTART:20170101\nRRULE:FREQ=DAILY"), '2017-01-02', false, 5, [date_create('2017-01-01')]], + [(new RSet())->addRRule("DTSTART:20170101\nRRULE:FREQ=DAILY;INTERVAL=2"), '2017-01-04', true, 2, [date_create('2017-01-01'),date_create('2017-01-03')]], + [(new RSet())->addRRule("DTSTART:20170101\nRRULE:FREQ=DAILY;INTERVAL=2"), '2017-01-04', false, 2, [date_create('2017-01-01'),date_create('2017-01-03')]], + [(new RSet())->addRRule("DTSTART:20170101\nRRULE:FREQ=DAILY;INTERVAL=2"), '2017-01-02', false, null, [date_create('2017-01-01')]], + [(new RSet())->addRRule("DTSTART:20170101\nRRULE:FREQ=DAILY;INTERVAL=2"), '2017-01-02', false, 5, [date_create('2017-01-01')]], + ]; + } + /** + * @dataProvider occurrencesBefore + */ + public function testGetOccurrencesBefore(RSet $rset, $date, $inclusive, $limit, $expected) + { + $occurrences = $rset->getOccurrencesBefore($date, $inclusive, $limit); + $this->assertEquals($expected, $occurrences); + } + + public function nthOccurrences() + { + return [ + [(new RSet())->addRRule("DTSTART:20170101\nRRULE:FREQ=DAILY"), '2017-01-01', 0, date_create('2017-01-01')], + [(new RSet())->addRRule("DTSTART:20170101\nRRULE:FREQ=DAILY"), '2017-01-01', 1, date_create('2017-01-02')], + [(new RSet())->addRRule("DTSTART:20170101\nRRULE:FREQ=DAILY;INTERVAL=2"), '2017-01-01', 2, date_create('2017-01-05')], + [(new RSet())->addRRule("DTSTART:20170101\nRRULE:FREQ=DAILY;INTERVAL=2"), '2017-01-02', 0, null], + [(new RSet())->addRRule("DTSTART:20170101\nRRULE:FREQ=DAILY"), '2017-01-01', -1, null], + [(new RSet())->addRRule("DTSTART:20170101\nRRULE:FREQ=DAILY"), '2017-01-10', -1, date_create('2017-01-09')], + [(new RSet())->addRRule("DTSTART:20170101\nRRULE:FREQ=DAILY"), '2017-01-10', -2, date_create('2017-01-08')], + [(new RSet())->addRRule("DTSTART:20170101\nRRULE:FREQ=DAILY;INTERVAL=2"), '2017-01-11', -2, date_create('2017-01-07')], + [(new RSet())->addRRule("DTSTART:20170101\nRRULE:FREQ=DAILY;INTERVAL=2"), '2017-01-10', -2, date_create('2017-01-07')], + + [(new RSet())->addRRule("DTSTART:20170101\nRRULE:FREQ=DAILY;COUNT=2"), '2017-01-01', 3, null], + [(new RSet())->addRRule("DTSTART:20170101\nRRULE:FREQ=DAILY;UNTIL=20170102"), '2017-01-01', 3, null], + + ]; + } + + /** + * @dataProvider nthOccurrences + */ + public function testGetNthOccurrenceFrom(RSet $rset, $date, $index, $result) + { + $occurrence = $rset->getNthOccurrenceFrom($date, $index); + $this->assertEquals($result, $occurrence); + } + /////////////////////////////////////////////////////////////////////////////// // RFC Strings