LibCore+LibJS+LibUnicode: Port retrieving available time zones to ICU

This required updating some LibJS spec steps to their latest versions,
as the data expected by the old steps does not quite match the APIs that
are available with the ICU. The new spec steps are much more aligned.
This commit is contained in:
Timothy Flynn 2024-06-25 11:06:08 -04:00 committed by Andreas Kling
parent d3e809bcd4
commit 4fc0fba646
Notes: sideshowbarker 2024-07-17 18:06:52 +09:00
15 changed files with 259 additions and 103 deletions

View file

@ -41,3 +41,17 @@ TEST_CASE(current_time_zone)
EXPECT_EQ(Unicode::current_time_zone(), "UTC"sv);
}
}
TEST_CASE(available_time_zones)
{
auto const& time_zones = Unicode::available_time_zones();
EXPECT(time_zones.contains_slow("UTC"sv));
EXPECT(!time_zones.contains_slow("EAT"sv));
}
TEST_CASE(resolve_primary_time_zone)
{
EXPECT_EQ(Unicode::resolve_primary_time_zone("UTC"sv), "Etc/UTC"sv);
EXPECT_EQ(Unicode::resolve_primary_time_zone("Asia/Katmandu"sv), "Asia/Kathmandu"sv);
EXPECT_EQ(Unicode::resolve_primary_time_zone("Australia/Canberra"sv), "Australia/Sydney"sv);
}

View file

@ -12,6 +12,7 @@
#include <AK/Time.h>
#include <LibCore/DateTime.h>
#include <LibTimeZone/TimeZone.h>
#include <LibUnicode/TimeZone.h>
#include <errno.h>
#include <time.h>
@ -19,6 +20,7 @@ namespace Core {
static Optional<StringView> parse_time_zone_name(GenericLexer& lexer)
{
auto const& time_zones = Unicode::available_time_zones();
auto start_position = lexer.tell();
Optional<StringView> canonicalized_time_zone;
@ -26,8 +28,12 @@ static Optional<StringView> parse_time_zone_name(GenericLexer& lexer)
lexer.ignore_until([&](auto) {
auto time_zone = lexer.input().substring_view(start_position, lexer.tell() - start_position + 1);
canonicalized_time_zone = TimeZone::canonicalize_time_zone(time_zone);
return canonicalized_time_zone.has_value();
auto it = time_zones.find_if([&](auto const& candidate) { return time_zone.equals_ignoring_ascii_case(candidate); });
if (it == time_zones.end())
return false;
canonicalized_time_zone = *it;
return true;
});
if (canonicalized_time_zone.has_value())

View file

@ -1,6 +1,6 @@
/*
* Copyright (c) 2020-2023, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2022-2023, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2022-2024, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -11,6 +11,7 @@
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Date.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Intl/AbstractOperations.h>
#include <LibJS/Runtime/Temporal/ISO8601.h>
#include <LibTimeZone/TimeZone.h>
#include <LibUnicode/TimeZone.h>
@ -416,58 +417,25 @@ i64 get_named_time_zone_offset_nanoseconds(StringView time_zone_identifier, Cryp
return offset->seconds * 1'000'000'000;
}
// 21.4.1.23 AvailableNamedTimeZoneIdentifiers ( ), https://tc39.es/ecma262/#sec-time-zone-identifier-record
Vector<TimeZoneIdentifier> available_named_time_zone_identifiers()
{
// 1. If the implementation does not include local political rules for any time zones, then
// a. Return « the Time Zone Identifier Record { [[Identifier]]: "UTC", [[PrimaryIdentifier]]: "UTC" } ».
// NOTE: This step is not applicable as LibTimeZone will always return at least UTC, even if the TZDB is disabled.
// 2. Let identifiers be the List of unique available named time zone identifiers.
auto identifiers = TimeZone::all_time_zones();
// 3. Sort identifiers into the same order as if an Array of the same values had been sorted using %Array.prototype.sort% with undefined as comparefn.
// NOTE: LibTimeZone provides the identifiers already sorted.
// 4. Let result be a new empty List.
Vector<TimeZoneIdentifier> result;
result.ensure_capacity(identifiers.size());
bool found_utc = false;
// 5. For each element identifier of identifiers, do
for (auto identifier : identifiers) {
// a. Let primary be identifier.
auto primary = identifier.name;
// b. If identifier is a non-primary time zone identifier in this implementation and identifier is not "UTC", then
if (identifier.is_link == TimeZone::IsLink::Yes && identifier.name != "UTC"sv) {
// i. Set primary to the primary time zone identifier associated with identifier.
// ii. NOTE: An implementation may need to resolve identifier iteratively to obtain the primary time zone identifier.
primary = TimeZone::canonicalize_time_zone(identifier.name).value();
}
// c. Let record be the Time Zone Identifier Record { [[Identifier]]: identifier, [[PrimaryIdentifier]]: primary }.
TimeZoneIdentifier record { .identifier = identifier.name, .primary_identifier = primary };
// d. Append record to result.
result.unchecked_append(record);
if (!found_utc && identifier.name == "UTC"sv && primary == "UTC"sv)
found_utc = true;
}
// 6. Assert: result contains a Time Zone Identifier Record r such that r.[[Identifier]] is "UTC" and r.[[PrimaryIdentifier]] is "UTC".
VERIFY(found_utc);
// 7. Return result.
return result;
}
// 21.4.1.24 SystemTimeZoneIdentifier ( ), https://tc39.es/ecma262/#sec-systemtimezoneidentifier
String system_time_zone_identifier()
{
return Unicode::current_time_zone();
// 1. If the implementation only supports the UTC time zone, return "UTC".
// 2. Let systemTimeZoneString be the String representing the host environment's current time zone, either a primary
// time zone identifier or an offset time zone identifier.
auto system_time_zone_string = Unicode::current_time_zone();
if (!is_time_zone_offset_string(system_time_zone_string)) {
auto time_zone_identifier = Intl::get_available_named_time_zone_identifier(system_time_zone_string);
if (!time_zone_identifier.has_value())
return "UTC"_string;
system_time_zone_string = time_zone_identifier->primary_identifier;
}
// 3. Return systemTimeZoneString.
return system_time_zone_string;
}
// 21.4.1.25 LocalTime ( t ), https://tc39.es/ecma262/#sec-localtime

View file

@ -1,6 +1,6 @@
/*
* Copyright (c) 2020-2022, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2022-2023, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2022-2024, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -35,8 +35,8 @@ private:
// 21.4.1.22 Time Zone Identifier Record, https://tc39.es/ecma262/#sec-time-zone-identifier-record
struct TimeZoneIdentifier {
StringView identifier; // [[Identifier]]
StringView primary_identifier; // [[PrimaryIdentifier]]
String identifier; // [[Identifier]]
String primary_identifier; // [[PrimaryIdentifier]]
};
// https://tc39.es/ecma262/#eqn-HoursPerDay
@ -75,7 +75,6 @@ u16 ms_from_time(double);
Crypto::SignedBigInteger get_utc_epoch_nanoseconds(i32 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond);
Vector<Crypto::SignedBigInteger> 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<TimeZoneIdentifier> available_named_time_zone_identifiers();
String system_time_zone_identifier();
double local_time(double time);
double utc_time(double time);

View file

@ -16,6 +16,7 @@
#include <LibJS/Runtime/Intl/SingleUnitIdentifiers.h>
#include <LibJS/Runtime/VM.h>
#include <LibJS/Runtime/ValueInlines.h>
#include <LibUnicode/TimeZone.h>
#include <LibUnicode/UnicodeKeywords.h>
namespace JS::Intl {
@ -138,6 +139,79 @@ bool is_well_formed_currency_code(StringView currency)
return true;
}
// 6.5.1 AvailableNamedTimeZoneIdentifiers ( ), https://tc39.es/ecma402/#sup-availablenamedtimezoneidentifiers
Vector<TimeZoneIdentifier> const& available_named_time_zone_identifiers()
{
// It is recommended that the result of AvailableNamedTimeZoneIdentifiers remains the same for the lifetime of the surrounding agent.
static auto named_time_zone_identifiers = []() {
// 1. Let identifiers be a List containing the String value of each Zone or Link name in the IANA Time Zone Database.
auto const& identifiers = Unicode::available_time_zones();
// 2. Assert: No element of identifiers is an ASCII-case-insensitive match for any other element.
// 3. Assert: Every element of identifiers identifies a Zone or Link name in the IANA Time Zone Database.
// 4. Sort identifiers according to lexicographic code unit order.
// NOTE: All of the above is handled by LibUnicode.
// 5. Let result be a new empty List.
Vector<TimeZoneIdentifier> result;
result.ensure_capacity(identifiers.size());
bool found_utc = false;
// 6. For each element identifier of identifiers, do
for (auto const& identifier : identifiers) {
// a. Let primary be identifier.
auto primary = identifier;
// b. If identifier is a Link name and identifier is not "UTC", then
if (identifier != "UTC"sv) {
if (auto resolved = Unicode::resolve_primary_time_zone(identifier); resolved.has_value() && identifier != resolved) {
// i. Set primary to the Zone name that identifier resolves to, according to the rules for resolving Link
// names in the IANA Time Zone Database.
primary = resolved.release_value();
// ii. NOTE: An implementation may need to resolve identifier iteratively.
}
}
// c. If primary is one of "Etc/UTC", "Etc/GMT", or "GMT", set primary to "UTC".
if (primary.is_one_of("Etc/UTC"sv, "Etc/GMT"sv, "GMT"sv))
primary = "UTC"_string;
// d. Let record be the Time Zone Identifier Record { [[Identifier]]: identifier, [[PrimaryIdentifier]]: primary }.
TimeZoneIdentifier record { .identifier = identifier, .primary_identifier = primary };
// e. Append record to result.
result.unchecked_append(move(record));
if (!found_utc && identifier == "UTC"sv && primary == "UTC"sv)
found_utc = true;
}
// 7. Assert: result contains a Time Zone Identifier Record r such that r.[[Identifier]] is "UTC" and r.[[PrimaryIdentifier]] is "UTC".
VERIFY(found_utc);
// 8. Return result.
return result;
}();
return named_time_zone_identifiers;
}
// 6.5.2 GetAvailableNamedTimeZoneIdentifier ( timeZoneIdentifier ), https://tc39.es/ecma402/#sec-getavailablenamedtimezoneidentifier
Optional<TimeZoneIdentifier const&> get_available_named_time_zone_identifier(StringView time_zone_identifier)
{
// 1. For each element record of AvailableNamedTimeZoneIdentifiers(), do
for (auto const& record : available_named_time_zone_identifiers()) {
// a. If record.[[Identifier]] is an ASCII-case-insensitive match for timeZoneIdentifier, return record.
if (record.identifier.equals_ignoring_ascii_case(time_zone_identifier))
return record;
}
// 2. Return EMPTY.
return {};
}
// 6.6.1 IsWellFormedUnitIdentifier ( unitIdentifier ), https://tc39.es/ecma402/#sec-iswellformedunitidentifier
bool is_well_formed_unit_identifier(StringView unit_identifier)
{

View file

@ -12,6 +12,7 @@
#include <AK/Vector.h>
#include <LibJS/Forward.h>
#include <LibJS/Runtime/Completion.h>
#include <LibJS/Runtime/Date.h>
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
#include <LibJS/Runtime/Value.h>
#include <LibUnicode/Locale.h>
@ -51,6 +52,8 @@ using StringOrBoolean = Variant<StringView, bool>;
bool is_structurally_valid_language_tag(StringView locale);
String canonicalize_unicode_locale_id(StringView locale);
bool is_well_formed_currency_code(StringView currency);
Vector<TimeZoneIdentifier> const& available_named_time_zone_identifiers();
Optional<TimeZoneIdentifier const&> get_available_named_time_zone_identifier(StringView time_zone_identifier);
bool is_well_formed_unit_identifier(StringView unit_identifier);
ThrowCompletionOr<Vector<String>> canonicalize_locale_list(VM&, Value locales);
Optional<MatchedLocale> lookup_matching_locale_by_prefix(ReadonlySpan<String> requested_locales);

View file

@ -231,15 +231,17 @@ ThrowCompletionOr<NonnullGCPtr<DateTimeFormat>> create_date_time_format(VM& vm,
// g. Set timeZone to FormatOffsetTimeZoneIdentifier(offsetMinutes).
time_zone = format_offset_time_zone_identifier(offset_minutes);
}
// 33. Else if IsValidTimeZoneName(timeZone) is true, then
else if (Temporal::is_available_time_zone_name(time_zone)) {
// a. Set timeZone to CanonicalizeTimeZoneName(timeZone).
time_zone = MUST(Temporal::canonicalize_time_zone_name(vm, time_zone));
}
// 34. Else,
// 33. Else
else {
// a. Throw a RangeError exception.
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, time_zone, vm.names.timeZone);
// a. Let timeZoneIdentifierRecord be GetAvailableNamedTimeZoneIdentifier(timeZone).
auto time_zone_identifier_record = get_available_named_time_zone_identifier(time_zone);
// b. If timeZoneIdentifierRecord is EMPTY, throw a RangeError exception.
if (!time_zone_identifier_record.has_value())
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, time_zone, vm.names.timeZone);
// c. Set timeZone to timeZoneIdentifierRecord.[[PrimaryIdentifier]].
time_zone = time_zone_identifier_record->primary_identifier;
}
// 35. Set dateTimeFormat.[[TimeZone]] to timeZone.

View file

@ -1,11 +1,13 @@
/*
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2024, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/QuickSort.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/Date.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Intl/AbstractOperations.h>
#include <LibJS/Runtime/Intl/CollatorConstructor.h>
@ -21,7 +23,6 @@
#include <LibJS/Runtime/Intl/SegmenterConstructor.h>
#include <LibJS/Runtime/Intl/SingleUnitIdentifiers.h>
#include <LibJS/Runtime/Temporal/TimeZone.h>
#include <LibTimeZone/TimeZone.h>
#include <LibUnicode/DateTimeFormat.h>
#include <LibUnicode/Locale.h>
#include <LibUnicode/NumberFormat.h>
@ -82,32 +83,25 @@ JS_DEFINE_NATIVE_FUNCTION(Intl::get_canonical_locales)
return Array::create_from(realm, marked_locale_list);
}
// 6.5.4 AvailableCanonicalTimeZones ( ), https://tc39.es/ecma402/#sec-availablecanonicaltimezones
static Vector<StringView> available_canonical_time_zones()
// 6.5.4 AvailablePrimaryTimeZoneIdentifiers ( ), https://tc39.es/ecma402/#sec-availableprimarytimezoneidentifiers
static Vector<String> available_primary_time_zone_identifiers()
{
// 1. Let names be a List of all supported Zone and Link names in the IANA Time Zone Database.
auto names = TimeZone::all_time_zones();
// 1. Let records be AvailableNamedTimeZoneIdentifiers().
auto const& records = available_named_time_zone_identifiers();
// 2. Let result be a new empty List.
Vector<StringView> result;
Vector<String> result;
// 3. For each element name of names, do
for (auto const& name : names) {
// a. Assert: IsValidTimeZoneName( name ) is true.
// b. Let canonical be ! CanonicalizeTimeZoneName( name ).
auto canonical = TimeZone::canonicalize_time_zone(name.name).value();
// c. If result does not contain an element equal to canonical, then
if (!result.contains_slow(canonical)) {
// i. Append canonical to the end of result.
result.append(canonical);
// 3. For each element timeZoneIdentifierRecord of records, do
for (auto const& time_zone_identifier_record : records) {
// a. If timeZoneIdentifierRecord.[[Identifier]] is timeZoneIdentifierRecord.[[PrimaryIdentifier]], then
if (time_zone_identifier_record.identifier == time_zone_identifier_record.primary_identifier) {
// i. Append timeZoneIdentifierRecord.[[Identifier]] to result.
result.append(time_zone_identifier_record.identifier);
}
}
// 4. Sort result in order as if an Array of the same values had been sorted using %Array.prototype.sort% using undefined as comparefn.
quick_sort(result);
// 5. Return result.
// 4. Return result.
return result;
}
@ -143,8 +137,8 @@ JS_DEFINE_NATIVE_FUNCTION(Intl::supported_values_of)
}
// 6. Else if key is "timeZone", then
else if (key == "timeZone"sv) {
// a. Let list be ! AvailableCanonicalTimeZones( ).
static auto const time_zones = available_canonical_time_zones();
// a. Let list be ! AvailablePrimaryTimeZoneIdentifiers( ).
static auto const time_zones = available_primary_time_zone_identifiers();
list = time_zones.span();
}
// 7. Else if key is "unit", then

View file

@ -20,7 +20,7 @@
#include <LibJS/Runtime/Temporal/TimeZoneConstructor.h>
#include <LibJS/Runtime/Temporal/TimeZoneMethods.h>
#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
#include <LibTimeZone/TimeZone.h>
#include <LibUnicode/TimeZone.h>
namespace JS::Temporal {
@ -36,28 +36,45 @@ TimeZone::TimeZone(Object& prototype)
bool is_available_time_zone_name(StringView time_zone)
{
// 1. Let timeZones be AvailableTimeZones().
auto const& time_zones = Unicode::available_time_zones();
// 2. For each String candidate in timeZones, do
// a. If timeZone is an ASCII-case-insensitive match for candidate, return true.
for (auto const& candidate : time_zones) {
// a. If timeZone is an ASCII-case-insensitive match for candidate, return true.
if (time_zone.equals_ignoring_ascii_case(candidate))
return true;
}
// 3. Return false.
// NOTE: When LibTimeZone is built without ENABLE_TIME_ZONE_DATA, this only recognizes 'UTC',
// which matches the minimum requirements of the Temporal spec.
return ::TimeZone::time_zone_from_string(time_zone).has_value();
return false;
}
// 6.4.2 CanonicalizeTimeZoneName ( timeZone ), https://tc39.es/ecma402/#sec-canonicalizetimezonename
// 11.1.2 CanonicalizeTimeZoneName ( timeZone ), https://tc39.es/proposal-temporal/#sec-canonicalizetimezonename
// 15.1.2 CanonicalizeTimeZoneName ( timeZone ), https://tc39.es/proposal-temporal/#sup-canonicalizetimezonename
ThrowCompletionOr<String> canonicalize_time_zone_name(VM& vm, StringView time_zone)
ThrowCompletionOr<String> canonicalize_time_zone_name(VM&, StringView time_zone)
{
// 1. Let ianaTimeZone be the String value of the Zone or Link name of the IANA Time Zone Database that is an ASCII-case-insensitive match of timeZone as described in 6.1.
// 2. If ianaTimeZone is a Link name, let ianaTimeZone be the String value of the corresponding Zone name as specified in the file backward of the IANA Time Zone Database.
auto iana_time_zone = ::TimeZone::canonicalize_time_zone(time_zone);
auto const& time_zones = Unicode::available_time_zones();
// 1. Let ianaTimeZone be the String value of the Zone or Link name of the IANA Time Zone Database that is an
// ASCII-case-insensitive match of timeZone as described in 6.1.
auto it = time_zones.find_if([&](auto const& candidate) {
return time_zone.equals_ignoring_ascii_case(candidate);
});
VERIFY(it != time_zones.end());
// 2. If ianaTimeZone is a Link name, let ianaTimeZone be the String value of the corresponding Zone name as specified
// in the file backward of the IANA Time Zone Database.
auto iana_time_zone = Unicode::resolve_primary_time_zone(*it).value_or_lazy_evaluated([&]() {
return MUST(String::from_utf8(time_zone));
});
// 3. If ianaTimeZone is one of "Etc/UTC", "Etc/GMT", or "GMT", return "UTC".
// NOTE: This is already done in canonicalize_time_zone().
if (iana_time_zone.is_one_of("Etc/UTC"sv, "Etc/GMT"sv, "GMT"sv))
return "UTC"_string;
// 4. Return ianaTimeZone.
return TRY_OR_THROW_OOM(vm, String::from_utf8(*iana_time_zone));
return iana_time_zone;
}
// 11.6.1 CreateTemporalTimeZone ( identifier [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporaltimezone

View file

@ -124,13 +124,13 @@ describe("correct behavior", () => {
});
test("timeZone", () => {
const en = new Intl.DateTimeFormat("en", { timeZone: "EST" });
expect(en.resolvedOptions().timeZone).toBe("EST");
const en = new Intl.DateTimeFormat("en", { timeZone: "EST5EDT" });
expect(en.resolvedOptions().timeZone).toBe("EST5EDT");
const el = new Intl.DateTimeFormat("el", { timeZone: "UTC" });
expect(el.resolvedOptions().timeZone).toBe("UTC");
["UTC", "EST", "+01:02", "-20:30", "+00:00"].forEach(timeZone => {
["UTC", "EST5EDT", "+01:02", "-20:30", "+00:00"].forEach(timeZone => {
const en = new Intl.DateTimeFormat("en", { timeZone: timeZone });
expect(en.resolvedOptions().timeZone).toBe(timeZone);

View file

@ -26,7 +26,7 @@ describe("normal behavior", () => {
["Etc/GMT+12", "Etc/GMT+12"],
["Etc/GMT-12", "Etc/GMT-12"],
["Europe/London", "Europe/London"],
["Europe/Isle_of_Man", "Europe/London"],
["Australia/Canberra", "Australia/Sydney"],
["1970-01-01T00:00:00+01", "+01:00"],
["1970-01-01T00:00:00.000000000+01", "+01:00"],
["1970-01-01T00:00:00.000000000+01:00:00", "+01:00"],

View file

@ -47,7 +47,7 @@ describe("normal behavior", () => {
["Etc/GMT+12", "Etc/GMT+12"],
["Etc/GMT-12", "Etc/GMT-12"],
["Europe/London", "Europe/London"],
["Europe/Isle_of_Man", "Europe/London"],
["Australia/Canberra", "Australia/Sydney"],
];
for (const [arg, expected] of values) {
expect(new Temporal.TimeZone(arg).id).toBe(expected);

View file

@ -12,7 +12,7 @@ describe("correct behavior", () => {
["Etc/UTC", "UTC"],
["Etc/GMT", "UTC"],
["Europe/London", "Europe/London"],
["Europe/Isle_of_Man", "Europe/London"],
["Australia/Canberra", "Australia/Sydney"],
["+00:00", "+00:00"],
["+00:00:00", "+00:00"],
["+00:00:00.000", "+00:00"],

View file

@ -6,11 +6,14 @@
#define AK_DONT_REPLACE_STD
#include <AK/Array.h>
#include <AK/NonnullOwnPtr.h>
#include <AK/QuickSort.h>
#include <LibUnicode/ICU.h>
#include <LibUnicode/TimeZone.h>
#include <unicode/timezone.h>
#include <unicode/ucal.h>
namespace Unicode {
@ -34,4 +37,76 @@ String current_time_zone()
return icu_string_to_string(time_zone_name);
}
// https://github.com/unicode-org/icu/blob/main/icu4c/source/tools/tzcode/icuzones
static constexpr bool is_legacy_non_iana_time_zone(StringView time_zone)
{
constexpr auto legacy_zones = to_array({
"ACT"sv,
"AET"sv,
"AGT"sv,
"ART"sv,
"AST"sv,
"BET"sv,
"BST"sv,
"Canada/East-Saskatchewan"sv,
"CAT"sv,
"CNT"sv,
"CST"sv,
"CTT"sv,
"EAT"sv,
"ECT"sv,
"IET"sv,
"IST"sv,
"JST"sv,
"MIT"sv,
"NET"sv,
"NST"sv,
"PLT"sv,
"PNT"sv,
"PRT"sv,
"PST"sv,
"SST"sv,
"US/Pacific-New"sv,
"VST"sv,
});
if (time_zone.starts_with("SystemV/"sv))
return true;
return legacy_zones.contains_slow(time_zone);
}
Vector<String> const& available_time_zones()
{
static auto time_zones = []() -> Vector<String> {
UErrorCode status = U_ZERO_ERROR;
auto time_zone_enumerator = adopt_own_if_nonnull(icu::TimeZone::createEnumeration(status));
if (icu_failure(status))
return { "UTC"_string };
auto time_zones = icu_string_enumeration_to_list(move(time_zone_enumerator), [](char const* zone) {
return !is_legacy_non_iana_time_zone({ zone, strlen(zone) });
});
quick_sort(time_zones);
return time_zones;
}();
return time_zones;
}
Optional<String> resolve_primary_time_zone(StringView time_zone)
{
UErrorCode status = U_ZERO_ERROR;
icu::UnicodeString iana_id;
icu::TimeZone::getIanaID(icu_string(time_zone), iana_id, status);
if (icu_failure(status))
return {};
return icu_string_to_string(iana_id);
}
}

View file

@ -4,12 +4,16 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Optional.h>
#include <AK/String.h>
#include <AK/Vector.h>
#pragma once
namespace Unicode {
String current_time_zone();
Vector<String> const& available_time_zones();
Optional<String> resolve_primary_time_zone(StringView time_zone);
}