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.
This commit is contained in:
Timothy Flynn 2022-09-28 14:21:52 -04:00 committed by Linus Groh
parent 5f9d8f25c6
commit c3fd4554a6
Notes: sideshowbarker 2024-07-17 23:02:37 +09:00

View file

@ -4,10 +4,15 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Debug.h>
#include <AK/String.h>
#include <LibTimeZone/TimeZone.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
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_t>(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<void> change_time_zone([[maybe_unused]] StringView time_zone)