From c3fd4554a6d6525bdb9309c744fadfe0bdaacbd7 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Wed, 28 Sep 2022 14:21:52 -0400 Subject: [PATCH] LibTimeZone: Do not use tzname to determine the current time zone The tzname array stores the abbreviated names of the current time zone when in standard and daylight time. These abbreviations are ambiguous; a single abbreviation often maps to multiple time zones. For example, EST is used by America/New_York and America/Detroit, and CST could be the abbreviation of Central Standard Time or China Standard Time. Instead, we mimic a subset of how both ICU and Howard Hinnant's "date" library determines the current time zone. First, we try to parse the TZ environment variable. If that fails, or isn't set, we try to resolve the /etc/localtime symbolic link. On most Linux systems and on macOS, this is a link to the current TZDB file in use. If all of the above fails, we fall back to UTC. --- Userland/Libraries/LibTimeZone/TimeZone.cpp | 48 +++++++++++++++++++-- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/Userland/Libraries/LibTimeZone/TimeZone.cpp b/Userland/Libraries/LibTimeZone/TimeZone.cpp index fae4ad50a8..175bf5fd0f 100644 --- a/Userland/Libraries/LibTimeZone/TimeZone.cpp +++ b/Userland/Libraries/LibTimeZone/TimeZone.cpp @@ -4,10 +4,15 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include +#include #include +#include +#include #include +#include namespace TimeZone { @@ -72,17 +77,52 @@ private: StringView system_time_zone() { TimeZoneFile time_zone_file("r"); + auto time_zone = time_zone_file.read_time_zone(); // FIXME: Propagate the error to existing callers. - if (auto time_zone = time_zone_file.read_time_zone(); !time_zone.is_error()) - return canonicalize_time_zone(time_zone.value()).value_or("UTC"sv); + if (time_zone.is_error()) { + dbgln_if(TIME_ZONE_DEBUG, "{}", time_zone.error()); + return "UTC"sv; + } - return "UTC"sv; + return canonicalize_time_zone(time_zone.value()).value_or("UTC"sv); } StringView current_time_zone() { - return canonicalize_time_zone({ tzname[0], __builtin_strlen(tzname[0]) }).value_or("UTC"sv); + if (char* tz = getenv("TZ"); tz != nullptr) { + // FIXME: Actually parse the TZ environment variable, described here: + // https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08 + StringView time_zone { tz, strlen(tz) }; + + if (auto maybe_time_zone = canonicalize_time_zone(time_zone); maybe_time_zone.has_value()) + return *maybe_time_zone; + + dbgln_if(TIME_ZONE_DEBUG, "Could not determine time zone from TZ environment: {}", time_zone); + } + +#ifdef __serenity__ + return system_time_zone(); +#else + static constexpr auto zoneinfo = "/zoneinfo/"sv; + char buffer[PATH_MAX]; + + if (auto size = readlink("/etc/localtime", buffer, sizeof(buffer)); size > 0) { + StringView time_zone { buffer, static_cast(size) }; + + if (auto index = time_zone.find(zoneinfo); index.has_value()) + time_zone = time_zone.substring_view(*index + zoneinfo.length()); + + if (auto maybe_time_zone = canonicalize_time_zone(time_zone); maybe_time_zone.has_value()) + return *maybe_time_zone; + + dbgln_if(TIME_ZONE_DEBUG, "Could not determine time zone from /etc/localtime: {}", time_zone); + } else { + dbgln_if(TIME_ZONE_DEBUG, "Could not read the /etc/localtime link: {}", strerror(errno)); + } + + return "UTC"sv; +#endif } ErrorOr change_time_zone([[maybe_unused]] StringView time_zone)