From 8f044685da46e25b3e7d8cf6212df04e714a8f02 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Thu, 9 May 2024 15:37:29 +0545 Subject: [PATCH] Handle summer time start for weekly recurrences --- lib/Recur/RRuleIterator.php | 16 ++++++ tests/VObject/Recur/RRuleIteratorTest.php | 62 ++++++++++++++++++++++- 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/lib/Recur/RRuleIterator.php b/lib/Recur/RRuleIterator.php index 5af57c77..e5190f06 100644 --- a/lib/Recur/RRuleIterator.php +++ b/lib/Recur/RRuleIterator.php @@ -373,7 +373,23 @@ protected function nextDaily(): void protected function nextWeekly(): void { if (!$this->byHour && !$this->byDay) { + $hourOfCurrentDate = (int) $this->currentDate->format('G'); $this->currentDate = $this->currentDate->modify('+'.$this->interval.' weeks'); + $hourOfNextDate = (int) $this->currentDate->format('G'); + if (0 === $this->hourJump) { + // Remember if the clock time jumped forward on the nextDate. + // That happens if nextDate is a day when summer time starts + // and the event time is in the non-existent hour of the day. + // For example, an event that normally starts at 02:30 will + // have to start at 03:30 on that day. + $this->hourJump = $hourOfNextDate - $hourOfCurrentDate; + } else { + // The hour "jumped" for the previous date, to avoid the non-existent time. + // currentDate got set ahead by (usually) one hour on that day. + // Adjust it back for this next occurrence. + $this->currentDate = $this->currentDate->sub(new \DateInterval('PT'.$this->hourJump.'H')); + $this->hourJump = 0; + } return; } diff --git a/tests/VObject/Recur/RRuleIteratorTest.php b/tests/VObject/Recur/RRuleIteratorTest.php index 6771bea0..6739ed6c 100644 --- a/tests/VObject/Recur/RRuleIteratorTest.php +++ b/tests/VObject/Recur/RRuleIteratorTest.php @@ -163,7 +163,7 @@ public function testDailyBySetPosLoop(): void } /** - * @dataProvider dstTransitionProvider + * @dataProvider dstDailyTransitionProvider */ public function testDailyOnDstTransition(string $start, array $expected): void { @@ -176,7 +176,7 @@ public function testDailyOnDstTransition(string $start, array $expected): void ); } - public function dstTransitionProvider(): iterable + public function dstDailyTransitionProvider(): iterable { yield 'On transition start' => [ 'Start' => '2023-03-24 02:00:00', @@ -323,6 +323,64 @@ public function testWeeklyByDaySpecificHour(): void ); } + /** + * @dataProvider dstWeeklyTransitionProvider + */ + public function testWeeklyOnDstTransition(string $start, array $expected): void + { + $this->parse( + 'FREQ=WEEKLY;INTERVAL=1;COUNT=5', + $start, + $expected, + null, + 'Europe/Zurich', + ); + } + + public function dstWeeklyTransitionProvider(): iterable + { + yield 'On transition start' => [ + 'Start' => '2023-03-12 02:00:00', + 'Expected' => [ + '2023-03-12 02:00:00', + '2023-03-19 02:00:00', + '2023-03-26 03:00:00', + '2023-04-02 02:00:00', + '2023-04-09 02:00:00', + ], + ]; + yield 'During transition' => [ + 'Start' => '2023-03-12 02:15:00', + 'Expected' => [ + '2023-03-12 02:15:00', + '2023-03-19 02:15:00', + '2023-03-26 03:15:00', + '2023-04-02 02:15:00', + '2023-04-09 02:15:00', + ], + ]; + yield 'On transition end' => [ + 'Start' => '2023-03-12 03:00:00', + 'Expected' => [ + '2023-03-12 03:00:00', + '2023-03-19 03:00:00', + '2023-03-26 03:00:00', + '2023-04-02 03:00:00', + '2023-04-09 03:00:00', + ], + ]; + yield 'After transition end' => [ + 'Start' => '2023-03-12 03:15:00', + 'Expected' => [ + '2023-03-12 03:15:00', + '2023-03-19 03:15:00', + '2023-03-26 03:15:00', + '2023-04-02 03:15:00', + '2023-04-09 03:15:00', + ], + ]; + } + public function testMonthly(): void { $this->parse(