From b1ea585149ecd9663aae9607c1b1c508cc98aec2 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Mon, 24 Jan 2022 14:42:51 -0500 Subject: [PATCH] LibC: Implement tzset with time zone awareness in accordance with POSIX --- Tests/LibC/TestLibCTime.cpp | 64 ++++++++++++++++++++++++++++++++ Userland/Libraries/LibC/time.cpp | 41 +++++++++++++++++--- 2 files changed, 100 insertions(+), 5 deletions(-) diff --git a/Tests/LibC/TestLibCTime.cpp b/Tests/LibC/TestLibCTime.cpp index fcf2327639..4fa829e2a2 100644 --- a/Tests/LibC/TestLibCTime.cpp +++ b/Tests/LibC/TestLibCTime.cpp @@ -10,6 +10,25 @@ const auto expected_epoch = "Thu Jan 1 00:00:00 1970\n"sv; +class TimeZoneGuard { +public: + TimeZoneGuard() + : m_tz(getenv("TZ")) + { + } + + ~TimeZoneGuard() + { + if (m_tz) + setenv("TZ", m_tz, 1); + else + unsetenv("TZ"); + } + +private: + char const* m_tz { nullptr }; +}; + TEST_CASE(asctime) { time_t epoch = 0; @@ -41,3 +60,48 @@ TEST_CASE(ctime_r) EXPECT_EQ(expected_epoch, StringView(result)); } + +TEST_CASE(tzset) +{ + TimeZoneGuard guard; + + auto set_tz = [](char const* tz) { + setenv("TZ", tz, 1); + tzset(); + }; + + set_tz("UTC"); + EXPECT_EQ(timezone, 0); + EXPECT_EQ(altzone, 0); + EXPECT_EQ(daylight, 0); + EXPECT_EQ(tzname[0], "UTC"sv); + EXPECT_EQ(tzname[1], "UTC"sv); + + set_tz("America/New_York"); + EXPECT_EQ(timezone, 5 * 60 * 60); + EXPECT_EQ(altzone, 4 * 60 * 60); + EXPECT_EQ(daylight, 1); + EXPECT_EQ(tzname[0], "EST"sv); + EXPECT_EQ(tzname[1], "EDT"sv); + + set_tz("America/Phoenix"); + EXPECT_EQ(timezone, 7 * 60 * 60); + EXPECT_EQ(altzone, 7 * 60 * 60); + EXPECT_EQ(daylight, 0); + EXPECT_EQ(tzname[0], "MST"sv); + EXPECT_EQ(tzname[1], "MST"sv); + + set_tz("America/Asuncion"); + EXPECT_EQ(timezone, 4 * 60 * 60); + EXPECT_EQ(altzone, 3 * 60 * 60); + EXPECT_EQ(daylight, 1); + EXPECT_EQ(tzname[0], "-04"sv); + EXPECT_EQ(tzname[1], "-03"sv); + + set_tz("CET"); + EXPECT_EQ(timezone, -1 * 60 * 60); + EXPECT_EQ(altzone, -2 * 60 * 60); + EXPECT_EQ(daylight, 1); + EXPECT_EQ(tzname[0], "CET"sv); + EXPECT_EQ(tzname[1], "CEST"sv); +} diff --git a/Userland/Libraries/LibC/time.cpp b/Userland/Libraries/LibC/time.cpp index 83f8f89156..53cdd4d5c6 100644 --- a/Userland/Libraries/LibC/time.cpp +++ b/Userland/Libraries/LibC/time.cpp @@ -11,7 +11,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -358,15 +360,44 @@ long altzone; char* tzname[2]; int daylight; +static char __tzname_standard[TZNAME_MAX]; +static char __tzname_daylight[TZNAME_MAX]; constexpr const char* __utc = "UTC"; void tzset() { - // FIXME: Here we pretend we are in UTC+0. - timezone = 0; - daylight = 0; - tzname[0] = const_cast(__utc); - tzname[1] = const_cast(__utc); + // FIXME: Actually parse the TZ environment variable, described here: + // https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08 + StringView time_zone; + + if (char* tz = getenv("TZ"); tz != nullptr) + time_zone = tz; + else + time_zone = TimeZone::current_time_zone(); + + auto set_default_values = []() { + timezone = 0; + altzone = 0; + daylight = 0; + tzname[0] = const_cast(__utc); + tzname[1] = const_cast(__utc); + }; + + if (auto offsets = TimeZone::get_named_time_zone_offsets(time_zone, AK::Time::now_realtime()); offsets.has_value()) { + if (!offsets->at(0).name.copy_characters_to_buffer(__tzname_standard, TZNAME_MAX)) + return set_default_values(); + if (!offsets->at(1).name.copy_characters_to_buffer(__tzname_daylight, TZNAME_MAX)) + return set_default_values(); + + // timezone and altzone are seconds west of UTC, i.e. the offsets are negated. + timezone = -offsets->at(0).seconds; + altzone = -offsets->at(1).seconds; + daylight = timezone != altzone; + tzname[0] = __tzname_standard; + tzname[1] = __tzname_daylight; + } else { + set_default_values(); + } } clock_t clock()