mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-09-11 05:25:32 +03:00
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:
parent
d3e809bcd4
commit
4fc0fba646
Notes:
sideshowbarker
2024-07-17 18:06:52 +09:00
Author: https://github.com/trflynn89 Commit: https://github.com/LadybirdBrowser/ladybird/commit/4fc0fba646 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/281
@ -41,3 +41,17 @@ TEST_CASE(current_time_zone)
|
|||||||
EXPECT_EQ(Unicode::current_time_zone(), "UTC"sv);
|
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);
|
||||||
|
}
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
#include <AK/Time.h>
|
#include <AK/Time.h>
|
||||||
#include <LibCore/DateTime.h>
|
#include <LibCore/DateTime.h>
|
||||||
#include <LibTimeZone/TimeZone.h>
|
#include <LibTimeZone/TimeZone.h>
|
||||||
|
#include <LibUnicode/TimeZone.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
@ -19,6 +20,7 @@ namespace Core {
|
|||||||
|
|
||||||
static Optional<StringView> parse_time_zone_name(GenericLexer& lexer)
|
static Optional<StringView> parse_time_zone_name(GenericLexer& lexer)
|
||||||
{
|
{
|
||||||
|
auto const& time_zones = Unicode::available_time_zones();
|
||||||
auto start_position = lexer.tell();
|
auto start_position = lexer.tell();
|
||||||
|
|
||||||
Optional<StringView> canonicalized_time_zone;
|
Optional<StringView> canonicalized_time_zone;
|
||||||
@ -26,8 +28,12 @@ static Optional<StringView> parse_time_zone_name(GenericLexer& lexer)
|
|||||||
lexer.ignore_until([&](auto) {
|
lexer.ignore_until([&](auto) {
|
||||||
auto time_zone = lexer.input().substring_view(start_position, lexer.tell() - start_position + 1);
|
auto time_zone = lexer.input().substring_view(start_position, lexer.tell() - start_position + 1);
|
||||||
|
|
||||||
canonicalized_time_zone = TimeZone::canonicalize_time_zone(time_zone);
|
auto it = time_zones.find_if([&](auto const& candidate) { return time_zone.equals_ignoring_ascii_case(candidate); });
|
||||||
return canonicalized_time_zone.has_value();
|
if (it == time_zones.end())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
canonicalized_time_zone = *it;
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (canonicalized_time_zone.has_value())
|
if (canonicalized_time_zone.has_value())
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2020-2023, Linus Groh <linusg@serenityos.org>
|
* 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
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
@ -11,6 +11,7 @@
|
|||||||
#include <LibJS/Runtime/AbstractOperations.h>
|
#include <LibJS/Runtime/AbstractOperations.h>
|
||||||
#include <LibJS/Runtime/Date.h>
|
#include <LibJS/Runtime/Date.h>
|
||||||
#include <LibJS/Runtime/GlobalObject.h>
|
#include <LibJS/Runtime/GlobalObject.h>
|
||||||
|
#include <LibJS/Runtime/Intl/AbstractOperations.h>
|
||||||
#include <LibJS/Runtime/Temporal/ISO8601.h>
|
#include <LibJS/Runtime/Temporal/ISO8601.h>
|
||||||
#include <LibTimeZone/TimeZone.h>
|
#include <LibTimeZone/TimeZone.h>
|
||||||
#include <LibUnicode/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;
|
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
|
// 21.4.1.24 SystemTimeZoneIdentifier ( ), https://tc39.es/ecma262/#sec-systemtimezoneidentifier
|
||||||
String system_time_zone_identifier()
|
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
|
// 21.4.1.25 LocalTime ( t ), https://tc39.es/ecma262/#sec-localtime
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2020-2022, Linus Groh <linusg@serenityos.org>
|
* 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
|
* 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
|
// 21.4.1.22 Time Zone Identifier Record, https://tc39.es/ecma262/#sec-time-zone-identifier-record
|
||||||
struct TimeZoneIdentifier {
|
struct TimeZoneIdentifier {
|
||||||
StringView identifier; // [[Identifier]]
|
String identifier; // [[Identifier]]
|
||||||
StringView primary_identifier; // [[PrimaryIdentifier]]
|
String primary_identifier; // [[PrimaryIdentifier]]
|
||||||
};
|
};
|
||||||
|
|
||||||
// https://tc39.es/ecma262/#eqn-HoursPerDay
|
// 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);
|
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);
|
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);
|
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();
|
String system_time_zone_identifier();
|
||||||
double local_time(double time);
|
double local_time(double time);
|
||||||
double utc_time(double time);
|
double utc_time(double time);
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
#include <LibJS/Runtime/Intl/SingleUnitIdentifiers.h>
|
#include <LibJS/Runtime/Intl/SingleUnitIdentifiers.h>
|
||||||
#include <LibJS/Runtime/VM.h>
|
#include <LibJS/Runtime/VM.h>
|
||||||
#include <LibJS/Runtime/ValueInlines.h>
|
#include <LibJS/Runtime/ValueInlines.h>
|
||||||
|
#include <LibUnicode/TimeZone.h>
|
||||||
#include <LibUnicode/UnicodeKeywords.h>
|
#include <LibUnicode/UnicodeKeywords.h>
|
||||||
|
|
||||||
namespace JS::Intl {
|
namespace JS::Intl {
|
||||||
@ -138,6 +139,79 @@ bool is_well_formed_currency_code(StringView currency)
|
|||||||
return true;
|
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
|
// 6.6.1 IsWellFormedUnitIdentifier ( unitIdentifier ), https://tc39.es/ecma402/#sec-iswellformedunitidentifier
|
||||||
bool is_well_formed_unit_identifier(StringView unit_identifier)
|
bool is_well_formed_unit_identifier(StringView unit_identifier)
|
||||||
{
|
{
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
#include <AK/Vector.h>
|
#include <AK/Vector.h>
|
||||||
#include <LibJS/Forward.h>
|
#include <LibJS/Forward.h>
|
||||||
#include <LibJS/Runtime/Completion.h>
|
#include <LibJS/Runtime/Completion.h>
|
||||||
|
#include <LibJS/Runtime/Date.h>
|
||||||
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
||||||
#include <LibJS/Runtime/Value.h>
|
#include <LibJS/Runtime/Value.h>
|
||||||
#include <LibUnicode/Locale.h>
|
#include <LibUnicode/Locale.h>
|
||||||
@ -51,6 +52,8 @@ using StringOrBoolean = Variant<StringView, bool>;
|
|||||||
bool is_structurally_valid_language_tag(StringView locale);
|
bool is_structurally_valid_language_tag(StringView locale);
|
||||||
String canonicalize_unicode_locale_id(StringView locale);
|
String canonicalize_unicode_locale_id(StringView locale);
|
||||||
bool is_well_formed_currency_code(StringView currency);
|
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);
|
bool is_well_formed_unit_identifier(StringView unit_identifier);
|
||||||
ThrowCompletionOr<Vector<String>> canonicalize_locale_list(VM&, Value locales);
|
ThrowCompletionOr<Vector<String>> canonicalize_locale_list(VM&, Value locales);
|
||||||
Optional<MatchedLocale> lookup_matching_locale_by_prefix(ReadonlySpan<String> requested_locales);
|
Optional<MatchedLocale> lookup_matching_locale_by_prefix(ReadonlySpan<String> requested_locales);
|
||||||
|
@ -231,15 +231,17 @@ ThrowCompletionOr<NonnullGCPtr<DateTimeFormat>> create_date_time_format(VM& vm,
|
|||||||
// g. Set timeZone to FormatOffsetTimeZoneIdentifier(offsetMinutes).
|
// g. Set timeZone to FormatOffsetTimeZoneIdentifier(offsetMinutes).
|
||||||
time_zone = format_offset_time_zone_identifier(offset_minutes);
|
time_zone = format_offset_time_zone_identifier(offset_minutes);
|
||||||
}
|
}
|
||||||
// 33. Else if IsValidTimeZoneName(timeZone) is true, then
|
// 33. Else
|
||||||
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,
|
|
||||||
else {
|
else {
|
||||||
// a. Throw a RangeError exception.
|
// a. Let timeZoneIdentifierRecord be GetAvailableNamedTimeZoneIdentifier(timeZone).
|
||||||
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, time_zone, vm.names.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.
|
// 35. Set dateTimeFormat.[[TimeZone]] to timeZone.
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
|
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
|
||||||
|
* Copyright (c) 2024, Tim Flynn <trflynn89@serenityos.org>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <AK/QuickSort.h>
|
#include <AK/QuickSort.h>
|
||||||
#include <LibJS/Runtime/Array.h>
|
#include <LibJS/Runtime/Array.h>
|
||||||
|
#include <LibJS/Runtime/Date.h>
|
||||||
#include <LibJS/Runtime/GlobalObject.h>
|
#include <LibJS/Runtime/GlobalObject.h>
|
||||||
#include <LibJS/Runtime/Intl/AbstractOperations.h>
|
#include <LibJS/Runtime/Intl/AbstractOperations.h>
|
||||||
#include <LibJS/Runtime/Intl/CollatorConstructor.h>
|
#include <LibJS/Runtime/Intl/CollatorConstructor.h>
|
||||||
@ -21,7 +23,6 @@
|
|||||||
#include <LibJS/Runtime/Intl/SegmenterConstructor.h>
|
#include <LibJS/Runtime/Intl/SegmenterConstructor.h>
|
||||||
#include <LibJS/Runtime/Intl/SingleUnitIdentifiers.h>
|
#include <LibJS/Runtime/Intl/SingleUnitIdentifiers.h>
|
||||||
#include <LibJS/Runtime/Temporal/TimeZone.h>
|
#include <LibJS/Runtime/Temporal/TimeZone.h>
|
||||||
#include <LibTimeZone/TimeZone.h>
|
|
||||||
#include <LibUnicode/DateTimeFormat.h>
|
#include <LibUnicode/DateTimeFormat.h>
|
||||||
#include <LibUnicode/Locale.h>
|
#include <LibUnicode/Locale.h>
|
||||||
#include <LibUnicode/NumberFormat.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);
|
return Array::create_from(realm, marked_locale_list);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6.5.4 AvailableCanonicalTimeZones ( ), https://tc39.es/ecma402/#sec-availablecanonicaltimezones
|
// 6.5.4 AvailablePrimaryTimeZoneIdentifiers ( ), https://tc39.es/ecma402/#sec-availableprimarytimezoneidentifiers
|
||||||
static Vector<StringView> available_canonical_time_zones()
|
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.
|
// 1. Let records be AvailableNamedTimeZoneIdentifiers().
|
||||||
auto names = TimeZone::all_time_zones();
|
auto const& records = available_named_time_zone_identifiers();
|
||||||
|
|
||||||
// 2. Let result be a new empty List.
|
// 2. Let result be a new empty List.
|
||||||
Vector<StringView> result;
|
Vector<String> result;
|
||||||
|
|
||||||
// 3. For each element name of names, do
|
// 3. For each element timeZoneIdentifierRecord of records, do
|
||||||
for (auto const& name : names) {
|
for (auto const& time_zone_identifier_record : records) {
|
||||||
// a. Assert: IsValidTimeZoneName( name ) is true.
|
// a. If timeZoneIdentifierRecord.[[Identifier]] is timeZoneIdentifierRecord.[[PrimaryIdentifier]], then
|
||||||
// b. Let canonical be ! CanonicalizeTimeZoneName( name ).
|
if (time_zone_identifier_record.identifier == time_zone_identifier_record.primary_identifier) {
|
||||||
auto canonical = TimeZone::canonicalize_time_zone(name.name).value();
|
// i. Append timeZoneIdentifierRecord.[[Identifier]] to result.
|
||||||
|
result.append(time_zone_identifier_record.identifier);
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Sort result in order as if an Array of the same values had been sorted using %Array.prototype.sort% using undefined as comparefn.
|
// 4. Return result.
|
||||||
quick_sort(result);
|
|
||||||
|
|
||||||
// 5. Return result.
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,8 +137,8 @@ JS_DEFINE_NATIVE_FUNCTION(Intl::supported_values_of)
|
|||||||
}
|
}
|
||||||
// 6. Else if key is "timeZone", then
|
// 6. Else if key is "timeZone", then
|
||||||
else if (key == "timeZone"sv) {
|
else if (key == "timeZone"sv) {
|
||||||
// a. Let list be ! AvailableCanonicalTimeZones( ).
|
// a. Let list be ! AvailablePrimaryTimeZoneIdentifiers( ).
|
||||||
static auto const time_zones = available_canonical_time_zones();
|
static auto const time_zones = available_primary_time_zone_identifiers();
|
||||||
list = time_zones.span();
|
list = time_zones.span();
|
||||||
}
|
}
|
||||||
// 7. Else if key is "unit", then
|
// 7. Else if key is "unit", then
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
#include <LibJS/Runtime/Temporal/TimeZoneConstructor.h>
|
#include <LibJS/Runtime/Temporal/TimeZoneConstructor.h>
|
||||||
#include <LibJS/Runtime/Temporal/TimeZoneMethods.h>
|
#include <LibJS/Runtime/Temporal/TimeZoneMethods.h>
|
||||||
#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
|
#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
|
||||||
#include <LibTimeZone/TimeZone.h>
|
#include <LibUnicode/TimeZone.h>
|
||||||
|
|
||||||
namespace JS::Temporal {
|
namespace JS::Temporal {
|
||||||
|
|
||||||
@ -36,28 +36,45 @@ TimeZone::TimeZone(Object& prototype)
|
|||||||
bool is_available_time_zone_name(StringView time_zone)
|
bool is_available_time_zone_name(StringView time_zone)
|
||||||
{
|
{
|
||||||
// 1. Let timeZones be AvailableTimeZones().
|
// 1. Let timeZones be AvailableTimeZones().
|
||||||
|
auto const& time_zones = Unicode::available_time_zones();
|
||||||
|
|
||||||
// 2. For each String candidate in timeZones, do
|
// 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.
|
// 3. Return false.
|
||||||
// NOTE: When LibTimeZone is built without ENABLE_TIME_ZONE_DATA, this only recognizes 'UTC',
|
return false;
|
||||||
// which matches the minimum requirements of the Temporal spec.
|
|
||||||
return ::TimeZone::time_zone_from_string(time_zone).has_value();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6.4.2 CanonicalizeTimeZoneName ( timeZone ), https://tc39.es/ecma402/#sec-canonicalizetimezonename
|
// 6.4.2 CanonicalizeTimeZoneName ( timeZone ), https://tc39.es/ecma402/#sec-canonicalizetimezonename
|
||||||
// 11.1.2 CanonicalizeTimeZoneName ( timeZone ), https://tc39.es/proposal-temporal/#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
|
// 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.
|
auto const& time_zones = Unicode::available_time_zones();
|
||||||
// 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);
|
// 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".
|
// 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.
|
// 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
|
// 11.6.1 CreateTemporalTimeZone ( identifier [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporaltimezone
|
||||||
|
@ -124,13 +124,13 @@ describe("correct behavior", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("timeZone", () => {
|
test("timeZone", () => {
|
||||||
const en = new Intl.DateTimeFormat("en", { timeZone: "EST" });
|
const en = new Intl.DateTimeFormat("en", { timeZone: "EST5EDT" });
|
||||||
expect(en.resolvedOptions().timeZone).toBe("EST");
|
expect(en.resolvedOptions().timeZone).toBe("EST5EDT");
|
||||||
|
|
||||||
const el = new Intl.DateTimeFormat("el", { timeZone: "UTC" });
|
const el = new Intl.DateTimeFormat("el", { timeZone: "UTC" });
|
||||||
expect(el.resolvedOptions().timeZone).toBe("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 });
|
const en = new Intl.DateTimeFormat("en", { timeZone: timeZone });
|
||||||
expect(en.resolvedOptions().timeZone).toBe(timeZone);
|
expect(en.resolvedOptions().timeZone).toBe(timeZone);
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ describe("normal behavior", () => {
|
|||||||
["Etc/GMT+12", "Etc/GMT+12"],
|
["Etc/GMT+12", "Etc/GMT+12"],
|
||||||
["Etc/GMT-12", "Etc/GMT-12"],
|
["Etc/GMT-12", "Etc/GMT-12"],
|
||||||
["Europe/London", "Europe/London"],
|
["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+01", "+01:00"],
|
||||||
["1970-01-01T00:00:00.000000000+01", "+01:00"],
|
["1970-01-01T00:00:00.000000000+01", "+01:00"],
|
||||||
["1970-01-01T00:00:00.000000000+01:00:00", "+01:00"],
|
["1970-01-01T00:00:00.000000000+01:00:00", "+01:00"],
|
||||||
|
@ -47,7 +47,7 @@ describe("normal behavior", () => {
|
|||||||
["Etc/GMT+12", "Etc/GMT+12"],
|
["Etc/GMT+12", "Etc/GMT+12"],
|
||||||
["Etc/GMT-12", "Etc/GMT-12"],
|
["Etc/GMT-12", "Etc/GMT-12"],
|
||||||
["Europe/London", "Europe/London"],
|
["Europe/London", "Europe/London"],
|
||||||
["Europe/Isle_of_Man", "Europe/London"],
|
["Australia/Canberra", "Australia/Sydney"],
|
||||||
];
|
];
|
||||||
for (const [arg, expected] of values) {
|
for (const [arg, expected] of values) {
|
||||||
expect(new Temporal.TimeZone(arg).id).toBe(expected);
|
expect(new Temporal.TimeZone(arg).id).toBe(expected);
|
||||||
|
@ -12,7 +12,7 @@ describe("correct behavior", () => {
|
|||||||
["Etc/UTC", "UTC"],
|
["Etc/UTC", "UTC"],
|
||||||
["Etc/GMT", "UTC"],
|
["Etc/GMT", "UTC"],
|
||||||
["Europe/London", "Europe/London"],
|
["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:00"],
|
["+00:00:00", "+00:00"],
|
||||||
["+00:00:00.000", "+00:00"],
|
["+00:00:00.000", "+00:00"],
|
||||||
|
@ -6,11 +6,14 @@
|
|||||||
|
|
||||||
#define AK_DONT_REPLACE_STD
|
#define AK_DONT_REPLACE_STD
|
||||||
|
|
||||||
|
#include <AK/Array.h>
|
||||||
#include <AK/NonnullOwnPtr.h>
|
#include <AK/NonnullOwnPtr.h>
|
||||||
|
#include <AK/QuickSort.h>
|
||||||
#include <LibUnicode/ICU.h>
|
#include <LibUnicode/ICU.h>
|
||||||
#include <LibUnicode/TimeZone.h>
|
#include <LibUnicode/TimeZone.h>
|
||||||
|
|
||||||
#include <unicode/timezone.h>
|
#include <unicode/timezone.h>
|
||||||
|
#include <unicode/ucal.h>
|
||||||
|
|
||||||
namespace Unicode {
|
namespace Unicode {
|
||||||
|
|
||||||
@ -34,4 +37,76 @@ String current_time_zone()
|
|||||||
return icu_string_to_string(time_zone_name);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,16 @@
|
|||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <AK/Optional.h>
|
||||||
#include <AK/String.h>
|
#include <AK/String.h>
|
||||||
|
#include <AK/Vector.h>
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
namespace Unicode {
|
namespace Unicode {
|
||||||
|
|
||||||
String current_time_zone();
|
String current_time_zone();
|
||||||
|
Vector<String> const& available_time_zones();
|
||||||
|
Optional<String> resolve_primary_time_zone(StringView time_zone);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user