-
Notifications
You must be signed in to change notification settings - Fork 132
Description
Found this error in production Ubuntu 24.04 LTS server (line numbers should match sabre/vobject version 4.5.6):
DateInvalidTimeZoneException: DateTimeZone::__construct(): Unknown or bad timezone (Europe/Kiev)
Backtrace: Backtrace (most recent call first):
.../lib/vendor/sabre/vobject/lib/TimezoneGuesser/FindFromTimezoneMap.php:25: DateTimeZone->__construct();
.../lib/vendor/sabre/vobject/lib/TimeZoneUtil.php:81: Sabre\VObject\TimezoneGuesser\FindFromTimezoneMap->find();
.../lib/vendor/sabre/vobject/lib/TimeZoneUtil.php:131: Sabre\VObject\TimeZoneUtil->findTimeZone();
.../lib/vendor/sabre/vobject/lib/Property/ICalendar/DateTime.php:162: Sabre\VObject\TimeZoneUtil::getTimeZone();
.../lib/vendor/sabre/vobject/lib/Property/ICalendar/DateTime.php:136: Sabre\VObject\Property\ICalendar\DateTime->getDateTimes();
.../lib/vendor/sabre/vobject/lib/Component/VEvent.php:52: Sabre\VObject\Property\ICalendar\DateTime->getDateTime();
.../lib/vendor/sabre/vobject/lib/Component/VCalendar.php:329: Sabre\VObject\Component\VEvent->isInTimeRange();
.../our-service/System/VCalendar.php:156: Sabre\VObject\Component\VCalendar->expand();
...
Looking at tzdata changelogs on Ubuntu it seems that Canonical / tzdata maintainer started conversion from Kiev to Kyiv in 2022 and in their infinite wisdom they also dropped support for the older name in recent release. The removal of the symlink was not documented in the changelog but it happened in reality somewhere between versions 2025b-0ubuntu0.22.04.1 and 2025b-0ubuntu0.24.04.1.
tzdata (2022b-1) unstable; urgency=medium
[ Aurelien Jarno ]
* New upstream version:
...
* debian/tzdata.config: convert Europe/Kiev into Europe/Kyiv.
Older versions seemed to have symlink from Kiev to Kyiv but most recent version 2025b-0ubuntu0.24.04.1 no longer ships with this symlink so the system fails to set timezone "Europe/Kiev" because it would need to be spelled "Europe/Kyiv" now.
Older release:
$ ls -lh /usr/share/zoneinfo/Europe/{Kiev,Kyiv}
lrwxrwxrwx 1 root root 4 Apr 22 13:15 /usr/share/zoneinfo/Europe/Kiev -> Kyiv
-rw-r--r-- 1 root root 2.1K Apr 22 13:15 /usr/share/zoneinfo/Europe/Kyiv
and the newer release is missing the symlink.
Since the output from DateTimeZone::listIdentifiers() doesn't seem to be accurate either, I think sabre/vobject should just run every statement new DateTimeZone(...) in a try-catch block and implement the fallback behavior for unsupported timezone name in the catch-block.
It also appears that catch (\Exception $e) wasn't enough for new DateTimeZone(...) because I had to do catch (\Throwable $e) instead. If I don't catch Throwable I get a weird exception that says Error: Cannot create dynamic property DateInvalidTimeZoneException::$xdebug_message on development server while debugging this issue. And our codebase has no string xdebug_message in any file! I guess TimeZone objects are somehow extra broken with the PHP xdebug version I've installed but I think sabre/vobject should handle this error, too, so Throwable it is.
The variant of xdebug I'm using on test server is package php8.3-xdebug at version 3.2.0+3.1.6+2.9.8+2.8.1+2.5.5-3ubuntu1. We don't have xdebug package on production and I haven't tried to workaround the issue on production yet, so I don't know if catch (\Exception $e) would work without xdebug active.
And I think the root cause of this issue isn't about spelling "Kiev" vs "Kyiv" but handling OS level non-supported timezones in general because timezone renames will happen in the future, too. For Kiev/Kyiv the problem is that only one or the other spelling is supported by the OS. If the OS timezone data (package tzdata on Ubuntu server and I guess Debian, too) are from before version 2022b then only Europe/Kiev is supported and if the OS libraries are from 2025b then only Europe/Kyiv is supported even if the PHP binary and sabre/vobject files were unchanged! And the only way to know which one is supported is to try to create a new DateTimeZone with either string. Between 2022b and before 2025b both variants would work.
Maybe the least bad option would be to maintain list of fallbacks like this?
$fallback_timezones = array(
"Europe/Kyiv" => array("Europe/Kiev", "Europe/Ukraine"),
);
and if the "Europe/Kyiv" throws, just try fallback timezones, one at a time before giving up.
Here's a workaround I'm currently using on the development server
diff --git i/lib/vendor/sabre/vobject/lib/TimezoneGuesser/FindFromTimezoneMap.php w/lib/vendor/sabre/vobject/lib/TimezoneGuesser/FindFrom>
index b52ba6a19..473de4bda 100644
--- i/lib/vendor/sabre/vobject/lib/TimezoneGuesser/FindFromTimezoneMap.php
+++ w/lib/vendor/sabre/vobject/lib/TimezoneGuesser/FindFromTimezoneMap.php
@@ -22,7 +22,15 @@ public function find(string $tzid, bool $failIfUncertain = false): ?DateTimeZone
{
// Next, we check if the tzid is somewhere in our tzid map.
if ($this->hasTzInMap($tzid)) {
- return new DateTimeZone($this->getTzFromMap($tzid));
+ try
+ {
+ return new DateTimeZone($this->getTzFromMap($tzid));
+ }
+ catch (\Throwable $e)
+ {
+ error_log("Failed to create DateTimeZone for '$tzid', falling back to UTC: " . $e->getMessage()."\n".backtrace());
+ return new DateTimeZone("UTC");
+ }
}
// Some Microsoft products prefix the offset first, so let's strip that off
However, I don't think this is good for production because it will silently interpret all hours incorrectly unless unknown timezone happens to match UTC.