From 90e31549ceccf8c61baf9931976e9bb8ea628dc2 Mon Sep 17 00:00:00 2001 From: Cyril van Schreven Date: Tue, 16 Apr 2024 18:48:02 +0200 Subject: [PATCH 1/3] Fix dst-transition-leap spreading to subsequent events --- lib/Recur/RRuleIterator.php | 137 +++++++++++++++++++--- tests/VObject/Recur/RRuleIteratorTest.php | 60 ++++++++++ 2 files changed, 182 insertions(+), 15 deletions(-) diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index ecf1affeb..bd1009298 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -362,6 +362,14 @@ private function jumpForward(\DateTimeInterface $dt): void */ protected float $counter = 0; + /** + * The leap offset in seconds, by iteration. + * + * An occurrence may add an offset, for example if it falls on a DST change. + * It is accounted for in this variable. It should be removed on the next iteration. + */ + protected array $leapOffset = []; + /** * Which weekdays to recur. * @@ -432,7 +440,7 @@ private function jumpForward(\DateTimeInterface $dt): void */ protected function nextHourly($amount = 1): void { - $this->currentDate = $this->currentDate->modify('+'.$amount * $this->interval.' hours'); + $this->currentDate = $this->getNextIterationDateTime($this->currentDate, modify: '+'.$amount * $this->interval.' hours'); } /** @@ -441,7 +449,7 @@ protected function nextHourly($amount = 1): void protected function nextDaily($amount = 1): void { if (!$this->byHour && !$this->byDay) { - $this->currentDate = $this->currentDate->modify('+'.$amount * $this->interval.' days'); + $this->currentDate = $this->getNextIterationDateTime($this->currentDate, modify: '+'.$amount * $this->interval.' days'); return; } @@ -465,13 +473,13 @@ protected function nextDaily($amount = 1): void if ($this->byHour) { if ('23' == $this->currentDate->format('G')) { // to obey the interval rule - $this->currentDate = $this->currentDate->modify('+'.(($amount * $this->interval) - 1).' days'); + $this->currentDate = $this->getNextIterationDateTime($this->currentDate, modify: '+'.(($amount * $this->interval) - 1).' days'); $amount = 1; } - $this->currentDate = $this->currentDate->modify('+1 hours'); + $this->currentDate = $this->getNextIterationDateTime($this->currentDate, modify: '+1 hours'); } else { - $this->currentDate = $this->currentDate->modify('+'.($amount * $this->interval).' days'); + $this->currentDate = $this->getNextIterationDateTime($this->currentDate, modify: '+'.($amount * $this->interval).' days'); $amount = 1; } @@ -502,7 +510,7 @@ protected function nextDaily($amount = 1): void protected function nextWeekly($amount = 1): void { if (!$this->byHour && !$this->byDay) { - $this->currentDate = $this->currentDate->modify('+'.($amount * $this->interval).' weeks'); + $this->currentDate = $this->getNextIterationDateTime($this->currentDate, modify: '+'.($amount * $this->interval).' weeks'); return; } @@ -522,9 +530,9 @@ protected function nextWeekly($amount = 1): void do { if ($this->byHour) { - $this->currentDate = $this->currentDate->modify('+1 hours'); + $this->currentDate = $this->getNextIterationDateTime($this->currentDate, modify: '+1 hours'); } else { - $this->currentDate = $this->currentDate->modify('+1 days'); + $this->currentDate = $this->getNextIterationDateTime($this->currentDate, modify: '+1 days'); } // Current day of the week @@ -535,12 +543,12 @@ protected function nextWeekly($amount = 1): void // We need to roll over to the next week if ($currentDay === $firstDay && (!$this->byHour || '0' == $currentHour)) { - $this->currentDate = $this->currentDate->modify('+'.(($amount * $this->interval) - 1).' weeks'); + $this->currentDate = $this->getNextIterationDateTime($this->currentDate, modify: '+'.(($amount * $this->interval) - 1).' weeks'); $amount = 1; // We need to go to the first day of this week, but only if we // are not already on this first day of this week. if ($this->currentDate->format('w') != $firstDay) { - $this->currentDate = $this->currentDate->modify('last '.$this->dayNames[$this->dayMap[$this->weekStart]]); + $this->currentDate = $this->getNextIterationDateTime($this->currentDate, modify: 'last '.$this->dayNames[$this->dayMap[$this->weekStart]]); } } @@ -564,13 +572,13 @@ protected function nextMonthly($amount = 1): void // occur to the next month. We Must skip these invalid // entries. if ($currentDayOfMonth < 29) { - $this->currentDate = $this->currentDate->modify('+'.($amount * $this->interval).' months'); + $this->currentDate = $this->getNextIterationDateTime($this->currentDate, modify: '+'.($amount * $this->interval).' months'); } else { $increase = $amount - 1; do { ++$increase; $tempDate = clone $this->currentDate; - $tempDate = $tempDate->modify('+ '.($this->interval * $increase).' months'); + $tempDate = $this->getNextIterationDateTime($tempDate, modify: '+ '.($this->interval * $increase).' months'); } while ($tempDate->format('j') != $currentDayOfMonth); $this->currentDate = $tempDate; } @@ -615,7 +623,7 @@ protected function nextMonthly($amount = 1): void 1 ); // end of workaround - $this->currentDate = $this->currentDate->modify('+ '.($amount * $this->interval).' months'); + $this->currentDate = $this->getNextIterationDateTime($this->currentDate, modify: '+ '.($amount * $this->interval).' months'); $amount = 1; // This goes to 0 because we need to start counting at the @@ -676,7 +684,7 @@ protected function nextYearly($amount = 1): void // 400. (1800, 1900, 2100). So we just rely on the datetime // functions instead. $nextDate = clone $this->currentDate; - $nextDate = $nextDate->modify('+ '.($this->interval * $counter).' years'); + $nextDate = $this->getNextIterationDateTime($nextDate, modify: '+ '.($this->interval * $counter).' years'); } while (2 != $nextDate->format('n')); $this->currentDate = $nextDate; @@ -767,7 +775,7 @@ protected function nextYearly($amount = 1): void } // The easiest form - $this->currentDate = $this->currentDate->modify('+'.($amount * $this->interval).' years'); + $this->currentDate = $this->getNextIterationDateTime($this->currentDate, modify: '+'.($amount * $this->interval).' years'); return; } @@ -1230,4 +1238,103 @@ protected function getMonths(): array return $recurrenceMonths; } + + private function getNextIterationDateTime(\DateTimeImmutable|\DateTime $dateTime, string $modify): \DateTimeImmutable|\DateTime + { + $startTs = $dateTime->getTimestamp(); + $initialDateTime = clone $dateTime; + $modifiedDateTime = $dateTime->modify($modify); + + $this->detectAndSaveDstOffset( + $initialDateTime, + $modifiedDateTime, + $modify, + ); + + $modifiedDateTime = $this->revertPastOffset($modifiedDateTime); + + return $modifiedDateTime; + } + + private function detectAndSaveDstOffset( + \DateTimeImmutable|\DateTime $initialDateTime, + \DateTimeImmutable|\DateTime $modifiedDateTime, + string $modify, + ) { + $dstTransitionLeap = $this->getDstTransitionLeap($initialDateTime, $modifiedDateTime, $modify); + if (!is_null($dstTransitionLeap)) { + $this->leapOffset[$this->counter] = ($this->leapOffset[$this->counter] ?? 0) + $dstTransitionLeap; + } + + } + + private function getDstTransitionLeap( + \DateTimeImmutable|\DateTime $initialDateTime, + \DateTimeImmutable|\DateTime $modifiedDateTime, + string $modify, + ): ?int { + $modifiedDateInterval = $this->substractDates($modifiedDateTime, $initialDateTime); + $modifyInterval = \DateInterval::createFromDateString($modify); + $leap = $this->substractIntervals($modifiedDateInterval, $modifyInterval); + + if ($this->isDstTransitionLeap($leap)) { + return 3_600 * $leap->h; + } + + return null; + } + + private function isDstTransitionLeap(\DateInterval $leap): bool + { + return $leap->y === 0 && $leap->m === 0 && $leap->d === 0 && $leap->h > 0 && $leap->i === 0 && $leap->s === 0; + } + + private function revertPastOffset(\DateTimeImmutable|\DateTime $dateTime): \DateTimeImmutable|\DateTime + { + if (!is_numeric($this->counter)) { + return $dateTime; + } + + $previousIterationLeapOffset = $this->leapOffset[$this->counter - 1] ?? null; + + if (!is_int($previousIterationLeapOffset)) { + return $dateTime; + } + + $modifiedDateTime = $dateTime->modify("- $previousIterationLeapOffset seconds"); + + unset($this->leapOffset[$this->counter - 1]); + + return $modifiedDateTime; + } + + private function substractIntervals(\DateInterval $dateIntervalOne, \DateInterval $dateIntervalTwo): \DateInterval + { + $yearsDiff = $dateIntervalOne->y - $dateIntervalTwo->y; + $monthsDiff = $dateIntervalOne->m - $dateIntervalTwo->m; + $daysDiff = $dateIntervalOne->d - $dateIntervalTwo->d; + $hoursDiff = $dateIntervalOne->h - $dateIntervalTwo->h; + $minutesDiff = $dateIntervalOne->i - $dateIntervalTwo->i; + $secondsDiff = $dateIntervalOne->s - $dateIntervalTwo->s; + + return \DateInterval::createFromDateString( + "$yearsDiff years $monthsDiff months $daysDiff days $hoursDiff hours $minutesDiff minutes $secondsDiff seconds", + ); + } + + private function substractDates( + \DateTimeImmutable|\DateTime $dateIntervalOne, + \DateTimeImmutable|\DateTime $dateIntervalTwo, + ): \DateInterval { + $yearsDiff = $dateIntervalOne->format('y') - $dateIntervalTwo->format('y'); + $monthsDiff = $dateIntervalOne->format('m') - $dateIntervalTwo->format('m'); + $daysDiff = $dateIntervalOne->format('d') - $dateIntervalTwo->format('d'); + $hoursDiff = $dateIntervalOne->format('h') - $dateIntervalTwo->format('h'); + $minutesDiff = $dateIntervalOne->format('i') - $dateIntervalTwo->format('i'); + $secondsDiff = $dateIntervalOne->format('s') - $dateIntervalTwo->format('s'); + + return \DateInterval::createFromDateString( + "$yearsDiff years $monthsDiff months $daysDiff days $hoursDiff hours $minutesDiff minutes $secondsDiff seconds", + ); + } } diff --git a/tests/VObject/Recur/RRuleIteratorTest.php b/tests/VObject/Recur/RRuleIteratorTest.php index 75222f217..bfe3d36c5 100644 --- a/tests/VObject/Recur/RRuleIteratorTest.php +++ b/tests/VObject/Recur/RRuleIteratorTest.php @@ -48,6 +48,66 @@ public function testDaily(): void ); } + /** + * @dataProvider dstTransitionProvider + */ + public function testRecurrenceWithDstTransition(string $start, array $expected): void + { + $this->parse( + rule:'FREQ=DAILY;INTERVAL=1;COUNT=5', + start: $start, + expected: $expected, + expectedFreq: 'daily', + expectedCount: 5, + expectedInterval: 1, + tz: 'Europe/Zurich', + ); + } + + public function dstTransitionProvider(): iterable + { + yield 'On transition start' => [ + 'Start' => '2023-03-24 02:00:00', + 'Expected' => [ + '2023-03-24 02:00:00', + '2023-03-25 02:00:00', + '2023-03-26 03:00:00', + '2023-03-27 02:00:00', + '2023-03-28 02:00:00', + ], + ]; + yield 'During transition' => [ + 'Start' => '2023-03-24 02:15:00', + 'Expected' => [ + '2023-03-24 02:15:00', + '2023-03-25 02:15:00', + '2023-03-26 03:15:00', + '2023-03-27 02:15:00', + '2023-03-28 02:15:00', + ], + ]; + yield 'On transition end' => [ + 'Start' => '2023-03-24 03:00:00', + 'Expected' => [ + '2023-03-24 03:00:00', + '2023-03-25 03:00:00', + '2023-03-26 03:00:00', + '2023-03-27 03:00:00', + '2023-03-28 03:00:00', + ], + ]; + yield 'After transition end' => [ + 'Start' => '2023-03-24 03:15:00', + 'Expected' => [ + '2023-03-24 03:15:00', + '2023-03-25 03:15:00', + '2023-03-26 03:15:00', + '2023-03-27 03:15:00', + '2023-03-28 03:15:00', + ], + ]; + } + public function testDailyByDayByHour(): void { $this->parse( From 19d0ed4f2a35a7d7fa57c1213ae97c8f3f5965dd Mon Sep 17 00:00:00 2001 From: Cyril van Schreven Date: Mon, 22 Apr 2024 11:09:33 +0200 Subject: [PATCH 2/3] Get rid of named parameters, get rid multi-type parameters, and update timezone guesser test --- lib/Recur/RRuleIterator.php | 52 +++++++++++------------ tests/VObject/Recur/RRuleIteratorTest.php | 16 ++++--- tests/VObject/TimeZoneUtilTest.php | 4 +- 3 files changed, 37 insertions(+), 35 deletions(-) diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index bd1009298..f987e7929 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -440,7 +440,7 @@ private function jumpForward(\DateTimeInterface $dt): void */ protected function nextHourly($amount = 1): void { - $this->currentDate = $this->getNextIterationDateTime($this->currentDate, modify: '+'.$amount * $this->interval.' hours'); + $this->currentDate = $this->getNextIterationDateTime($this->currentDate, '+'.$amount * $this->interval.' hours'); } /** @@ -449,7 +449,7 @@ protected function nextHourly($amount = 1): void protected function nextDaily($amount = 1): void { if (!$this->byHour && !$this->byDay) { - $this->currentDate = $this->getNextIterationDateTime($this->currentDate, modify: '+'.$amount * $this->interval.' days'); + $this->currentDate = $this->getNextIterationDateTime($this->currentDate, '+'.$amount * $this->interval.' days'); return; } @@ -473,13 +473,13 @@ protected function nextDaily($amount = 1): void if ($this->byHour) { if ('23' == $this->currentDate->format('G')) { // to obey the interval rule - $this->currentDate = $this->getNextIterationDateTime($this->currentDate, modify: '+'.(($amount * $this->interval) - 1).' days'); + $this->currentDate = $this->getNextIterationDateTime($this->currentDate, '+'.(($amount * $this->interval) - 1).' days'); $amount = 1; } - $this->currentDate = $this->getNextIterationDateTime($this->currentDate, modify: '+1 hours'); + $this->currentDate = $this->getNextIterationDateTime($this->currentDate, '+1 hours'); } else { - $this->currentDate = $this->getNextIterationDateTime($this->currentDate, modify: '+'.($amount * $this->interval).' days'); + $this->currentDate = $this->getNextIterationDateTime($this->currentDate, '+'.($amount * $this->interval).' days'); $amount = 1; } @@ -510,7 +510,7 @@ protected function nextDaily($amount = 1): void protected function nextWeekly($amount = 1): void { if (!$this->byHour && !$this->byDay) { - $this->currentDate = $this->getNextIterationDateTime($this->currentDate, modify: '+'.($amount * $this->interval).' weeks'); + $this->currentDate = $this->getNextIterationDateTime($this->currentDate, '+'.($amount * $this->interval).' weeks'); return; } @@ -530,9 +530,9 @@ protected function nextWeekly($amount = 1): void do { if ($this->byHour) { - $this->currentDate = $this->getNextIterationDateTime($this->currentDate, modify: '+1 hours'); + $this->currentDate = $this->getNextIterationDateTime($this->currentDate, '+1 hours'); } else { - $this->currentDate = $this->getNextIterationDateTime($this->currentDate, modify: '+1 days'); + $this->currentDate = $this->getNextIterationDateTime($this->currentDate, '+1 days'); } // Current day of the week @@ -543,12 +543,12 @@ protected function nextWeekly($amount = 1): void // We need to roll over to the next week if ($currentDay === $firstDay && (!$this->byHour || '0' == $currentHour)) { - $this->currentDate = $this->getNextIterationDateTime($this->currentDate, modify: '+'.(($amount * $this->interval) - 1).' weeks'); + $this->currentDate = $this->getNextIterationDateTime($this->currentDate, '+'.(($amount * $this->interval) - 1).' weeks'); $amount = 1; // We need to go to the first day of this week, but only if we // are not already on this first day of this week. if ($this->currentDate->format('w') != $firstDay) { - $this->currentDate = $this->getNextIterationDateTime($this->currentDate, modify: 'last '.$this->dayNames[$this->dayMap[$this->weekStart]]); + $this->currentDate = $this->getNextIterationDateTime($this->currentDate, 'last '.$this->dayNames[$this->dayMap[$this->weekStart]]); } } @@ -572,13 +572,13 @@ protected function nextMonthly($amount = 1): void // occur to the next month. We Must skip these invalid // entries. if ($currentDayOfMonth < 29) { - $this->currentDate = $this->getNextIterationDateTime($this->currentDate, modify: '+'.($amount * $this->interval).' months'); + $this->currentDate = $this->getNextIterationDateTime($this->currentDate, '+'.($amount * $this->interval).' months'); } else { $increase = $amount - 1; do { ++$increase; $tempDate = clone $this->currentDate; - $tempDate = $this->getNextIterationDateTime($tempDate, modify: '+ '.($this->interval * $increase).' months'); + $tempDate = $this->getNextIterationDateTime($tempDate, '+ '.($this->interval * $increase).' months'); } while ($tempDate->format('j') != $currentDayOfMonth); $this->currentDate = $tempDate; } @@ -623,7 +623,7 @@ protected function nextMonthly($amount = 1): void 1 ); // end of workaround - $this->currentDate = $this->getNextIterationDateTime($this->currentDate, modify: '+ '.($amount * $this->interval).' months'); + $this->currentDate = $this->getNextIterationDateTime($this->currentDate, '+ '.($amount * $this->interval).' months'); $amount = 1; // This goes to 0 because we need to start counting at the @@ -684,7 +684,7 @@ protected function nextYearly($amount = 1): void // 400. (1800, 1900, 2100). So we just rely on the datetime // functions instead. $nextDate = clone $this->currentDate; - $nextDate = $this->getNextIterationDateTime($nextDate, modify: '+ '.($this->interval * $counter).' years'); + $nextDate = $this->getNextIterationDateTime($nextDate, '+ '.($this->interval * $counter).' years'); } while (2 != $nextDate->format('n')); $this->currentDate = $nextDate; @@ -775,7 +775,7 @@ protected function nextYearly($amount = 1): void } // The easiest form - $this->currentDate = $this->getNextIterationDateTime($this->currentDate, modify: '+'.($amount * $this->interval).' years'); + $this->currentDate = $this->getNextIterationDateTime($this->currentDate, '+'.($amount * $this->interval).' years'); return; } @@ -1239,7 +1239,7 @@ protected function getMonths(): array return $recurrenceMonths; } - private function getNextIterationDateTime(\DateTimeImmutable|\DateTime $dateTime, string $modify): \DateTimeImmutable|\DateTime + private function getNextIterationDateTime(\DateTimeInterface $dateTime, string $modify): \DateTimeInterface { $startTs = $dateTime->getTimestamp(); $initialDateTime = clone $dateTime; @@ -1257,9 +1257,9 @@ private function getNextIterationDateTime(\DateTimeImmutable|\DateTime $dateTime } private function detectAndSaveDstOffset( - \DateTimeImmutable|\DateTime $initialDateTime, - \DateTimeImmutable|\DateTime $modifiedDateTime, - string $modify, + \DateTimeInterface $initialDateTime, + \DateTimeInterface $modifiedDateTime, + string $modify ) { $dstTransitionLeap = $this->getDstTransitionLeap($initialDateTime, $modifiedDateTime, $modify); if (!is_null($dstTransitionLeap)) { @@ -1269,9 +1269,9 @@ private function detectAndSaveDstOffset( } private function getDstTransitionLeap( - \DateTimeImmutable|\DateTime $initialDateTime, - \DateTimeImmutable|\DateTime $modifiedDateTime, - string $modify, + \DateTimeInterface $initialDateTime, + \DateTimeInterface $modifiedDateTime, + string $modify ): ?int { $modifiedDateInterval = $this->substractDates($modifiedDateTime, $initialDateTime); $modifyInterval = \DateInterval::createFromDateString($modify); @@ -1289,13 +1289,13 @@ private function isDstTransitionLeap(\DateInterval $leap): bool return $leap->y === 0 && $leap->m === 0 && $leap->d === 0 && $leap->h > 0 && $leap->i === 0 && $leap->s === 0; } - private function revertPastOffset(\DateTimeImmutable|\DateTime $dateTime): \DateTimeImmutable|\DateTime + private function revertPastOffset(\DateTimeInterface $dateTime): \DateTimeInterface { if (!is_numeric($this->counter)) { return $dateTime; } - $previousIterationLeapOffset = $this->leapOffset[$this->counter - 1] ?? null; + $previousIterationLeapOffset = $this->leapOffset[(int) $this->counter - 1] ?? null; if (!is_int($previousIterationLeapOffset)) { return $dateTime; @@ -1323,8 +1323,8 @@ private function substractIntervals(\DateInterval $dateIntervalOne, \DateInterva } private function substractDates( - \DateTimeImmutable|\DateTime $dateIntervalOne, - \DateTimeImmutable|\DateTime $dateIntervalTwo, + \DateTimeInterface $dateIntervalOne, + \DateTimeInterface $dateIntervalTwo ): \DateInterval { $yearsDiff = $dateIntervalOne->format('y') - $dateIntervalTwo->format('y'); $monthsDiff = $dateIntervalOne->format('m') - $dateIntervalTwo->format('m'); diff --git a/tests/VObject/Recur/RRuleIteratorTest.php b/tests/VObject/Recur/RRuleIteratorTest.php index bfe3d36c5..0f7630a54 100644 --- a/tests/VObject/Recur/RRuleIteratorTest.php +++ b/tests/VObject/Recur/RRuleIteratorTest.php @@ -54,13 +54,15 @@ public function testDaily(): void public function testRecurrenceWithDstTransition(string $start, array $expected): void { $this->parse( - rule:'FREQ=DAILY;INTERVAL=1;COUNT=5', - start: $start, - expected: $expected, - expectedFreq: 'daily', - expectedCount: 5, - expectedInterval: 1, - tz: 'Europe/Zurich', + 'FREQ=DAILY;INTERVAL=1;COUNT=5', + $start, + $expected, + 'daily', + 5, + 1, + null, + null, + 'Europe/Zurich', ); } diff --git a/tests/VObject/TimeZoneUtilTest.php b/tests/VObject/TimeZoneUtilTest.php index 1069b437a..551f04052 100644 --- a/tests/VObject/TimeZoneUtilTest.php +++ b/tests/VObject/TimeZoneUtilTest.php @@ -629,7 +629,7 @@ public function testCustomizedTimeZone(): void $ics = <<getCustomizedICS(); $tz = TimeZoneUtil::getTimeZone('Customized Time Zone', Reader::read($ics)); - self::assertSame('Asia/Brunei', $tz->getName()); + self::assertSame('Antarctica/Casey', $tz->getName()); $start = new \DateTimeImmutable('2022-04-25'); self::assertSame(8 * 60 * 60, $tz->getOffset($start)); } From 82ee4e90c20e4f1575f43af6b769bdcf60a8061f Mon Sep 17 00:00:00 2001 From: Cyril van Schreven Date: Mon, 22 Apr 2024 12:46:46 +0200 Subject: [PATCH 3/3] Do not apply workaround on inner loops --- lib/Recur/RRuleIterator.php | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index f987e7929..a63268b66 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -440,7 +440,7 @@ private function jumpForward(\DateTimeInterface $dt): void */ protected function nextHourly($amount = 1): void { - $this->currentDate = $this->getNextIterationDateTime($this->currentDate, '+'.$amount * $this->interval.' hours'); + $this->currentDate = $this->currentDate->modify('+'.$amount * $this->interval.' hours'); } /** @@ -473,13 +473,13 @@ protected function nextDaily($amount = 1): void if ($this->byHour) { if ('23' == $this->currentDate->format('G')) { // to obey the interval rule - $this->currentDate = $this->getNextIterationDateTime($this->currentDate, '+'.(($amount * $this->interval) - 1).' days'); + $this->currentDate = $this->currentDate->modify('+'.(($amount * $this->interval) - 1).' days'); $amount = 1; } - $this->currentDate = $this->getNextIterationDateTime($this->currentDate, '+1 hours'); + $this->currentDate = $this->currentDate->modify('+1 hours'); } else { - $this->currentDate = $this->getNextIterationDateTime($this->currentDate, '+'.($amount * $this->interval).' days'); + $this->currentDate = $this->currentDate->modify('+'.($amount * $this->interval).' days'); $amount = 1; } @@ -530,9 +530,9 @@ protected function nextWeekly($amount = 1): void do { if ($this->byHour) { - $this->currentDate = $this->getNextIterationDateTime($this->currentDate, '+1 hours'); + $this->currentDate = $this->currentDate->modify('+1 hours'); } else { - $this->currentDate = $this->getNextIterationDateTime($this->currentDate, '+1 days'); + $this->currentDate = $this->currentDate->modify('+1 days'); } // Current day of the week @@ -543,12 +543,12 @@ protected function nextWeekly($amount = 1): void // We need to roll over to the next week if ($currentDay === $firstDay && (!$this->byHour || '0' == $currentHour)) { - $this->currentDate = $this->getNextIterationDateTime($this->currentDate, '+'.(($amount * $this->interval) - 1).' weeks'); + $this->currentDate = $this->currentDate->modify('+'.(($amount * $this->interval) - 1).' weeks'); $amount = 1; // We need to go to the first day of this week, but only if we // are not already on this first day of this week. if ($this->currentDate->format('w') != $firstDay) { - $this->currentDate = $this->getNextIterationDateTime($this->currentDate, 'last '.$this->dayNames[$this->dayMap[$this->weekStart]]); + $this->currentDate = $this->currentDate->modify('last '.$this->dayNames[$this->dayMap[$this->weekStart]]); } } @@ -578,7 +578,7 @@ protected function nextMonthly($amount = 1): void do { ++$increase; $tempDate = clone $this->currentDate; - $tempDate = $this->getNextIterationDateTime($tempDate, '+ '.($this->interval * $increase).' months'); + $tempDate = $tempDate->modify('+ '.($this->interval * $increase).' months'); } while ($tempDate->format('j') != $currentDayOfMonth); $this->currentDate = $tempDate; } @@ -623,7 +623,7 @@ protected function nextMonthly($amount = 1): void 1 ); // end of workaround - $this->currentDate = $this->getNextIterationDateTime($this->currentDate, '+ '.($amount * $this->interval).' months'); + $this->currentDate = $this->currentDate->modify('+ '.($amount * $this->interval).' months'); $amount = 1; // This goes to 0 because we need to start counting at the @@ -684,7 +684,7 @@ protected function nextYearly($amount = 1): void // 400. (1800, 1900, 2100). So we just rely on the datetime // functions instead. $nextDate = clone $this->currentDate; - $nextDate = $this->getNextIterationDateTime($nextDate, '+ '.($this->interval * $counter).' years'); + $nextDate = $nextDate->modify('+ '.($this->interval * $counter).' years'); } while (2 != $nextDate->format('n')); $this->currentDate = $nextDate;