2024-11-20 12:59:15 -05:00
|
|
|
|
/*
|
|
|
|
|
|
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
|
|
|
|
|
|
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
|
2026-02-14 10:35:01 -05:00
|
|
|
|
* Copyright (c) 2024-2026, Tim Flynn <trflynn89@ladybird.org>
|
2024-11-20 12:59:15 -05:00
|
|
|
|
*
|
|
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
#include <LibJS/Runtime/AbstractOperations.h>
|
|
|
|
|
|
#include <LibJS/Runtime/Date.h>
|
|
|
|
|
|
#include <LibJS/Runtime/Intl/AbstractOperations.h>
|
2024-11-21 13:21:29 -05:00
|
|
|
|
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
2024-11-24 15:46:43 -05:00
|
|
|
|
#include <LibJS/Runtime/Temporal/DateEquations.h>
|
2026-03-08 15:56:24 -04:00
|
|
|
|
#include <LibJS/Runtime/Temporal/Duration.h>
|
2024-11-20 12:59:15 -05:00
|
|
|
|
#include <LibJS/Runtime/Temporal/ISO8601.h>
|
2024-11-21 13:21:29 -05:00
|
|
|
|
#include <LibJS/Runtime/Temporal/Instant.h>
|
2024-11-24 15:46:43 -05:00
|
|
|
|
#include <LibJS/Runtime/Temporal/PlainDate.h>
|
2024-11-21 13:21:29 -05:00
|
|
|
|
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
|
2024-11-24 15:46:43 -05:00
|
|
|
|
#include <LibJS/Runtime/Temporal/PlainTime.h>
|
2024-11-20 12:59:15 -05:00
|
|
|
|
#include <LibJS/Runtime/Temporal/TimeZone.h>
|
2024-11-24 20:42:47 -05:00
|
|
|
|
#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
|
2024-11-20 12:59:15 -05:00
|
|
|
|
#include <LibJS/Runtime/VM.h>
|
2026-01-14 14:07:20 +00:00
|
|
|
|
#include <LibUnicode/TimeZone.h>
|
2024-11-20 12:59:15 -05:00
|
|
|
|
|
|
|
|
|
|
namespace JS::Temporal {
|
|
|
|
|
|
|
2026-02-18 12:37:01 -05:00
|
|
|
|
String UTC_TIME_ZONE = "UTC"_string;
|
|
|
|
|
|
|
2024-11-24 15:46:43 -05:00
|
|
|
|
// 11.1.2 GetISOPartsFromEpoch ( epochNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-getisopartsfromepoch
|
|
|
|
|
|
ISODateTime get_iso_parts_from_epoch(Crypto::SignedBigInteger const& epoch_nanoseconds)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 1. Assert: IsValidEpochNanoseconds(ℤ(epochNanoseconds)) is true.
|
|
|
|
|
|
VERIFY(is_valid_epoch_nanoseconds(epoch_nanoseconds));
|
|
|
|
|
|
|
|
|
|
|
|
// 2. Let remainderNs be epochNanoseconds modulo 10**6.
|
|
|
|
|
|
auto remainder_nanoseconds = modulo(epoch_nanoseconds, NANOSECONDS_PER_MILLISECOND);
|
|
|
|
|
|
auto remainder_nanoseconds_value = remainder_nanoseconds.to_double();
|
|
|
|
|
|
|
|
|
|
|
|
// 3. Let epochMilliseconds be 𝔽((epochNanoseconds - remainderNs) / 10**6).
|
|
|
|
|
|
auto epoch_milliseconds = epoch_nanoseconds.minus(remainder_nanoseconds).divided_by(NANOSECONDS_PER_MILLISECOND).quotient.to_double();
|
|
|
|
|
|
|
|
|
|
|
|
// 4. Let year be EpochTimeToEpochYear(epochMilliseconds).
|
|
|
|
|
|
auto year = epoch_time_to_epoch_year(epoch_milliseconds);
|
|
|
|
|
|
|
|
|
|
|
|
// 5. Let month be EpochTimeToMonthInYear(epochMilliseconds) + 1.
|
|
|
|
|
|
auto month = epoch_time_to_month_in_year(epoch_milliseconds) + 1;
|
|
|
|
|
|
|
|
|
|
|
|
// 6. Let day be EpochTimeToDate(epochMilliseconds).
|
|
|
|
|
|
auto day = epoch_time_to_date(epoch_milliseconds);
|
|
|
|
|
|
|
|
|
|
|
|
// 7. Let hour be ℝ(HourFromTime(epochMilliseconds)).
|
|
|
|
|
|
auto hour = hour_from_time(epoch_milliseconds);
|
|
|
|
|
|
|
|
|
|
|
|
// 8. Let minute be ℝ(MinFromTime(epochMilliseconds)).
|
|
|
|
|
|
auto minute = min_from_time(epoch_milliseconds);
|
|
|
|
|
|
|
|
|
|
|
|
// 9. Let second be ℝ(SecFromTime(epochMilliseconds)).
|
|
|
|
|
|
auto second = sec_from_time(epoch_milliseconds);
|
|
|
|
|
|
|
|
|
|
|
|
// 10. Let millisecond be ℝ(msFromTime(epochMilliseconds)).
|
|
|
|
|
|
auto millisecond = ms_from_time(epoch_milliseconds);
|
|
|
|
|
|
|
|
|
|
|
|
// 11. Let microsecond be floor(remainderNs / 1000).
|
|
|
|
|
|
auto microsecond = floor(remainder_nanoseconds_value / 1000.0);
|
|
|
|
|
|
|
|
|
|
|
|
// 12. Assert: microsecond < 1000.
|
|
|
|
|
|
VERIFY(microsecond < 1000.0);
|
|
|
|
|
|
|
|
|
|
|
|
// 13. Let nanosecond be remainderNs modulo 1000.
|
|
|
|
|
|
auto nanosecond = modulo(remainder_nanoseconds_value, 1000.0);
|
|
|
|
|
|
|
|
|
|
|
|
// 14. Let isoDate be CreateISODateRecord(year, month, day).
|
|
|
|
|
|
auto iso_date = create_iso_date_record(year, month, day);
|
|
|
|
|
|
|
|
|
|
|
|
// 15. Let time be CreateTimeRecord(hour, minute, second, millisecond, microsecond, nanosecond).
|
|
|
|
|
|
auto time = create_time_record(hour, minute, second, millisecond, microsecond, nanosecond);
|
|
|
|
|
|
|
|
|
|
|
|
// 16. Return CombineISODateAndTimeRecord(isoDate, time).
|
|
|
|
|
|
return combine_iso_date_and_time_record(iso_date, time);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-25 13:51:48 -05:00
|
|
|
|
// 11.1.3 GetNamedTimeZoneNextTransition ( timeZoneIdentifier, epochNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-getnamedtimezonenexttransition
|
2026-02-18 12:37:01 -05:00
|
|
|
|
Optional<Crypto::SignedBigInteger> get_named_time_zone_next_transition(String const& time_zone, Crypto::SignedBigInteger const& epoch_nanoseconds)
|
2024-11-25 13:51:48 -05:00
|
|
|
|
{
|
2026-01-14 14:07:20 +00:00
|
|
|
|
auto epoch_milliseconds = big_floor(epoch_nanoseconds, NANOSECONDS_PER_MILLISECOND);
|
|
|
|
|
|
auto time = UnixDateTime::from_milliseconds_since_epoch(clip_bigint_to_sane_time(epoch_milliseconds));
|
2024-11-25 13:51:48 -05:00
|
|
|
|
|
2026-01-14 14:07:20 +00:00
|
|
|
|
auto options = Unicode::TimeZoneTransition::Options {
|
|
|
|
|
|
.direction = Unicode::TimeZoneTransition::Options::Direction::Next,
|
|
|
|
|
|
.include_given_time = Unicode::TimeZoneTransition::Options::IncludeGivenTime::No,
|
|
|
|
|
|
.transition_rule = Unicode::TimeZoneTransition::Options::TransitionRule::TransitionWhereUTCOffsetChanges,
|
|
|
|
|
|
};
|
|
|
|
|
|
auto time_zone_transition = Unicode::get_time_zone_transition(time_zone, time, options);
|
|
|
|
|
|
|
|
|
|
|
|
if (!time_zone_transition.has_value())
|
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
|
|
auto result_nanoseconds = Crypto::SignedBigInteger { time_zone_transition->transition.to_milliseconds() }.multiplied_by(NANOSECONDS_PER_MILLISECOND);
|
|
|
|
|
|
if (result_nanoseconds > NANOSECONDS_MAX_INSTANT)
|
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
|
|
return result_nanoseconds;
|
2024-11-25 13:51:48 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 11.1.4 GetNamedTimeZonePreviousTransition ( timeZoneIdentifier, epochNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-getnamedtimezoneprevioustransition
|
2026-02-18 12:37:01 -05:00
|
|
|
|
Optional<Crypto::SignedBigInteger> get_named_time_zone_previous_transition(String const& time_zone, Crypto::SignedBigInteger const& epoch_nanoseconds)
|
2024-11-25 13:51:48 -05:00
|
|
|
|
{
|
2026-01-14 14:07:20 +00:00
|
|
|
|
auto epoch_milliseconds = big_floor(epoch_nanoseconds, NANOSECONDS_PER_MILLISECOND);
|
|
|
|
|
|
auto time = UnixDateTime::from_milliseconds_since_epoch(clip_bigint_to_sane_time(epoch_milliseconds));
|
|
|
|
|
|
|
|
|
|
|
|
// Assume there's a hypothetical time zone with 10000ms as a time zone transition and arbitrary transitions before that time.
|
|
|
|
|
|
// If there's sub-millisecond precision, for example 10000.1ms, it will be floored to 10000ms.
|
|
|
|
|
|
// If we then don't include the given time, we will go on to find a transition before 10000ms, which is incorrect because it should find
|
|
|
|
|
|
// the 10000ms transition when going backwards from 10000.1ms.
|
|
|
|
|
|
auto remainder = modulo(epoch_nanoseconds, NANOSECONDS_PER_MILLISECOND);
|
|
|
|
|
|
bool has_sub_millisecond_precision = !remainder.is_zero();
|
|
|
|
|
|
|
|
|
|
|
|
auto options = Unicode::TimeZoneTransition::Options {
|
|
|
|
|
|
.direction = Unicode::TimeZoneTransition::Options::Direction::Previous,
|
|
|
|
|
|
.include_given_time = has_sub_millisecond_precision ? Unicode::TimeZoneTransition::Options::IncludeGivenTime::Yes : Unicode::TimeZoneTransition::Options::IncludeGivenTime::No,
|
|
|
|
|
|
.transition_rule = Unicode::TimeZoneTransition::Options::TransitionRule::TransitionWhereUTCOffsetChanges,
|
|
|
|
|
|
};
|
|
|
|
|
|
auto time_zone_transition = Unicode::get_time_zone_transition(time_zone, time, options);
|
|
|
|
|
|
|
|
|
|
|
|
if (!time_zone_transition.has_value())
|
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
|
|
auto result_nanoseconds = Crypto::SignedBigInteger { time_zone_transition->transition.to_milliseconds() }.multiplied_by(NANOSECONDS_PER_MILLISECOND);
|
|
|
|
|
|
if (result_nanoseconds < NANOSECONDS_MIN_INSTANT)
|
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
|
|
return result_nanoseconds;
|
2024-11-25 13:51:48 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-20 12:59:15 -05:00
|
|
|
|
// 11.1.5 FormatOffsetTimeZoneIdentifier ( offsetMinutes [ , style ] ), https://tc39.es/proposal-temporal/#sec-temporal-formatoffsettimezoneidentifier
|
|
|
|
|
|
String format_offset_time_zone_identifier(i64 offset_minutes, Optional<TimeStyle> style)
|
|
|
|
|
|
{
|
2026-02-14 10:35:01 -05:00
|
|
|
|
// 1. If offsetMinutes ≥ 0, let sign be the code unit 0x002B (PLUS SIGN); else, let sign be the code unit 0x002D (HYPHEN-MINUS).
|
2024-11-20 12:59:15 -05:00
|
|
|
|
auto sign = offset_minutes >= 0 ? '+' : '-';
|
|
|
|
|
|
|
|
|
|
|
|
// 2. Let absoluteMinutes be abs(offsetMinutes).
|
|
|
|
|
|
auto absolute_minutes = abs(offset_minutes);
|
|
|
|
|
|
|
|
|
|
|
|
// 3. Let hour be floor(absoluteMinutes / 60).
|
|
|
|
|
|
auto hour = static_cast<u8>(floor(static_cast<double>(absolute_minutes) / 60.0));
|
|
|
|
|
|
|
|
|
|
|
|
// 4. Let minute be absoluteMinutes modulo 60.
|
|
|
|
|
|
auto minute = static_cast<u8>(modulo(static_cast<double>(absolute_minutes), 60.0));
|
|
|
|
|
|
|
|
|
|
|
|
// 5. Let timeString be FormatTimeString(hour, minute, 0, 0, MINUTE, style).
|
|
|
|
|
|
auto time_string = format_time_string(hour, minute, 0, 0, SecondsStringPrecision::Minute {}, style);
|
|
|
|
|
|
|
|
|
|
|
|
// 6. Return the string-concatenation of sign and timeString.
|
|
|
|
|
|
return MUST(String::formatted("{}{}", sign, time_string));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-24 20:42:47 -05:00
|
|
|
|
// 11.1.6 FormatUTCOffsetNanoseconds ( offsetNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-formatutcoffsetnanoseconds
|
|
|
|
|
|
String format_utc_offset_nanoseconds(i64 offset_nanoseconds)
|
|
|
|
|
|
{
|
2026-02-14 10:35:01 -05:00
|
|
|
|
// 1. If offsetNanoseconds ≥ 0, let sign be the code unit 0x002B (PLUS SIGN); else, let sign be the code unit 0x002D (HYPHEN-MINUS).
|
2024-11-24 20:42:47 -05:00
|
|
|
|
auto sign = offset_nanoseconds >= 0 ? '+' : '-';
|
|
|
|
|
|
|
|
|
|
|
|
// 2. Let absoluteNanoseconds be abs(offsetNanoseconds).
|
|
|
|
|
|
auto absolute_nanoseconds = static_cast<double>(abs(offset_nanoseconds));
|
|
|
|
|
|
|
|
|
|
|
|
// 3. Let hour be floor(absoluteNanoseconds / (3600 × 10**9)).
|
|
|
|
|
|
auto hour = floor(absolute_nanoseconds / 3'600'000'000'000.0);
|
|
|
|
|
|
|
|
|
|
|
|
// 4. Let minute be floor(absoluteNanoseconds / (60 × 10**9)) modulo 60.
|
|
|
|
|
|
auto minute = modulo(floor(absolute_nanoseconds / 60'000'000'000.0), 60.0);
|
|
|
|
|
|
|
|
|
|
|
|
// 5. Let second be floor(absoluteNanoseconds / 10**9) modulo 60.
|
|
|
|
|
|
auto second = modulo(floor(absolute_nanoseconds / 1'000'000'000.0), 60.0);
|
|
|
|
|
|
|
|
|
|
|
|
// 6. Let subSecondNanoseconds be absoluteNanoseconds modulo 10**9.
|
|
|
|
|
|
auto sub_second_nanoseconds = modulo(absolute_nanoseconds, 1'000'000'000.0);
|
|
|
|
|
|
|
2026-02-14 10:35:01 -05:00
|
|
|
|
// 7. If second = 0 and subSecondNanoseconds = 0, let precision be MINUTE; else, let precision be AUTO.
|
2024-11-24 20:42:47 -05:00
|
|
|
|
SecondsStringPrecision::Precision precision { Auto {} };
|
|
|
|
|
|
if (second == 0 && sub_second_nanoseconds == 0)
|
|
|
|
|
|
precision = SecondsStringPrecision::Minute {};
|
|
|
|
|
|
|
|
|
|
|
|
// 8. Let timeString be FormatTimeString(hour, minute, second, subSecondNanoseconds, precision).
|
|
|
|
|
|
auto time_string = format_time_string(hour, minute, second, sub_second_nanoseconds, precision);
|
|
|
|
|
|
|
|
|
|
|
|
// 9. Return the string-concatenation of sign and timeString.
|
|
|
|
|
|
return MUST(String::formatted("{}{}", sign, time_string));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-24 15:46:43 -05:00
|
|
|
|
// 11.1.7 FormatDateTimeUTCOffsetRounded ( offsetNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-formatdatetimeutcoffsetrounded
|
|
|
|
|
|
String format_date_time_utc_offset_rounded(i64 offset_nanoseconds)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 1. Set offsetNanoseconds to RoundNumberToIncrement(offsetNanoseconds, 60 × 10**9, HALF-EXPAND).
|
|
|
|
|
|
auto offset_nanoseconds_value = round_number_to_increment(static_cast<double>(offset_nanoseconds), 60'000'000'000, RoundingMode::HalfExpand);
|
|
|
|
|
|
|
|
|
|
|
|
// 2. Let offsetMinutes be offsetNanoseconds / (60 × 10**9).
|
|
|
|
|
|
auto offset_minutes = offset_nanoseconds_value / 60'000'000'000;
|
|
|
|
|
|
|
|
|
|
|
|
// 3. Assert: offsetMinutes is an integer.
|
|
|
|
|
|
VERIFY(trunc(offset_minutes) == offset_minutes);
|
|
|
|
|
|
|
|
|
|
|
|
// 4. Return FormatOffsetTimeZoneIdentifier(offsetMinutes).
|
|
|
|
|
|
return format_offset_time_zone_identifier(static_cast<i64>(offset_minutes));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-20 12:59:15 -05:00
|
|
|
|
// 11.1.8 ToTemporalTimeZoneIdentifier ( temporalTimeZoneLike ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaltimezoneidentifier
|
|
|
|
|
|
ThrowCompletionOr<String> to_temporal_time_zone_identifier(VM& vm, Value temporal_time_zone_like)
|
|
|
|
|
|
{
|
2026-02-14 12:42:36 -05:00
|
|
|
|
// 1. If temporalTimeZoneLike is an Object and temporalTimeZoneLike has an [[InitializedTemporalZonedDateTime]]
|
|
|
|
|
|
// internal slot, return temporalTimeZoneLike.[[TimeZone]].
|
2026-02-27 08:29:11 -05:00
|
|
|
|
if (auto zoned_date_time = temporal_time_zone_like.as_if<ZonedDateTime>())
|
2026-02-14 12:42:36 -05:00
|
|
|
|
return zoned_date_time->time_zone();
|
2024-11-20 12:59:15 -05:00
|
|
|
|
|
|
|
|
|
|
// 2. If temporalTimeZoneLike is not a String, throw a TypeError exception.
|
|
|
|
|
|
if (!temporal_time_zone_like.is_string())
|
|
|
|
|
|
return vm.throw_completion<TypeError>(ErrorType::TemporalInvalidTimeZoneName, temporal_time_zone_like);
|
|
|
|
|
|
|
2024-11-22 17:18:38 -05:00
|
|
|
|
return to_temporal_time_zone_identifier(vm, temporal_time_zone_like.as_string().utf8_string_view());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 11.1.8 ToTemporalTimeZoneIdentifier ( temporalTimeZoneLike ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaltimezoneidentifier
|
|
|
|
|
|
ThrowCompletionOr<String> to_temporal_time_zone_identifier(VM& vm, StringView temporal_time_zone_like)
|
|
|
|
|
|
{
|
2024-11-20 12:59:15 -05:00
|
|
|
|
// 3. Let parseResult be ? ParseTemporalTimeZoneString(temporalTimeZoneLike).
|
2024-11-22 17:18:38 -05:00
|
|
|
|
auto parse_result = TRY(parse_temporal_time_zone_string(vm, temporal_time_zone_like));
|
2024-11-20 12:59:15 -05:00
|
|
|
|
|
|
|
|
|
|
// 4. Let offsetMinutes be parseResult.[[OffsetMinutes]].
|
|
|
|
|
|
// 5. If offsetMinutes is not empty, return FormatOffsetTimeZoneIdentifier(offsetMinutes).
|
|
|
|
|
|
if (parse_result.offset_minutes.has_value())
|
|
|
|
|
|
return format_offset_time_zone_identifier(*parse_result.offset_minutes);
|
|
|
|
|
|
|
|
|
|
|
|
// 6. Let name be parseResult.[[Name]].
|
|
|
|
|
|
// 7. Let timeZoneIdentifierRecord be GetAvailableNamedTimeZoneIdentifier(name).
|
|
|
|
|
|
auto time_zone_identifier_record = Intl::get_available_named_time_zone_identifier(*parse_result.name);
|
|
|
|
|
|
|
|
|
|
|
|
// 8. If timeZoneIdentifierRecord is empty, throw a RangeError exception.
|
|
|
|
|
|
if (!time_zone_identifier_record.has_value())
|
|
|
|
|
|
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidTimeZoneName, temporal_time_zone_like);
|
|
|
|
|
|
|
|
|
|
|
|
// 9. Return timeZoneIdentifierRecord.[[Identifier]].
|
|
|
|
|
|
return time_zone_identifier_record->identifier;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-24 15:46:43 -05:00
|
|
|
|
// 11.1.9 GetOffsetNanosecondsFor ( timeZone, epochNs ), https://tc39.es/proposal-temporal/#sec-temporal-getoffsetnanosecondsfor
|
2026-02-18 12:37:01 -05:00
|
|
|
|
i64 get_offset_nanoseconds_for(String const& time_zone, Crypto::SignedBigInteger const& epoch_nanoseconds)
|
2024-11-24 15:46:43 -05:00
|
|
|
|
{
|
|
|
|
|
|
// 1. Let parseResult be ! ParseTimeZoneIdentifier(timeZone).
|
2026-02-18 12:55:34 -05:00
|
|
|
|
auto const& parse_result = parse_time_zone_identifier(time_zone);
|
2024-11-24 15:46:43 -05:00
|
|
|
|
|
|
|
|
|
|
// 2. If parseResult.[[OffsetMinutes]] is not empty, return parseResult.[[OffsetMinutes]] × (60 × 10**9).
|
|
|
|
|
|
if (parse_result.offset_minutes.has_value())
|
|
|
|
|
|
return *parse_result.offset_minutes * 60'000'000'000;
|
|
|
|
|
|
|
|
|
|
|
|
// 3. Return GetNamedTimeZoneOffsetNanoseconds(parseResult.[[Name]], epochNs).
|
|
|
|
|
|
return get_named_time_zone_offset_nanoseconds(*parse_result.name, epoch_nanoseconds).offset.to_nanoseconds();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 11.1.10 GetISODateTimeFor ( timeZone, epochNs ), https://tc39.es/proposal-temporal/#sec-temporal-getisodatetimefor
|
2026-02-18 12:37:01 -05:00
|
|
|
|
ISODateTime get_iso_date_time_for(String const& time_zone, Crypto::SignedBigInteger const& epoch_nanoseconds)
|
2024-11-24 15:46:43 -05:00
|
|
|
|
{
|
|
|
|
|
|
// 1. Let offsetNanoseconds be GetOffsetNanosecondsFor(timeZone, epochNs).
|
|
|
|
|
|
auto offset_nanoseconds = get_offset_nanoseconds_for(time_zone, epoch_nanoseconds);
|
|
|
|
|
|
|
|
|
|
|
|
// 2. Let result be GetISOPartsFromEpoch(ℝ(epochNs)).
|
|
|
|
|
|
auto result = get_iso_parts_from_epoch(epoch_nanoseconds);
|
|
|
|
|
|
|
|
|
|
|
|
// 3. Return BalanceISODateTime(result.[[ISODate]].[[Year]], result.[[ISODate]].[[Month]], result.[[ISODate]].[[Day]], result.[[Time]].[[Hour]], result.[[Time]].[[Minute]], result.[[Time]].[[Second]], result.[[Time]].[[Millisecond]], result.[[Time]].[[Microsecond]], result.[[Time]].[[Nanosecond]] + offsetNanoseconds).
|
|
|
|
|
|
return balance_iso_date_time(result.iso_date.year, result.iso_date.month, result.iso_date.day, result.time.hour, result.time.minute, result.time.second, result.time.millisecond, result.time.microsecond, static_cast<double>(result.time.nanosecond) + static_cast<double>(offset_nanoseconds));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-21 13:21:29 -05:00
|
|
|
|
// 11.1.11 GetEpochNanosecondsFor ( timeZone, isoDateTime, disambiguation ), https://tc39.es/proposal-temporal/#sec-temporal-getepochnanosecondsfor
|
2026-02-18 12:37:01 -05:00
|
|
|
|
ThrowCompletionOr<Crypto::SignedBigInteger> get_epoch_nanoseconds_for(VM& vm, String const& time_zone, ISODateTime const& iso_date_time, Disambiguation disambiguation)
|
2024-11-21 13:21:29 -05:00
|
|
|
|
{
|
|
|
|
|
|
// 1. Let possibleEpochNs be ? GetPossibleEpochNanoseconds(timeZone, isoDateTime).
|
|
|
|
|
|
auto possible_epoch_ns = TRY(get_possible_epoch_nanoseconds(vm, time_zone, iso_date_time));
|
|
|
|
|
|
|
|
|
|
|
|
// 2. Return ? DisambiguatePossibleEpochNanoseconds(possibleEpochNs, timeZone, isoDateTime, disambiguation).
|
|
|
|
|
|
return TRY(disambiguate_possible_epoch_nanoseconds(vm, move(possible_epoch_ns), time_zone, iso_date_time, disambiguation));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 11.1.12 DisambiguatePossibleEpochNanoseconds ( possibleEpochNs, timeZone, isoDateTime, disambiguation ), https://tc39.es/proposal-temporal/#sec-temporal-disambiguatepossibleepochnanoseconds
|
2026-02-18 12:37:01 -05:00
|
|
|
|
ThrowCompletionOr<Crypto::SignedBigInteger> disambiguate_possible_epoch_nanoseconds(VM& vm, Vector<Crypto::SignedBigInteger> possible_epoch_ns, String const& time_zone, ISODateTime const& iso_date_time, Disambiguation disambiguation)
|
2024-11-21 13:21:29 -05:00
|
|
|
|
{
|
2026-02-14 11:31:29 -05:00
|
|
|
|
// 1. Let n be the number of elements in possibleEpochNs.
|
2024-11-21 13:21:29 -05:00
|
|
|
|
auto n = possible_epoch_ns.size();
|
|
|
|
|
|
|
2026-02-14 12:42:36 -05:00
|
|
|
|
// 2. If n = 1, return the sole element of possibleEpochNs.
|
|
|
|
|
|
if (n == 1)
|
2024-11-21 13:21:29 -05:00
|
|
|
|
return move(possible_epoch_ns[0]);
|
|
|
|
|
|
|
|
|
|
|
|
// 3. If n ≠ 0, then
|
|
|
|
|
|
if (n != 0) {
|
2026-02-14 12:42:36 -05:00
|
|
|
|
// a. If disambiguation is either EARLIER or COMPATIBLE, return possibleEpochNs[0].
|
|
|
|
|
|
if (disambiguation == Disambiguation::Earlier || disambiguation == Disambiguation::Compatible)
|
2024-11-21 13:21:29 -05:00
|
|
|
|
return move(possible_epoch_ns[0]);
|
|
|
|
|
|
|
2026-02-14 12:42:36 -05:00
|
|
|
|
// b. If disambiguation is LATER, return possibleEpochNs[n - 1].
|
|
|
|
|
|
if (disambiguation == Disambiguation::Later)
|
2024-11-21 13:21:29 -05:00
|
|
|
|
return move(possible_epoch_ns[n - 1]);
|
|
|
|
|
|
|
|
|
|
|
|
// c. Assert: disambiguation is REJECT.
|
|
|
|
|
|
VERIFY(disambiguation == Disambiguation::Reject);
|
|
|
|
|
|
|
|
|
|
|
|
// d. Throw a RangeError exception.
|
|
|
|
|
|
return vm.throw_completion<RangeError>(ErrorType::TemporalDisambiguatePossibleEpochNSRejectMoreThanOne);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4. Assert: n = 0.
|
|
|
|
|
|
VERIFY(n == 0);
|
|
|
|
|
|
|
2026-02-14 12:42:36 -05:00
|
|
|
|
// 5. If disambiguation is REJECT, throw a RangeError exception.
|
|
|
|
|
|
if (disambiguation == Disambiguation::Reject)
|
2024-11-21 13:21:29 -05:00
|
|
|
|
return vm.throw_completion<RangeError>(ErrorType::TemporalDisambiguatePossibleEpochNSRejectZero);
|
|
|
|
|
|
|
2026-03-08 15:56:24 -04:00
|
|
|
|
// 6. Let before be the latest possible ISO Date-Time Record for which CompareISODateTime(before, isoDateTime) = -1
|
|
|
|
|
|
// and ! GetPossibleEpochNanoseconds(timeZone, before) is not empty.
|
|
|
|
|
|
// 7. Let after be the earliest possible ISO Date-Time Record for which CompareISODateTime(after, isoDateTime) = 1
|
|
|
|
|
|
// and ! GetPossibleEpochNanoseconds(timeZone, after) is not empty.
|
|
|
|
|
|
// 8. Let beforePossible be ! GetPossibleEpochNanoseconds(timeZone, before).
|
|
|
|
|
|
// 9. Assert: The number of elements in beforePossible = 1.
|
|
|
|
|
|
// 10. Let afterPossible be ! GetPossibleEpochNanoseconds(timeZone, after).
|
|
|
|
|
|
// 11. Assert: The number of elements in afterPossible = 1.
|
|
|
|
|
|
// NB: We implement this by finding the UTC offsets one day before and after the gap, which is guaranteed to be
|
|
|
|
|
|
// outside the transition period. We then use those offsets to determine the before/after epoch nanoseconds.
|
|
|
|
|
|
auto epoch_nanoseconds = get_utc_epoch_nanoseconds(iso_date_time);
|
|
|
|
|
|
auto before_possible = epoch_nanoseconds.minus(NANOSECONDS_PER_DAY);
|
|
|
|
|
|
auto after_possible = epoch_nanoseconds.plus(NANOSECONDS_PER_DAY);
|
|
|
|
|
|
|
|
|
|
|
|
// 12. Let offsetBefore be GetOffsetNanosecondsFor(timeZone, the sole element of beforePossible).
|
|
|
|
|
|
auto offset_before = get_offset_nanoseconds_for(time_zone, before_possible);
|
|
|
|
|
|
|
|
|
|
|
|
// 13. Let offsetAfter be GetOffsetNanosecondsFor(timeZone, the sole element of afterPossible).
|
|
|
|
|
|
auto offset_after = get_offset_nanoseconds_for(time_zone, after_possible);
|
|
|
|
|
|
|
|
|
|
|
|
// 14. Let nanoseconds be offsetAfter - offsetBefore.
|
|
|
|
|
|
auto nanoseconds = offset_after - offset_before;
|
|
|
|
|
|
|
|
|
|
|
|
// 15. Assert: abs(nanoseconds) ≤ nsPerDay.
|
|
|
|
|
|
|
|
|
|
|
|
// 16. If disambiguation is EARLIER, then
|
|
|
|
|
|
if (disambiguation == Disambiguation::Earlier) {
|
|
|
|
|
|
// a. Let timeDuration be TimeDurationFromComponents(0, 0, 0, 0, 0, -nanoseconds).
|
|
|
|
|
|
auto time_duration = time_duration_from_components(0, 0, 0, 0, 0, -static_cast<double>(nanoseconds));
|
|
|
|
|
|
|
|
|
|
|
|
// b. Let earlierTime be AddTime(isoDateTime.[[Time]], timeDuration).
|
|
|
|
|
|
auto earlier_time = add_time(iso_date_time.time, time_duration);
|
|
|
|
|
|
|
|
|
|
|
|
// c. Let earlierDate be AddDaysToISODate(isoDateTime.[[ISODate]], earlierTime.[[Days]]).
|
|
|
|
|
|
auto earlier_date = add_days_to_iso_date(iso_date_time.iso_date, earlier_time.days);
|
|
|
|
|
|
|
|
|
|
|
|
// d. Let earlierDateTime be CombineISODateAndTimeRecord(earlierDate, earlierTime).
|
|
|
|
|
|
auto earlier_date_time = combine_iso_date_and_time_record(earlier_date, earlier_time);
|
|
|
|
|
|
|
|
|
|
|
|
// e. Set possibleEpochNs to ? GetPossibleEpochNanoseconds(timeZone, earlierDateTime).
|
|
|
|
|
|
possible_epoch_ns = TRY(get_possible_epoch_nanoseconds(vm, time_zone, earlier_date_time));
|
|
|
|
|
|
|
|
|
|
|
|
// f. Assert: possibleEpochNs is not empty.
|
|
|
|
|
|
VERIFY(!possible_epoch_ns.is_empty());
|
|
|
|
|
|
|
|
|
|
|
|
// g. Return possibleEpochNs[0].
|
|
|
|
|
|
return move(possible_epoch_ns[0]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 17. Assert: disambiguation is COMPATIBLE or LATER.
|
|
|
|
|
|
VERIFY(disambiguation == Disambiguation::Compatible || disambiguation == Disambiguation::Later);
|
|
|
|
|
|
|
|
|
|
|
|
// 18. Let timeDuration be TimeDurationFromComponents(0, 0, 0, 0, 0, nanoseconds).
|
|
|
|
|
|
auto time_duration = time_duration_from_components(0, 0, 0, 0, 0, static_cast<double>(nanoseconds));
|
|
|
|
|
|
|
|
|
|
|
|
// 19. Let laterTime be AddTime(isoDateTime.[[Time]], timeDuration).
|
|
|
|
|
|
auto later_time = add_time(iso_date_time.time, time_duration);
|
|
|
|
|
|
|
|
|
|
|
|
// 20. Let laterDate be AddDaysToISODate(isoDateTime.[[ISODate]], laterTime.[[Days]]).
|
|
|
|
|
|
auto later_date = add_days_to_iso_date(iso_date_time.iso_date, later_time.days);
|
|
|
|
|
|
|
|
|
|
|
|
// 21. Let laterDateTime be CombineISODateAndTimeRecord(laterDate, laterTime).
|
|
|
|
|
|
auto later_date_time = combine_iso_date_and_time_record(later_date, later_time);
|
|
|
|
|
|
|
|
|
|
|
|
// 22. Set possibleEpochNs to ? GetPossibleEpochNanoseconds(timeZone, laterDateTime).
|
|
|
|
|
|
possible_epoch_ns = TRY(get_possible_epoch_nanoseconds(vm, time_zone, later_date_time));
|
|
|
|
|
|
|
|
|
|
|
|
// 23. Set n to the number of elements in possibleEpochNs.
|
|
|
|
|
|
n = possible_epoch_ns.size();
|
|
|
|
|
|
|
|
|
|
|
|
// 24. Assert: n ≠ 0.
|
|
|
|
|
|
VERIFY(n != 0);
|
|
|
|
|
|
|
|
|
|
|
|
// 25. Return possibleEpochNs[n - 1].
|
|
|
|
|
|
return move(possible_epoch_ns[n - 1]);
|
2024-11-21 13:21:29 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 11.1.13 GetPossibleEpochNanoseconds ( timeZone, isoDateTime ), https://tc39.es/proposal-temporal/#sec-temporal-getpossibleepochnanoseconds
|
2026-02-18 12:37:01 -05:00
|
|
|
|
ThrowCompletionOr<Vector<Crypto::SignedBigInteger>> get_possible_epoch_nanoseconds(VM& vm, String const& time_zone, ISODateTime const& iso_date_time)
|
2024-11-21 13:21:29 -05:00
|
|
|
|
{
|
|
|
|
|
|
Vector<Crypto::SignedBigInteger> possible_epoch_nanoseconds;
|
|
|
|
|
|
|
|
|
|
|
|
// 1. Let parseResult be ! ParseTimeZoneIdentifier(timeZone).
|
2026-02-18 12:55:34 -05:00
|
|
|
|
auto const& parse_result = parse_time_zone_identifier(time_zone);
|
2024-11-21 13:21:29 -05:00
|
|
|
|
|
|
|
|
|
|
// 2. If parseResult.[[OffsetMinutes]] is not empty, then
|
|
|
|
|
|
if (parse_result.offset_minutes.has_value()) {
|
|
|
|
|
|
// a. Let balanced be BalanceISODateTime(isoDateTime.[[ISODate]].[[Year]], isoDateTime.[[ISODate]].[[Month]], isoDateTime.[[ISODate]].[[Day]], isoDateTime.[[Time]].[[Hour]], isoDateTime.[[Time]].[[Minute]] - parseResult.[[OffsetMinutes]], isoDateTime.[[Time]].[[Second]], isoDateTime.[[Time]].[[Millisecond]], isoDateTime.[[Time]].[[Microsecond]], isoDateTime.[[Time]].[[Nanosecond]]).
|
|
|
|
|
|
auto balanced = balance_iso_date_time(
|
|
|
|
|
|
iso_date_time.iso_date.year,
|
|
|
|
|
|
iso_date_time.iso_date.month,
|
|
|
|
|
|
iso_date_time.iso_date.day,
|
|
|
|
|
|
iso_date_time.time.hour,
|
|
|
|
|
|
static_cast<double>(iso_date_time.time.minute) - static_cast<double>(*parse_result.offset_minutes),
|
|
|
|
|
|
iso_date_time.time.second,
|
|
|
|
|
|
iso_date_time.time.millisecond,
|
|
|
|
|
|
iso_date_time.time.microsecond,
|
|
|
|
|
|
iso_date_time.time.nanosecond);
|
|
|
|
|
|
|
|
|
|
|
|
// b. Perform ? CheckISODaysRange(balanced.[[ISODate]]).
|
|
|
|
|
|
TRY(check_iso_days_range(vm, balanced.iso_date));
|
|
|
|
|
|
|
|
|
|
|
|
// c. Let epochNanoseconds be GetUTCEpochNanoseconds(balanced).
|
|
|
|
|
|
auto epoch_nanoseconds = get_utc_epoch_nanoseconds(balanced);
|
|
|
|
|
|
|
|
|
|
|
|
// d. Let possibleEpochNanoseconds be « epochNanoseconds ».
|
|
|
|
|
|
possible_epoch_nanoseconds.append(move(epoch_nanoseconds));
|
|
|
|
|
|
}
|
|
|
|
|
|
// 3. Else,
|
|
|
|
|
|
else {
|
2025-09-19 09:04:57 -04:00
|
|
|
|
// a. Let possibleEpochNanoseconds be GetNamedTimeZoneEpochNanoseconds(parseResult.[[Name]], isoDateTime).
|
2025-02-28 11:54:54 -05:00
|
|
|
|
possible_epoch_nanoseconds = get_named_time_zone_epoch_nanoseconds(*parse_result.name, iso_date_time);
|
2024-11-21 13:21:29 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4. For each value epochNanoseconds in possibleEpochNanoseconds, do
|
|
|
|
|
|
for (auto const& epoch_nanoseconds : possible_epoch_nanoseconds) {
|
|
|
|
|
|
// a. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception.
|
|
|
|
|
|
if (!is_valid_epoch_nanoseconds(epoch_nanoseconds))
|
|
|
|
|
|
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidEpochNanoseconds);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 5. Return possibleEpochNanoseconds.
|
|
|
|
|
|
return possible_epoch_nanoseconds;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-24 20:42:47 -05:00
|
|
|
|
// 11.1.14 GetStartOfDay ( timeZone, isoDate ), https://tc39.es/proposal-temporal/#sec-temporal-getstartofday
|
2026-02-18 12:37:01 -05:00
|
|
|
|
ThrowCompletionOr<Crypto::SignedBigInteger> get_start_of_day(VM& vm, String const& time_zone, ISODate iso_date)
|
2024-11-24 20:42:47 -05:00
|
|
|
|
{
|
|
|
|
|
|
// 1. Let isoDateTime be CombineISODateAndTimeRecord(isoDate, MidnightTimeRecord()).
|
|
|
|
|
|
auto iso_date_time = combine_iso_date_and_time_record(iso_date, midnight_time_record());
|
|
|
|
|
|
|
|
|
|
|
|
// 2. Let possibleEpochNs be ? GetPossibleEpochNanoseconds(timeZone, isoDateTime).
|
|
|
|
|
|
auto possible_epoch_nanoseconds = TRY(get_possible_epoch_nanoseconds(vm, time_zone, iso_date_time));
|
|
|
|
|
|
|
|
|
|
|
|
// 3. If possibleEpochNs is not empty, return possibleEpochNs[0].
|
|
|
|
|
|
if (!possible_epoch_nanoseconds.is_empty())
|
|
|
|
|
|
return move(possible_epoch_nanoseconds[0]);
|
|
|
|
|
|
|
2026-03-08 15:56:24 -04:00
|
|
|
|
// 4. Assert: IsOffsetTimeZoneIdentifier(timeZone) is false.
|
|
|
|
|
|
VERIFY(!is_offset_time_zone_identifier(time_zone));
|
|
|
|
|
|
|
|
|
|
|
|
// 5. Let possibleEpochNsAfter be GetNamedTimeZoneEpochNanoseconds(timeZone, isoDateTimeAfter), where isoDateTimeAfter
|
|
|
|
|
|
// is the ISO Date-Time Record for which DifferenceISODateTime(isoDateTime, isoDateTimeAfter, "iso8601", hour).[[Time]]
|
|
|
|
|
|
// is the smallest possible value > 0 for which possibleEpochNsAfter is not empty (i.e., isoDateTimeAfter represents
|
|
|
|
|
|
// the first local time after the transition).
|
|
|
|
|
|
// NB: We implement this by finding the next UTC offset transition after one day before midnight, which is guaranteed
|
|
|
|
|
|
// to be before the gap. The transition instant is the first valid epoch nanoseconds of the day.
|
|
|
|
|
|
auto epoch_nanoseconds = get_utc_epoch_nanoseconds(iso_date_time);
|
|
|
|
|
|
auto day_before = epoch_nanoseconds.minus(NANOSECONDS_PER_DAY);
|
|
|
|
|
|
auto possible_epoch_nanoseconds_after = get_named_time_zone_next_transition(time_zone, day_before);
|
|
|
|
|
|
|
|
|
|
|
|
// 6. Assert: The number of elements in possibleEpochNsAfter = 1.
|
|
|
|
|
|
VERIFY(possible_epoch_nanoseconds_after.has_value());
|
|
|
|
|
|
|
|
|
|
|
|
// 7. Return the sole element of possibleEpochNsAfter.
|
|
|
|
|
|
return possible_epoch_nanoseconds_after.release_value();
|
2024-11-24 20:42:47 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-25 12:34:35 -05:00
|
|
|
|
// 11.1.15 TimeZoneEquals ( one, two ), https://tc39.es/proposal-temporal/#sec-temporal-timezoneequals
|
|
|
|
|
|
bool time_zone_equals(StringView one, StringView two)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 1. If one is two, return true.
|
|
|
|
|
|
if (one == two)
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
2026-02-14 10:57:37 -05:00
|
|
|
|
// NB: IsOffsetTimeZoneIdentifier simply invokes parse_utc_offset and returns whether it has a value. We do this
|
|
|
|
|
|
// manually here so that we can handle the offset minutes assertion below without any extra performance penalty.
|
|
|
|
|
|
auto time_zone_offset_one = parse_utc_offset(one, SubMinutePrecision::No);
|
|
|
|
|
|
auto time_zone_offset_two = parse_utc_offset(two, SubMinutePrecision::No);
|
2024-11-25 12:34:35 -05:00
|
|
|
|
|
2026-02-14 10:57:37 -05:00
|
|
|
|
// 2. If IsOffsetTimeZoneIdentifier(one) is false and IsOffsetTimeZoneIdentifier(two) is false, then
|
|
|
|
|
|
if (!time_zone_offset_one.has_value() && !time_zone_offset_two.has_value()) {
|
2024-11-25 12:34:35 -05:00
|
|
|
|
// a. Let recordOne be GetAvailableNamedTimeZoneIdentifier(one).
|
|
|
|
|
|
auto record_one = Intl::get_available_named_time_zone_identifier(one);
|
|
|
|
|
|
|
|
|
|
|
|
// b. Let recordTwo be GetAvailableNamedTimeZoneIdentifier(two).
|
|
|
|
|
|
auto record_two = Intl::get_available_named_time_zone_identifier(two);
|
|
|
|
|
|
|
2026-02-14 10:57:37 -05:00
|
|
|
|
// c. Assert: recordOne is not EMPTY.
|
|
|
|
|
|
VERIFY(record_one.has_value());
|
|
|
|
|
|
|
|
|
|
|
|
// d. Assert: recordTwo is not EMPTY.
|
|
|
|
|
|
VERIFY(record_two.has_value());
|
|
|
|
|
|
|
|
|
|
|
|
// e. If recordOne.[[PrimaryIdentifier]] is recordTwo.[[PrimaryIdentifier]], return true.
|
|
|
|
|
|
if (record_one->primary_identifier == record_two->primary_identifier)
|
|
|
|
|
|
return true;
|
2024-11-25 12:34:35 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-14 10:57:37 -05:00
|
|
|
|
// 3. Assert: If one and two are both offset time zone identifiers, they do not represent the same number of offset minutes.
|
|
|
|
|
|
if (time_zone_offset_one.has_value() && time_zone_offset_two.has_value())
|
|
|
|
|
|
VERIFY(time_zone_offset_one->minutes != time_zone_offset_two->minutes);
|
|
|
|
|
|
|
|
|
|
|
|
// 4. Return false.
|
2024-11-25 12:34:35 -05:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-18 12:55:34 -05:00
|
|
|
|
// OPTIMIZATION: The result of parsing a time zone identifier will not change, so we can cache the result.
|
|
|
|
|
|
static HashMap<String, ParsedTimeZoneIdentifier> s_time_zone_id_cache;
|
|
|
|
|
|
|
2024-11-20 12:59:15 -05:00
|
|
|
|
// 11.1.16 ParseTimeZoneIdentifier ( identifier ), https://tc39.es/proposal-temporal/#sec-parsetimezoneidentifier
|
2026-02-18 12:37:01 -05:00
|
|
|
|
ThrowCompletionOr<ParsedTimeZoneIdentifier> parse_time_zone_identifier(VM& vm, String const& identifier)
|
2024-11-20 12:59:15 -05:00
|
|
|
|
{
|
2026-02-18 12:55:34 -05:00
|
|
|
|
if (auto result = s_time_zone_id_cache.get(identifier); result.has_value())
|
|
|
|
|
|
return *result;
|
|
|
|
|
|
|
2024-11-20 12:59:15 -05:00
|
|
|
|
// 1. Let parseResult be ParseText(StringToCodePoints(identifier), TimeZoneIdentifier).
|
|
|
|
|
|
auto parse_result = parse_iso8601(Production::TimeZoneIdentifier, identifier);
|
|
|
|
|
|
|
|
|
|
|
|
// 2. If parseResult is a List of errors, throw a RangeError exception.
|
|
|
|
|
|
if (!parse_result.has_value())
|
|
|
|
|
|
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidTimeZoneString, identifier);
|
|
|
|
|
|
|
2026-02-18 12:55:34 -05:00
|
|
|
|
auto result = parse_time_zone_identifier(*parse_result);
|
|
|
|
|
|
s_time_zone_id_cache.set(identifier, result);
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
2024-11-20 12:59:15 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 11.1.16 ParseTimeZoneIdentifier ( identifier ), https://tc39.es/proposal-temporal/#sec-parsetimezoneidentifier
|
2026-02-18 12:55:34 -05:00
|
|
|
|
ParsedTimeZoneIdentifier const& parse_time_zone_identifier(String const& identifier)
|
2024-11-20 12:59:15 -05:00
|
|
|
|
{
|
|
|
|
|
|
// OPTIMIZATION: Some callers can assume that parsing will succeed.
|
2026-02-18 12:55:34 -05:00
|
|
|
|
return s_time_zone_id_cache.ensure(identifier, [&]() {
|
|
|
|
|
|
// 1. Let parseResult be ParseText(StringToCodePoints(identifier), TimeZoneIdentifier).
|
|
|
|
|
|
auto parse_result = parse_iso8601(Production::TimeZoneIdentifier, identifier);
|
|
|
|
|
|
VERIFY(parse_result.has_value());
|
2024-11-20 12:59:15 -05:00
|
|
|
|
|
2026-02-18 12:55:34 -05:00
|
|
|
|
return parse_time_zone_identifier(*parse_result);
|
|
|
|
|
|
});
|
2024-11-20 12:59:15 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 11.1.16 ParseTimeZoneIdentifier ( identifier ), https://tc39.es/proposal-temporal/#sec-parsetimezoneidentifier
|
2025-11-12 15:10:32 -05:00
|
|
|
|
ParsedTimeZoneIdentifier parse_time_zone_identifier(ParseResult const& parse_result)
|
2024-11-20 12:59:15 -05:00
|
|
|
|
{
|
|
|
|
|
|
// OPTIMIZATION: Some callers will have already parsed and validated the time zone identifier.
|
|
|
|
|
|
|
|
|
|
|
|
// 3. If parseResult contains a TimeZoneIANAName Parse Node, then
|
|
|
|
|
|
if (parse_result.time_zone_iana_name.has_value()) {
|
|
|
|
|
|
// a. Let name be the source text matched by the TimeZoneIANAName Parse Node contained within parseResult.
|
|
|
|
|
|
// b. NOTE: name is syntactically valid, but does not necessarily conform to IANA Time Zone Database naming
|
|
|
|
|
|
// guidelines or correspond with an available named time zone identifier.
|
2025-11-12 15:10:32 -05:00
|
|
|
|
// c. Return Time Zone Identifier Parse Record { [[Name]]: CodePointsToString(name), [[OffsetMinutes]]: EMPTY }.
|
|
|
|
|
|
return ParsedTimeZoneIdentifier { .name = String::from_utf8_without_validation(parse_result.time_zone_iana_name->bytes()), .offset_minutes = {} };
|
2024-11-20 12:59:15 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-14 10:38:13 -05:00
|
|
|
|
// 4. Assert: parseResult contains a UTCOffset[~SubMinutePrecision] Parse Node.
|
|
|
|
|
|
VERIFY(parse_result.time_zone_offset.has_value());
|
2024-11-20 12:59:15 -05:00
|
|
|
|
|
2026-02-14 10:38:13 -05:00
|
|
|
|
// 5. Let offset be the source text matched by the UTCOffset[~SubMinutePrecision] Parse Node contained within parseResult.
|
|
|
|
|
|
// 6. Let offsetNanoseconds be ! ParseDateTimeUTCOffset(CodePointsToString(offset)).
|
|
|
|
|
|
auto offset_nanoseconds = parse_date_time_utc_offset(parse_result.time_zone_offset->source_text);
|
2024-11-20 12:59:15 -05:00
|
|
|
|
|
2026-02-14 10:38:13 -05:00
|
|
|
|
// 7. Let offsetMinutes be offsetNanoseconds / (60 × 10**9).
|
|
|
|
|
|
auto offset_minutes = offset_nanoseconds / 60'000'000'000;
|
|
|
|
|
|
|
|
|
|
|
|
// 8. Return Time Zone Identifier Parse Record { [[Name]]: empty, [[OffsetMinutes]]: offsetMinutes }.
|
|
|
|
|
|
return ParsedTimeZoneIdentifier { .name = {}, .offset_minutes = static_cast<i64>(offset_minutes) };
|
2024-11-20 12:59:15 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|