Skip to content

Commit c68668b

Browse files
committed
Fix incorrect calculation from partially filled cache
When resuming calculation from a partially filled cache, if the time is the last second of the day, this causes the generator to consider the wrong frame of reference and therefore generate the wrong occurrences. Ref #160
1 parent bef9568 commit c68668b

File tree

2 files changed

+43
-12
lines changed

2 files changed

+43
-12
lines changed

src/RRule.php

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1362,16 +1362,8 @@ public function getIterator()
13621362

13631363
if ($occurrence) {
13641364
$dtstart = clone $occurrence; // since DateTime is not immutable, clone to avoid any problem
1365-
// so we skip the last occurrence of the cache
1366-
if ($this->freq === self::SECONDLY) {
1367-
$dtstart = $dtstart->modify('+'.$this->interval.'second');
1368-
}
1369-
else {
1370-
$dtstart = $dtstart->modify('+1second');
1371-
}
13721365
}
1373-
1374-
if ($dtstart === null) {
1366+
elseif ($dtstart === null) {
13751367
$dtstart = clone $this->dtstart;
13761368
}
13771369

@@ -1412,8 +1404,21 @@ public function getIterator()
14121404
}
14131405
}
14141406

1407+
// if we restarted the calculation from cache, we know that dtstart has already been yielded
1408+
// so we can skip ahead to the next second to avoid the same date to be yielded again
1409+
// we need to do that after the correct frame as been set (see https://github.com/rlanvin/php-rrule/issues/160)
1410+
if ($occurrence) {
1411+
if ($this->freq === self::SECONDLY) {
1412+
$dtstart = $dtstart->modify('+'.$this->interval.'second');
1413+
}
1414+
else {
1415+
$dtstart = $dtstart->modify('+1second');
1416+
}
1417+
}
1418+
14151419
$max_cycles = self::MAX_CYCLES[$this->freq <= self::DAILY ? $this->freq : self::DAILY];
14161420
for ($i = 0; $i < $max_cycles; $i++) {
1421+
14171422
// 1. get an array of all days in the next interval (day, month, week, etc.)
14181423
// we filter out from this array all days that do not match the BYXXX conditions
14191424
// to speed things up, we use days of the year (day numbers) instead of date
@@ -1546,6 +1551,7 @@ public function getIterator()
15461551
$this->total = $total;
15471552
return;
15481553
}
1554+
15491555
$total += 1;
15501556
$this->cache[] = clone $occurrence;
15511557
yield clone $occurrence; // yield
@@ -1576,6 +1582,7 @@ public function getIterator()
15761582
$this->total = $total;
15771583
return;
15781584
}
1585+
15791586
$total += 1;
15801587
$this->cache[] = clone $occurrence;
15811588
yield clone $occurrence; // yield

tests/RRuleTest.php

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -248,15 +248,15 @@ public function testYearly($rule, $occurrences)
248248
$this->assertEquals($occurrences, $rule->getOccurrences());
249249
$this->assertEquals($occurrences, $rule->getOccurrences(), 'Cached version');
250250
foreach ($occurrences as $date) {
251-
$this->assertTrue($rule->occursAt($date), $date->format('r').'in cached version');
251+
$this->assertTrue($rule->occursAt($date), $date->format('r').' in cached version');
252252
}
253253
$rule->clearCache();
254254
foreach ($occurrences as $date) {
255-
$this->assertTrue($rule->occursAt($date), $date->format('r').'in uncached version');
255+
$this->assertTrue($rule->occursAt($date), $date->format('r').' in uncached version');
256256
}
257257
$rule->clearCache();
258258
for ($i = 0; $i < count($occurrences); $i++) {
259-
$this->assertEquals($rule[$i], $occurrences[$i], 'array access uncached');
259+
$this->assertEquals($rule[$i], $occurrences[$i], ' array access uncached');
260260
}
261261
}
262262

@@ -1835,6 +1835,30 @@ public function testDST()
18351835
$this->assertSame('2022-11-06T01:00:00-05:00 CDT 1667714400', $rrule[1]->format('c T U'));
18361836
}
18371837

1838+
public function testResumeFromPartiallyFilledCache()
1839+
{
1840+
// https://github.com/rlanvin/php-rrule/issues/160
1841+
$rrule = new \RRule\RRule([
1842+
'DTSTART' => new DateTime('2023-03-31 23:59:59.000000'),
1843+
'FREQ' => 'MONTHLY',
1844+
'INTERVAL' => '12',
1845+
'WKST' => 'MO',
1846+
'COUNT' => 3
1847+
]);
1848+
1849+
// Break on first loop during first iterator use.
1850+
foreach ($rrule as $occurrence) {
1851+
break;
1852+
}
1853+
1854+
// Print first 3 occurrences (cache used).
1855+
$this->assertEquals([
1856+
date_create('2023-03-31 23:59:59'),
1857+
date_create('2024-03-31 23:59:59'),
1858+
date_create('2025-03-31 23:59:59')
1859+
],$rrule->getOccurrences());
1860+
}
1861+
18381862
///////////////////////////////////////////////////////////////////////////////
18391863
// GetOccurrences
18401864

0 commit comments

Comments
 (0)