From d3e809bcd4a661b9d2e7a41ec2f328bf6485cc11 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Mon, 24 Jun 2024 10:17:32 -0400 Subject: [PATCH] LibJS+LibUnicode: Port retrieving the system time zone to ICU --- Tests/LibUnicode/CMakeLists.txt | 1 + Tests/LibUnicode/TestTimeZone.cpp | 43 +++++++++++++++++++ Userland/Libraries/LibJS/Runtime/Date.cpp | 5 ++- Userland/Libraries/LibJS/Runtime/Date.h | 2 +- .../Libraries/LibJS/Runtime/DatePrototype.cpp | 3 +- .../Intl/DateTimeFormatConstructor.cpp | 2 +- .../Runtime/Temporal/AbstractOperations.cpp | 2 +- .../LibJS/Runtime/Temporal/Instant.cpp | 2 +- .../Libraries/LibJS/Runtime/Temporal/Now.cpp | 2 +- .../LibJS/Runtime/Temporal/TimeZone.cpp | 10 ++--- .../LibJS/Runtime/Temporal/TimeZone.h | 2 +- .../Runtime/Temporal/TimeZoneConstructor.cpp | 2 +- .../LibJS/Runtime/Temporal/ZonedDateTime.cpp | 2 +- Userland/Libraries/LibUnicode/CMakeLists.txt | 1 + Userland/Libraries/LibUnicode/TimeZone.cpp | 37 ++++++++++++++++ Userland/Libraries/LibUnicode/TimeZone.h | 15 +++++++ 16 files changed, 115 insertions(+), 16 deletions(-) create mode 100644 Tests/LibUnicode/TestTimeZone.cpp create mode 100644 Userland/Libraries/LibUnicode/TimeZone.cpp create mode 100644 Userland/Libraries/LibUnicode/TimeZone.h diff --git a/Tests/LibUnicode/CMakeLists.txt b/Tests/LibUnicode/CMakeLists.txt index c074a75d820..52e9638f154 100644 --- a/Tests/LibUnicode/CMakeLists.txt +++ b/Tests/LibUnicode/CMakeLists.txt @@ -4,6 +4,7 @@ set(TEST_SOURCES TestIDNA.cpp TestLocale.cpp TestSegmenter.cpp + TestTimeZone.cpp TestUnicodeCharacterTypes.cpp TestUnicodeNormalization.cpp ) diff --git a/Tests/LibUnicode/TestTimeZone.cpp b/Tests/LibUnicode/TestTimeZone.cpp new file mode 100644 index 00000000000..017ab32edcf --- /dev/null +++ b/Tests/LibUnicode/TestTimeZone.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +#include +#include +#include + +class TimeZoneGuard { +public: + explicit TimeZoneGuard(StringView time_zone) + : m_time_zone(Core::Environment::get("TZ"sv)) + { + MUST(Core::Environment::set("TZ"sv, time_zone, Core::Environment::Overwrite::Yes)); + } + + ~TimeZoneGuard() + { + if (m_time_zone.has_value()) + MUST(Core::Environment::set("TZ"sv, *m_time_zone, Core::Environment::Overwrite::Yes)); + else + MUST(Core::Environment::unset("TZ"sv)); + } + +private: + Optional m_time_zone; +}; + +TEST_CASE(current_time_zone) +{ + { + TimeZoneGuard guard { "America/New_York"sv }; + EXPECT_EQ(Unicode::current_time_zone(), "America/New_York"sv); + } + { + TimeZoneGuard guard { "ladybird"sv }; + EXPECT_EQ(Unicode::current_time_zone(), "UTC"sv); + } +} diff --git a/Userland/Libraries/LibJS/Runtime/Date.cpp b/Userland/Libraries/LibJS/Runtime/Date.cpp index a4787ee438e..955f6a3c1b8 100644 --- a/Userland/Libraries/LibJS/Runtime/Date.cpp +++ b/Userland/Libraries/LibJS/Runtime/Date.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include namespace JS { @@ -464,9 +465,9 @@ Vector available_named_time_zone_identifiers() } // 21.4.1.24 SystemTimeZoneIdentifier ( ), https://tc39.es/ecma262/#sec-systemtimezoneidentifier -StringView system_time_zone_identifier() +String system_time_zone_identifier() { - return TimeZone::current_time_zone(); + return Unicode::current_time_zone(); } // 21.4.1.25 LocalTime ( t ), https://tc39.es/ecma262/#sec-localtime diff --git a/Userland/Libraries/LibJS/Runtime/Date.h b/Userland/Libraries/LibJS/Runtime/Date.h index 2dae8eead9c..5eb2b569326 100644 --- a/Userland/Libraries/LibJS/Runtime/Date.h +++ b/Userland/Libraries/LibJS/Runtime/Date.h @@ -76,7 +76,7 @@ Crypto::SignedBigInteger get_utc_epoch_nanoseconds(i32 year, u8 month, u8 day, u Vector get_named_time_zone_epoch_nanoseconds(StringView time_zone_identifier, i32 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond); i64 get_named_time_zone_offset_nanoseconds(StringView time_zone_identifier, Crypto::SignedBigInteger const& epoch_nanoseconds); Vector available_named_time_zone_identifiers(); -StringView system_time_zone_identifier(); +String system_time_zone_identifier(); double local_time(double time); double utc_time(double time); double make_time(double hour, double min, double sec, double ms); diff --git a/Userland/Libraries/LibJS/Runtime/DatePrototype.cpp b/Userland/Libraries/LibJS/Runtime/DatePrototype.cpp index da5cc364365..9e3100071c0 100644 --- a/Userland/Libraries/LibJS/Runtime/DatePrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/DatePrototype.cpp @@ -27,6 +27,7 @@ #include #include #include +#include namespace JS { @@ -1159,7 +1160,7 @@ ByteString time_zone_string(double time) if (auto name = Unicode::time_zone_display_name(Unicode::default_locale(), tz_name, maybe_offset->in_dst, time); name.has_value()) tz_name = name.release_value(); } else { - tz_name = MUST(String::from_utf8(TimeZone::current_time_zone())); + tz_name = Unicode::current_time_zone(); } // 10. Return the string-concatenation of offsetSign, offsetHour, offsetMin, and tzName. diff --git a/Userland/Libraries/LibJS/Runtime/Intl/DateTimeFormatConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Intl/DateTimeFormatConstructor.cpp index b6c6bff3577..c6f24351f82 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/DateTimeFormatConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/DateTimeFormatConstructor.cpp @@ -197,7 +197,7 @@ ThrowCompletionOr> create_date_time_format(VM& vm, // 30. If timeZone is undefined, then if (time_zone_value.is_undefined()) { // a. Set timeZone to DefaultTimeZone(). - time_zone = MUST(String::from_utf8(system_time_zone_identifier())); + time_zone = system_time_zone_identifier(); } // 31. Else, else { diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp index ecc5240c12e..0c722af5af4 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp @@ -687,7 +687,7 @@ ThrowCompletionOr to_relative_temporal_object(VM& vm, Object const& } // ii. Let timeZone be ! CreateTemporalTimeZone(timeZoneName). - time_zone = MUST_OR_THROW_OOM(create_temporal_time_zone(vm, *time_zone_name)); + time_zone = MUST_OR_THROW_OOM(create_temporal_time_zone(vm, time_zone_name.release_value())); // iii. If result.[[TimeZone]].[[Z]] is true, then if (result.time_zone.z) { diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp index 086c4e57050..d9ca44f2360 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp @@ -279,7 +279,7 @@ ThrowCompletionOr temporal_instant_to_string(VM& vm, Instant& instant, V // 4. If outputTimeZone is undefined, then if (output_time_zone.is_undefined()) { // a. Set outputTimeZone to ! CreateTemporalTimeZone("UTC"). - output_time_zone = MUST_OR_THROW_OOM(create_temporal_time_zone(vm, "UTC"sv)); + output_time_zone = MUST_OR_THROW_OOM(create_temporal_time_zone(vm, "UTC"_string)); } // 5. Let isoCalendar be ! GetISO8601Calendar(). diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Now.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/Now.cpp index 5eaa1638110..49a55262dd6 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Now.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Now.cpp @@ -158,7 +158,7 @@ TimeZone* system_time_zone(VM& vm) // 2. Return ! CreateTemporalTimeZone(identifier). // FIXME: Propagate possible OOM error - return MUST(create_temporal_time_zone(vm, identifier)); + return MUST(create_temporal_time_zone(vm, move(identifier))); } // 2.3.2 SystemUTCEpochNanoseconds ( ), https://tc39.es/proposal-temporal/#sec-temporal-systemutcepochnanoseconds diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp index 7a2bf047aa8..d408e1c0dcd 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp @@ -61,7 +61,7 @@ ThrowCompletionOr canonicalize_time_zone_name(VM& vm, StringView time_zo } // 11.6.1 CreateTemporalTimeZone ( identifier [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporaltimezone -ThrowCompletionOr create_temporal_time_zone(VM& vm, StringView identifier, FunctionObject const* new_target) +ThrowCompletionOr create_temporal_time_zone(VM& vm, String identifier, FunctionObject const* new_target) { auto& realm = *vm.current_realm(); @@ -89,7 +89,7 @@ ThrowCompletionOr create_temporal_time_zone(VM& vm, StringView identi VERIFY(MUST_OR_THROW_OOM(canonicalize_time_zone_name(vm, identifier)) == identifier); // b. Set object.[[Identifier]] to identifier. - object->set_identifier(TRY_OR_THROW_OOM(vm, String::from_utf8(identifier))); + object->set_identifier(move(identifier)); // c. Set object.[[OffsetNanoseconds]] to undefined. // NOTE: No-op. @@ -318,15 +318,15 @@ ThrowCompletionOr to_temporal_time_zone(VM& vm, Value temporal_time_zon } // c. Return ! CreateTemporalTimeZone(name). - return MUST_OR_THROW_OOM(create_temporal_time_zone(vm, name)); + return MUST_OR_THROW_OOM(create_temporal_time_zone(vm, move(name))); } // 5. If parseResult.[[Z]] is true, return ! CreateTemporalTimeZone("UTC"). if (parse_result.z) - return MUST_OR_THROW_OOM(create_temporal_time_zone(vm, "UTC"sv)); + return MUST_OR_THROW_OOM(create_temporal_time_zone(vm, "UTC"_string)); // 6. Return ! CreateTemporalTimeZone(parseResult.[[OffsetString]]). - return MUST_OR_THROW_OOM(create_temporal_time_zone(vm, *parse_result.offset_string)); + return MUST_OR_THROW_OOM(create_temporal_time_zone(vm, parse_result.offset_string.release_value())); } // 11.5.19 GetOffsetNanosecondsFor ( timeZoneRec, instant ), https://tc39.es/proposal-temporal/#sec-temporal-getoffsetnanosecondsfor diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h index aaef96bab41..1fda94550fc 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h @@ -39,7 +39,7 @@ private: bool is_available_time_zone_name(StringView time_zone); ThrowCompletionOr canonicalize_time_zone_name(VM&, StringView time_zone); -ThrowCompletionOr create_temporal_time_zone(VM&, StringView identifier, FunctionObject const* new_target = nullptr); +ThrowCompletionOr create_temporal_time_zone(VM&, String identifier, FunctionObject const* new_target = nullptr); ISODateTime get_iso_parts_from_epoch(VM&, Crypto::SignedBigInteger const& epoch_nanoseconds); BigInt* get_named_time_zone_next_transition(VM&, StringView time_zone_identifier, BigInt const& epoch_nanoseconds); BigInt* get_named_time_zone_previous_transition(VM&, StringView time_zone_identifier, BigInt const& epoch_nanoseconds); diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZoneConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZoneConstructor.cpp index 8e27e2e6e69..352ee7fd230 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZoneConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZoneConstructor.cpp @@ -65,7 +65,7 @@ ThrowCompletionOr> TimeZoneConstructor::construct(FunctionO } // 4. Return ? CreateTemporalTimeZone(identifier, NewTarget). - return *TRY(create_temporal_time_zone(vm, identifier, &new_target)); + return *TRY(create_temporal_time_zone(vm, move(identifier), &new_target)); } // 11.3.2 Temporal.TimeZone.from ( item ), https://tc39.es/proposal-temporal/#sec-temporal.timezone.from diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp index e4fa2595915..49a545f29c9 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp @@ -227,7 +227,7 @@ ThrowCompletionOr to_temporal_zoned_date_time(VM& vm, Value item } // j. Let timeZone be ! CreateTemporalTimeZone(timeZoneName). - time_zone = MUST_OR_THROW_OOM(create_temporal_time_zone(vm, *time_zone_name)); + time_zone = MUST_OR_THROW_OOM(create_temporal_time_zone(vm, time_zone_name.release_value())); // k. Let calendar be ? ToTemporalCalendarWithISODefault(result.[[Calendar]]). auto temporal_calendar_like = result.calendar.has_value() diff --git a/Userland/Libraries/LibUnicode/CMakeLists.txt b/Userland/Libraries/LibUnicode/CMakeLists.txt index 3db3f44d5f8..e978beb68a1 100644 --- a/Userland/Libraries/LibUnicode/CMakeLists.txt +++ b/Userland/Libraries/LibUnicode/CMakeLists.txt @@ -17,6 +17,7 @@ set(SOURCES RelativeTimeFormat.cpp Segmenter.cpp String.cpp + TimeZone.cpp UnicodeKeywords.cpp ${UNICODE_DATA_SOURCES} ) diff --git a/Userland/Libraries/LibUnicode/TimeZone.cpp b/Userland/Libraries/LibUnicode/TimeZone.cpp new file mode 100644 index 00000000000..c2435560802 --- /dev/null +++ b/Userland/Libraries/LibUnicode/TimeZone.cpp @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#define AK_DONT_REPLACE_STD + +#include +#include +#include + +#include + +namespace Unicode { + +String current_time_zone() +{ + UErrorCode status = U_ZERO_ERROR; + + auto time_zone = adopt_own_if_nonnull(icu::TimeZone::detectHostTimeZone()); + if (!time_zone) + return "UTC"_string; + + icu::UnicodeString time_zone_id; + time_zone->getID(time_zone_id); + + icu::UnicodeString time_zone_name; + time_zone->getCanonicalID(time_zone_id, time_zone_name, status); + + if (icu_failure(status)) + return "UTC"_string; + + return icu_string_to_string(time_zone_name); +} + +} diff --git a/Userland/Libraries/LibUnicode/TimeZone.h b/Userland/Libraries/LibUnicode/TimeZone.h new file mode 100644 index 00000000000..daf6130484c --- /dev/null +++ b/Userland/Libraries/LibUnicode/TimeZone.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2024, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +#pragma once + +namespace Unicode { + +String current_time_zone(); + +}