diff --git a/CHANGELOG.md b/CHANGELOG.md index 967f1d3..ff455ad 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Fixed +- Fix valid rules wrongly detected as not producing results, and cut short after MAX_CYCLES [#78](https://github.com/rlanvin/php-rrule/issues/78) - Fix `RRule::createFromRfcString` not detecting RSet properly if the rule was lowercase - [internal] Replace static variables by array constants (permitted since PHP 5.6). Shouldn't break backward compatibility unless you were doing weird things with this lib in the first place. diff --git a/src/RRule.php b/src/RRule.php index 9e0159e..5f867b7 100755 --- a/src/RRule.php +++ b/src/RRule.php @@ -1398,7 +1398,7 @@ class RRule implements RRuleInterface } } - $max_cycles = self::REPEAT_CYCLES[$this->freq <= self::DAILY ? $this->freq : self::DAILY]; + $max_cycles = self::MAX_CYCLES[$this->freq <= self::DAILY ? $this->freq : self::DAILY]; for ($i = 0; $i < $max_cycles; $i++) { // 1. get an array of all days in the next interval (day, month, week, etc.) // we filter out from this array all days that do not match the BYXXX conditions @@ -1514,7 +1514,7 @@ class RRule implements RRuleInterface } } - // 2. loop, generate a valid date, and return the result (fake "yield") + // 2. loop, generate a valid date, and yield the result // at the same time, we check the end condition and return null if // we need to stop if ($this->bysetpos && $timeset) { @@ -1535,12 +1535,12 @@ class RRule implements RRuleInterface $total += 1; $this->cache[] = clone $occurrence; yield clone $occurrence; // yield + $i = 0; // reset the max cycles counter, since we yieled a result } } } else { // normal loop, without BYSETPOS - // while ( ($yearday = current($dayset)) !== false ) { foreach ($dayset as $yearday) { $occurrence = \DateTime::createFromFormat( 'Y z', @@ -1566,10 +1566,9 @@ class RRule implements RRuleInterface $total += 1; $this->cache[] = clone $occurrence; yield clone $occurrence; // yield + $i = 0; // reset the max cycles counter, since we yieled a result } } - // reset($timeset); - // next($dayset); } } @@ -1621,7 +1620,7 @@ class RRule implements RRuleInterface } $found = false; - for ($j = 0; $j < self::REPEAT_CYCLES[self::HOURLY]; $j++) { + for ($j = 0; $j < self::MAX_CYCLES[self::HOURLY]; $j++) { $hour += $this->interval; $div = (int) ($hour / 24); $mod = $hour % 24; @@ -1648,7 +1647,7 @@ class RRule implements RRuleInterface } $found = false; - for ($j = 0; $j < self::REPEAT_CYCLES[self::MINUTELY]; $j++) { + for ($j = 0; $j < self::MAX_CYCLES[self::MINUTELY]; $j++) { $minute += $this->interval; $div = (int) ($minute / 60); $mod = $minute % 60; @@ -1682,7 +1681,7 @@ class RRule implements RRuleInterface } $found = false; - for ($j = 0; $j < self::REPEAT_CYCLES[self::SECONDLY]; $j++) { + for ($j = 0; $j < self::MAX_CYCLES[self::SECONDLY]; $j++) { $second += $this->interval; $div = (int) ($second / 60); $mod = $second % 60; @@ -1865,7 +1864,7 @@ class RRule implements RRuleInterface * going to be a problem anytime soon, so at the moment I use the 28 years * cycle. */ - const REPEAT_CYCLES = [ + const MAX_CYCLES = [ // self::YEARLY => 400, // self::MONTHLY => 4800, // self::WEEKLY => 20871, diff --git a/tests/RRuleTest.php b/tests/RRuleTest.php index 42a40e7..1f1f0a9 100755 --- a/tests/RRuleTest.php +++ b/tests/RRuleTest.php @@ -1793,6 +1793,28 @@ class RRuleTest extends TestCase } } + public function rulesBeyondMaxCycles() + { + return [ + ['yearly' => 'YEARLY', 30], + ['monthly' => 'MONTHLY', 400], + ['weekly' => 'WEEKLY', 1500], + ['daily' => 'DAILY', 11000], + ['hourly' => 'HOURLY', 30], + ['minutely' => 'MINUTELY', 1500] + ]; + } + + /** + * @dataProvider rulesBeyondMaxCycles + */ + public function testMaxCyclesDoesntKickInIfTheRuleProduceOccurrences($frequency, $count) + { + // see https://github.com/rlanvin/php-rrule/issues/78 + $rrule = new RRule(['FREQ' => $frequency, 'COUNT' => $count]); + $this->assertEquals($count, $rrule->count()); + } + /////////////////////////////////////////////////////////////////////////////// // GetOccurrences