From dfc0d3846edc31e3e59b7222b7da30a43c572a05 Mon Sep 17 00:00:00 2001 From: schlotzz Date: Sun, 26 Feb 2023 16:02:44 +0100 Subject: [PATCH] Wrong calculation of timezone transitions There might be a bug in the calculation of timezone transitions. According to the example in RFC 5545, section 3.6.5 (page 67ff.) the DTSTART timestamp should be using the previously valid timezone. The small code change did the trick for me. Maybe there's a more suitable solution. --- src/Domain/Entity/TimeZone.php | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/Domain/Entity/TimeZone.php b/src/Domain/Entity/TimeZone.php index c2c6d644..742ca55c 100644 --- a/src/Domain/Entity/TimeZone.php +++ b/src/Domain/Entity/TimeZone.php @@ -48,17 +48,16 @@ public static function createFromPhpDateTimeZone( $timeZone = new self($phpDateTimeZone->getName()); foreach ($transitions as $transitionArray) { - $fromDateTime = DateTimeImmutable::createFromFormat( - DateTimeImmutable::ISO8601, - $transitionArray['time'] - ); + $fromDateTime = DateTimeImmutable::createFromFormat('U', $transition['ts'], new DateTimeZone('UTC')); assert($fromDateTime instanceof DateTimeImmutable, $transitionArray['time']); - $localFromDateTime = $fromDateTime->setTimezone($phpDateTimeZone); + $offsetFrom = $phpDateTimeZone->getOffset($fromDateTime->sub(new DateInterval('PT1S'))); + $offsetFromInterval = $this->resolveInterval($offsetFrom); + $previousTimezoneAwareFromDateTime = $fromDateTime->add($offsetFromInterval); $timeZone->addTransition(new TimeZoneTransition( $transitionArray['isdst'] ? TimeZoneTransitionType::DAYLIGHT() : TimeZoneTransitionType::STANDARD(), - $localFromDateTime, - $phpDateTimeZone->getOffset($fromDateTime->sub(new DateInterval('PT1S'))), + $previousTimezoneAwareFromDateTime, + $offsetFrom, $transitionArray['offset'], $transitionArray['abbr'] )); @@ -67,6 +66,21 @@ public static function createFromPhpDateTimeZone( return $timeZone; } + private function resolveInterval(int $seconds): DateInterval + { + $hours = (int)floor($seconds / 3600); + $minutes = ($seconds / 60) % 60; + + $interval = new DateInterval( + 'PT' . abs($hours) . 'H' . abs($minutes) . 'M' + ); + + if ($seconds < 0) + $interval->invert = 1; + + return $interval; + } + public function getTimeZoneId(): string { return $this->timeZoneId;