From 7103012c7d447c1ba3d18597b086ae868661f82e Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Mon, 24 Jan 2022 12:36:17 -0500 Subject: [PATCH] LibTimeZone: Add an API to retrieve both daylight and standard offsets This API will also include the formatted name of the time zone, with respect for DST (e.g. EST vs EDT for America/New_York). --- .../LibTimeZone/GenerateTimeZoneData.cpp | 32 +++++++++++++++++++ Tests/LibTimeZone/TestTimeZone.cpp | 25 +++++++++++++++ Userland/Libraries/LibTimeZone/TimeZone.cpp | 22 ++++++++++++- Userland/Libraries/LibTimeZone/TimeZone.h | 9 ++++++ 4 files changed, 87 insertions(+), 1 deletion(-) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibTimeZone/GenerateTimeZoneData.cpp b/Meta/Lagom/Tools/CodeGenerators/LibTimeZone/GenerateTimeZoneData.cpp index 47bf85b275..264e523bb7 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibTimeZone/GenerateTimeZoneData.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibTimeZone/GenerateTimeZoneData.cpp @@ -582,6 +582,38 @@ Optional get_time_zone_offset(TimeZone time_zone, AK::Time time) return dst_offset; } +Optional> get_named_time_zone_offsets(TimeZone time_zone, AK::Time time) +{ + auto const& time_zone_offset = find_time_zone_offset(time_zone, time); + Array named_offsets; + + auto format_name = [](auto format, auto offset) -> String { + if (offset == 0) + return s_string_list[format].replace("{}"sv, ""sv); + return String::formatted(s_string_list[format], s_string_list[offset]); + }; + + auto set_named_offset = [&](auto& named_offset, auto dst_offset, auto in_dst, auto format, auto offset) { + named_offset.seconds = time_zone_offset.offset + dst_offset; + named_offset.in_dst = in_dst; + named_offset.name = format_name(format, offset); + }; + + if (time_zone_offset.dst_rule != -1) { + auto offsets = find_dst_offsets(time_zone_offset, time); + auto in_dst = offsets[1]->offset == 0 ? InDST::No : InDST::Yes; + + set_named_offset(named_offsets[0], offsets[0]->offset, InDST::No, time_zone_offset.standard_format, offsets[0]->format); + set_named_offset(named_offsets[1], offsets[1]->offset, in_dst, time_zone_offset.daylight_format, offsets[1]->format); + } else { + auto in_dst = time_zone_offset.dst_offset == 0 ? InDST::No : InDST::Yes; + set_named_offset(named_offsets[0], time_zone_offset.dst_offset, in_dst, time_zone_offset.standard_format, 0); + set_named_offset(named_offsets[1], time_zone_offset.dst_offset, in_dst, time_zone_offset.daylight_format, 0); + } + + return named_offsets; +} + Span all_time_zones() { static constexpr auto all_time_zones = Array { diff --git a/Tests/LibTimeZone/TestTimeZone.cpp b/Tests/LibTimeZone/TestTimeZone.cpp index d86aa6166f..f164db103b 100644 --- a/Tests/LibTimeZone/TestTimeZone.cpp +++ b/Tests/LibTimeZone/TestTimeZone.cpp @@ -149,6 +149,31 @@ TEST_CASE(get_time_zone_offset_with_dst) test_offset("Europe/Moscow"sv, -1589068800, offset(+1, 3, 00, 00), No); // Monday, August 25, 1919 12:00:00 AM } +TEST_CASE(get_named_time_zone_offsets) +{ + auto test_named_offsets = [](auto time_zone, i64 time, i64 expected_standard_offset, i64 expected_daylight_offset, auto expected_standard_name, auto expected_daylight_name) { + auto actual_offsets = TimeZone::get_named_time_zone_offsets(time_zone, AK::Time::from_seconds(time)); + VERIFY(actual_offsets.has_value()); + + EXPECT_EQ(actual_offsets->at(0).seconds, expected_standard_offset); + EXPECT_EQ(actual_offsets->at(1).seconds, expected_daylight_offset); + EXPECT_EQ(actual_offsets->at(0).name, expected_standard_name); + EXPECT_EQ(actual_offsets->at(1).name, expected_daylight_name); + }; + + test_named_offsets("America/New_York"sv, 1642558528, offset(-1, 5, 00, 00), offset(-1, 4, 00, 00), "EST"sv, "EDT"sv); // Wednesday, January 19, 2022 2:15:28 AM + test_named_offsets("UTC"sv, 1642558528, offset(+1, 0, 00, 00), offset(+1, 0, 00, 00), "UTC"sv, "UTC"sv); // Wednesday, January 19, 2022 2:15:28 AM + test_named_offsets("GMT"sv, 1642558528, offset(+1, 0, 00, 00), offset(+1, 0, 00, 00), "GMT"sv, "GMT"sv); // Wednesday, January 19, 2022 2:15:28 AM + + // Phoenix does not observe DST. + test_named_offsets("America/Phoenix"sv, 1642558528, offset(-1, 7, 00, 00), offset(-1, 7, 00, 00), "MST"sv, "MST"sv); // Wednesday, January 19, 2022 2:15:28 AM + + // Moscow's observed DST changed several times in 1919. + test_named_offsets("Europe/Moscow"sv, -1609459200, offset(+1, 2, 31, 19), offset(+1, 3, 31, 19), "MSK"sv, "MSD"sv); // Wednesday, January 1, 1919 12:00:00 AM + test_named_offsets("Europe/Moscow"sv, -1596412800, offset(+1, 2, 31, 19), offset(+1, 4, 31, 19), "MSK"sv, "MDST"sv); // Sunday, June 1, 1919 12:00:00 AM + test_named_offsets("Europe/Moscow"sv, -1589068800, offset(+1, 3, 00, 00), offset(+1, 4, 00, 00), "MSK"sv, "MSD"sv); // Monday, August 25, 1919 12:00:00 AM +} + #else TEST_CASE(time_zone_from_string) diff --git a/Userland/Libraries/LibTimeZone/TimeZone.cpp b/Userland/Libraries/LibTimeZone/TimeZone.cpp index 8629ca7380..af9b739b1e 100644 --- a/Userland/Libraries/LibTimeZone/TimeZone.cpp +++ b/Userland/Libraries/LibTimeZone/TimeZone.cpp @@ -4,7 +4,6 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include #include #include #include @@ -157,4 +156,25 @@ Optional get_time_zone_offset(StringView time_zone, AK::Time time) return {}; } +Optional> __attribute__((weak)) get_named_time_zone_offsets([[maybe_unused]] TimeZone time_zone, AK::Time) +{ +#if !ENABLE_TIME_ZONE_DATA + VERIFY(time_zone == TimeZone::UTC); + + NamedOffset utc_offset {}; + utc_offset.name = "UTC"sv; + + return Array { utc_offset, utc_offset }; +#else + return {}; +#endif +} + +Optional> get_named_time_zone_offsets(StringView time_zone, AK::Time time) +{ + if (auto maybe_time_zone = time_zone_from_string(time_zone); maybe_time_zone.has_value()) + return get_named_time_zone_offsets(*maybe_time_zone, time); + return {}; +} + } diff --git a/Userland/Libraries/LibTimeZone/TimeZone.h b/Userland/Libraries/LibTimeZone/TimeZone.h index fb4d58177a..710461f86c 100644 --- a/Userland/Libraries/LibTimeZone/TimeZone.h +++ b/Userland/Libraries/LibTimeZone/TimeZone.h @@ -6,8 +6,10 @@ #pragma once +#include #include #include +#include #include #include #include @@ -25,6 +27,10 @@ struct Offset { InDST in_dst { InDST::No }; }; +struct NamedOffset : public Offset { + String name; +}; + StringView current_time_zone(); ErrorOr change_time_zone(StringView time_zone); Span all_time_zones(); @@ -39,4 +45,7 @@ StringView daylight_savings_rule_to_string(DaylightSavingsRule daylight_savings_ Optional get_time_zone_offset(TimeZone time_zone, AK::Time time); Optional get_time_zone_offset(StringView time_zone, AK::Time time); +Optional> get_named_time_zone_offsets(TimeZone time_zone, AK::Time time); +Optional> get_named_time_zone_offsets(StringView time_zone, AK::Time time); + }