mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 07:17:35 +00:00
LibJS: Implement time zone identifier AOs centrally within Date
This is an editorial change in the ECMA-262 spec. See:
73926a5
The idea here is to reduce duplication of these AOs between ECMA-262,
ECMA-402, and Temporal. This patch contains only the ECMA-262 changes.
This commit is contained in:
parent
0bc401a1d6
commit
f31540e419
5 changed files with 88 additions and 33 deletions
|
@ -411,9 +411,57 @@ 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.24 DefaultTimeZone ( ), https://tc39.es/ecma262/#sec-defaulttimezone
|
// 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
|
||||||
// 6.4.3 DefaultTimeZone ( ), https://tc39.es/ecma402/#sup-defaulttimezone
|
// 6.4.3 DefaultTimeZone ( ), https://tc39.es/ecma402/#sup-defaulttimezone
|
||||||
StringView default_time_zone()
|
StringView system_time_zone_identifier()
|
||||||
{
|
{
|
||||||
return TimeZone::current_time_zone();
|
return TimeZone::current_time_zone();
|
||||||
}
|
}
|
||||||
|
@ -421,21 +469,21 @@ StringView default_time_zone()
|
||||||
// 21.4.1.25 LocalTime ( t ), https://tc39.es/ecma262/#sec-localtime
|
// 21.4.1.25 LocalTime ( t ), https://tc39.es/ecma262/#sec-localtime
|
||||||
double local_time(double time)
|
double local_time(double time)
|
||||||
{
|
{
|
||||||
// 1. Let localTimeZone be DefaultTimeZone().
|
// 1. Let systemTimeZoneIdentifier be SystemTimeZoneIdentifier().
|
||||||
auto local_time_zone = default_time_zone();
|
auto system_time_zone_identifier = JS::system_time_zone_identifier();
|
||||||
|
|
||||||
double offset_nanoseconds { 0 };
|
double offset_nanoseconds { 0 };
|
||||||
|
|
||||||
// 2. If IsTimeZoneOffsetString(localTimeZone) is true, then
|
// 2. If IsTimeZoneOffsetString(systemTimeZoneIdentifier) is true, then
|
||||||
if (is_time_zone_offset_string(local_time_zone)) {
|
if (is_time_zone_offset_string(system_time_zone_identifier)) {
|
||||||
// a. Let offsetNs be ParseTimeZoneOffsetString(localTimeZone).
|
// a. Let offsetNs be ParseTimeZoneOffsetString(systemTimeZoneIdentifier).
|
||||||
offset_nanoseconds = parse_time_zone_offset_string(local_time_zone);
|
offset_nanoseconds = parse_time_zone_offset_string(system_time_zone_identifier);
|
||||||
}
|
}
|
||||||
// 3. Else,
|
// 3. Else,
|
||||||
else {
|
else {
|
||||||
// a. Let offsetNs be GetNamedTimeZoneOffsetNanoseconds(localTimeZone, ℤ(ℝ(t) × 10^6)).
|
// a. Let offsetNs be GetNamedTimeZoneOffsetNanoseconds(systemTimeZoneIdentifier, ℤ(ℝ(t) × 10^6)).
|
||||||
auto time_bigint = Crypto::SignedBigInteger { time }.multiplied_by(s_one_million_bigint);
|
auto time_bigint = Crypto::SignedBigInteger { time }.multiplied_by(s_one_million_bigint);
|
||||||
offset_nanoseconds = get_named_time_zone_offset_nanoseconds(local_time_zone, time_bigint);
|
offset_nanoseconds = get_named_time_zone_offset_nanoseconds(system_time_zone_identifier, time_bigint);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Let offsetMs be truncate(offsetNs / 10^6).
|
// 4. Let offsetMs be truncate(offsetNs / 10^6).
|
||||||
|
@ -448,20 +496,20 @@ double local_time(double time)
|
||||||
// 21.4.1.26 UTC ( t ), https://tc39.es/ecma262/#sec-utc-t
|
// 21.4.1.26 UTC ( t ), https://tc39.es/ecma262/#sec-utc-t
|
||||||
double utc_time(double time)
|
double utc_time(double time)
|
||||||
{
|
{
|
||||||
// 1. Let localTimeZone be DefaultTimeZone().
|
// 1. Let systemTimeZoneIdentifier be SystemTimeZoneIdentifier().
|
||||||
auto local_time_zone = default_time_zone();
|
auto system_time_zone_identifier = JS::system_time_zone_identifier();
|
||||||
|
|
||||||
double offset_nanoseconds { 0 };
|
double offset_nanoseconds { 0 };
|
||||||
|
|
||||||
// 2. If IsTimeZoneOffsetString(localTimeZone) is true, then
|
// 2. If IsTimeZoneOffsetString(systemTimeZoneIdentifier) is true, then
|
||||||
if (is_time_zone_offset_string(local_time_zone)) {
|
if (is_time_zone_offset_string(system_time_zone_identifier)) {
|
||||||
// a. Let offsetNs be ParseTimeZoneOffsetString(localTimeZone).
|
// a. Let offsetNs be ParseTimeZoneOffsetString(systemTimeZoneIdentifier).
|
||||||
offset_nanoseconds = parse_time_zone_offset_string(local_time_zone);
|
offset_nanoseconds = parse_time_zone_offset_string(system_time_zone_identifier);
|
||||||
}
|
}
|
||||||
// 3. Else,
|
// 3. Else,
|
||||||
else {
|
else {
|
||||||
// a. Let possibleInstants be GetNamedTimeZoneEpochNanoseconds(localTimeZone, ℝ(YearFromTime(t)), ℝ(MonthFromTime(t)) + 1, ℝ(DateFromTime(t)), ℝ(HourFromTime(t)), ℝ(MinFromTime(t)), ℝ(SecFromTime(t)), ℝ(msFromTime(t)), 0, 0).
|
// a. Let possibleInstants be GetNamedTimeZoneEpochNanoseconds(systemTimeZoneIdentifier, ℝ(YearFromTime(t)), ℝ(MonthFromTime(t)) + 1, ℝ(DateFromTime(t)), ℝ(HourFromTime(t)), ℝ(MinFromTime(t)), ℝ(SecFromTime(t)), ℝ(msFromTime(t)), 0, 0).
|
||||||
auto possible_instants = get_named_time_zone_epoch_nanoseconds(local_time_zone, year_from_time(time), month_from_time(time) + 1, date_from_time(time), hour_from_time(time), min_from_time(time), sec_from_time(time), ms_from_time(time), 0, 0);
|
auto possible_instants = get_named_time_zone_epoch_nanoseconds(system_time_zone_identifier, year_from_time(time), month_from_time(time) + 1, date_from_time(time), hour_from_time(time), min_from_time(time), sec_from_time(time), ms_from_time(time), 0, 0);
|
||||||
|
|
||||||
// b. NOTE: The following steps ensure that when t represents local time repeating multiple times at a negative time zone transition (e.g. when the daylight saving time ends or the time zone offset is decreased due to a time zone rule change) or skipped local time at a positive time zone transition (e.g. when the daylight saving time starts or the time zone offset is increased due to a time zone rule change), t is interpreted using the time zone offset before the transition.
|
// b. NOTE: The following steps ensure that when t represents local time repeating multiple times at a negative time zone transition (e.g. when the daylight saving time ends or the time zone offset is decreased due to a time zone rule change) or skipped local time at a positive time zone transition (e.g. when the daylight saving time starts or the time zone offset is increased due to a time zone rule change), t is interpreted using the time zone offset before the transition.
|
||||||
Crypto::SignedBigInteger disambiguated_instant;
|
Crypto::SignedBigInteger disambiguated_instant;
|
||||||
|
@ -474,7 +522,7 @@ double utc_time(double time)
|
||||||
// d. Else,
|
// d. Else,
|
||||||
else {
|
else {
|
||||||
// i. NOTE: t represents a local time skipped at a positive time zone transition (e.g. due to daylight saving time starting or a time zone rule change increasing the UTC offset).
|
// i. NOTE: t represents a local time skipped at a positive time zone transition (e.g. due to daylight saving time starting or a time zone rule change increasing the UTC offset).
|
||||||
// ii. Let possibleInstantsBefore be GetNamedTimeZoneEpochNanoseconds(localTimeZone, ℝ(YearFromTime(tBefore)), ℝ(MonthFromTime(tBefore)) + 1, ℝ(DateFromTime(tBefore)), ℝ(HourFromTime(tBefore)), ℝ(MinFromTime(tBefore)), ℝ(SecFromTime(tBefore)), ℝ(msFromTime(tBefore)), 0, 0), where tBefore is the largest integral Number < t for which possibleInstantsBefore is not empty (i.e., tBefore represents the last local time before the transition).
|
// ii. Let possibleInstantsBefore be GetNamedTimeZoneEpochNanoseconds(systemTimeZoneIdentifier, ℝ(YearFromTime(tBefore)), ℝ(MonthFromTime(tBefore)) + 1, ℝ(DateFromTime(tBefore)), ℝ(HourFromTime(tBefore)), ℝ(MinFromTime(tBefore)), ℝ(SecFromTime(tBefore)), ℝ(msFromTime(tBefore)), 0, 0), where tBefore is the largest integral Number < t for which possibleInstantsBefore is not empty (i.e., tBefore represents the last local time before the transition).
|
||||||
// iii. Let disambiguatedInstant be the last element of possibleInstantsBefore.
|
// iii. Let disambiguatedInstant be the last element of possibleInstantsBefore.
|
||||||
|
|
||||||
// FIXME: This branch currently cannot be reached with our implementation, because LibTimeZone does not handle skipped time points.
|
// FIXME: This branch currently cannot be reached with our implementation, because LibTimeZone does not handle skipped time points.
|
||||||
|
@ -482,8 +530,8 @@ double utc_time(double time)
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
|
||||||
// e. Let offsetNs be GetNamedTimeZoneOffsetNanoseconds(localTimeZone, disambiguatedInstant).
|
// e. Let offsetNs be GetNamedTimeZoneOffsetNanoseconds(systemTimeZoneIdentifier, disambiguatedInstant).
|
||||||
offset_nanoseconds = get_named_time_zone_offset_nanoseconds(local_time_zone, disambiguated_instant);
|
offset_nanoseconds = get_named_time_zone_offset_nanoseconds(system_time_zone_identifier, disambiguated_instant);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Let offsetMs be truncate(offsetNs / 10^6).
|
// 4. Let offsetMs be truncate(offsetNs / 10^6).
|
||||||
|
|
|
@ -31,6 +31,12 @@ private:
|
||||||
double m_date_value { 0 }; // [[DateValue]]
|
double m_date_value { 0 }; // [[DateValue]]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 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]]
|
||||||
|
};
|
||||||
|
|
||||||
// https://tc39.es/ecma262/#eqn-HoursPerDay
|
// https://tc39.es/ecma262/#eqn-HoursPerDay
|
||||||
constexpr inline double hours_per_day = 24;
|
constexpr inline double hours_per_day = 24;
|
||||||
// https://tc39.es/ecma262/#eqn-MinutesPerHour
|
// https://tc39.es/ecma262/#eqn-MinutesPerHour
|
||||||
|
@ -67,7 +73,8 @@ 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);
|
||||||
StringView default_time_zone();
|
Vector<TimeZoneIdentifier> available_named_time_zone_identifiers();
|
||||||
|
StringView system_time_zone_identifier();
|
||||||
double local_time(double time);
|
double local_time(double time);
|
||||||
double utc_time(double time);
|
double utc_time(double time);
|
||||||
double make_time(double hour, double min, double sec, double ms);
|
double make_time(double hour, double min, double sec, double ms);
|
||||||
|
|
|
@ -1106,21 +1106,21 @@ DeprecatedString date_string(double time)
|
||||||
// 21.4.4.41.3 TimeZoneString ( tv ), https://tc39.es/ecma262/#sec-timezoneestring
|
// 21.4.4.41.3 TimeZoneString ( tv ), https://tc39.es/ecma262/#sec-timezoneestring
|
||||||
DeprecatedString time_zone_string(double time)
|
DeprecatedString time_zone_string(double time)
|
||||||
{
|
{
|
||||||
// 1. Let localTimeZone be DefaultTimeZone().
|
// 1. Let systemTimeZoneIdentifier be SystemTimeZoneIdentifier().
|
||||||
auto local_time_zone = default_time_zone();
|
auto system_time_zone_identifier = JS::system_time_zone_identifier();
|
||||||
|
|
||||||
double offset_nanoseconds { 0 };
|
double offset_nanoseconds { 0 };
|
||||||
|
|
||||||
// 2. If IsTimeZoneOffsetString(localTimeZone) is true, then
|
// 2. If IsTimeZoneOffsetString(systemTimeZoneIdentifier) is true, then
|
||||||
if (is_time_zone_offset_string(local_time_zone)) {
|
if (is_time_zone_offset_string(system_time_zone_identifier)) {
|
||||||
// a. Let offsetNs be ParseTimeZoneOffsetString(localTimeZone).
|
// a. Let offsetNs be ParseTimeZoneOffsetString(systemTimeZoneIdentifier).
|
||||||
offset_nanoseconds = parse_time_zone_offset_string(local_time_zone);
|
offset_nanoseconds = parse_time_zone_offset_string(system_time_zone_identifier);
|
||||||
}
|
}
|
||||||
// 3. Else,
|
// 3. Else,
|
||||||
else {
|
else {
|
||||||
// a. Let offsetNs be GetNamedTimeZoneOffsetNanoseconds(localTimeZone, ℤ(ℝ(tv) × 10^6)).
|
// a. Let offsetNs be GetNamedTimeZoneOffsetNanoseconds(systemTimeZoneIdentifier, ℤ(ℝ(tv) × 10^6)).
|
||||||
auto time_bigint = Crypto::SignedBigInteger { time }.multiplied_by(Crypto::UnsignedBigInteger { 1'000'000 });
|
auto time_bigint = Crypto::SignedBigInteger { time }.multiplied_by(Crypto::UnsignedBigInteger { 1'000'000 });
|
||||||
offset_nanoseconds = get_named_time_zone_offset_nanoseconds(local_time_zone, time_bigint);
|
offset_nanoseconds = get_named_time_zone_offset_nanoseconds(system_time_zone_identifier, time_bigint);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Let offset be 𝔽(truncate(offsetNs / 106)).
|
// 4. Let offset be 𝔽(truncate(offsetNs / 106)).
|
||||||
|
|
|
@ -170,7 +170,7 @@ ThrowCompletionOr<NonnullGCPtr<DateTimeFormat>> create_date_time_format(VM& vm,
|
||||||
|
|
||||||
// Non-standard, default_hour_cycle will be empty if Unicode data generation is disabled.
|
// Non-standard, default_hour_cycle will be empty if Unicode data generation is disabled.
|
||||||
if (!default_hour_cycle.has_value()) {
|
if (!default_hour_cycle.has_value()) {
|
||||||
date_time_format->set_time_zone(MUST(String::from_utf8(default_time_zone())));
|
date_time_format->set_time_zone(MUST(String::from_utf8(system_time_zone_identifier())));
|
||||||
return date_time_format;
|
return date_time_format;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,7 +217,7 @@ ThrowCompletionOr<NonnullGCPtr<DateTimeFormat>> create_date_time_format(VM& vm,
|
||||||
// 31. If timeZone is undefined, then
|
// 31. If timeZone is undefined, then
|
||||||
if (time_zone_value.is_undefined()) {
|
if (time_zone_value.is_undefined()) {
|
||||||
// a. Set timeZone to DefaultTimeZone().
|
// a. Set timeZone to DefaultTimeZone().
|
||||||
time_zone = MUST(String::from_utf8(default_time_zone()));
|
time_zone = MUST(String::from_utf8(system_time_zone_identifier()));
|
||||||
}
|
}
|
||||||
// 32. Else,
|
// 32. Else,
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -152,7 +152,7 @@ JS_DEFINE_NATIVE_FUNCTION(Now::plain_time_iso)
|
||||||
TimeZone* system_time_zone(VM& vm)
|
TimeZone* system_time_zone(VM& vm)
|
||||||
{
|
{
|
||||||
// 1. Let identifier be ! DefaultTimeZone().
|
// 1. Let identifier be ! DefaultTimeZone().
|
||||||
auto identifier = default_time_zone();
|
auto identifier = system_time_zone_identifier();
|
||||||
|
|
||||||
// 2. Return ! CreateTemporalTimeZone(identifier).
|
// 2. Return ! CreateTemporalTimeZone(identifier).
|
||||||
// FIXME: Propagate possible OOM error
|
// FIXME: Propagate possible OOM error
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue