ladybird/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp
Timothy Flynn f9fa548d43 LibJS: Pass time zone strings around as String more regularly
These are String from the outset, so this patch is almost entirely just
changing function parameter types. This will allow us to cache time zone
parse results without invoking any extra allocations.
2026-02-19 09:20:15 +01:00

477 lines
25 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
* Copyright (c) 2024-2026, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Date.h>
#include <LibJS/Runtime/Intl/AbstractOperations.h>
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
#include <LibJS/Runtime/Temporal/DateEquations.h>
#include <LibJS/Runtime/Temporal/ISO8601.h>
#include <LibJS/Runtime/Temporal/Instant.h>
#include <LibJS/Runtime/Temporal/PlainDate.h>
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
#include <LibJS/Runtime/Temporal/PlainTime.h>
#include <LibJS/Runtime/Temporal/TimeZone.h>
#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
#include <LibJS/Runtime/VM.h>
#include <LibUnicode/TimeZone.h>
namespace JS::Temporal {
String UTC_TIME_ZONE = "UTC"_string;
// 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);
}
// 11.1.3 GetNamedTimeZoneNextTransition ( timeZoneIdentifier, epochNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-getnamedtimezonenexttransition
Optional<Crypto::SignedBigInteger> get_named_time_zone_next_transition(String const& time_zone, Crypto::SignedBigInteger const& epoch_nanoseconds)
{
auto epoch_milliseconds = big_floor(epoch_nanoseconds, NANOSECONDS_PER_MILLISECOND);
auto time = UnixDateTime::from_milliseconds_since_epoch(clip_bigint_to_sane_time(epoch_milliseconds));
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;
}
// 11.1.4 GetNamedTimeZonePreviousTransition ( timeZoneIdentifier, epochNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-getnamedtimezoneprevioustransition
Optional<Crypto::SignedBigInteger> get_named_time_zone_previous_transition(String const& time_zone, Crypto::SignedBigInteger const& epoch_nanoseconds)
{
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;
}
// 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)
{
// 1. If offsetMinutes โ‰ฅ 0, let sign be the code unit 0x002B (PLUS SIGN); else, let sign be the code unit 0x002D (HYPHEN-MINUS).
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));
}
// 11.1.6 FormatUTCOffsetNanoseconds ( offsetNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-formatutcoffsetnanoseconds
String format_utc_offset_nanoseconds(i64 offset_nanoseconds)
{
// 1. If offsetNanoseconds โ‰ฅ 0, let sign be the code unit 0x002B (PLUS SIGN); else, let sign be the code unit 0x002D (HYPHEN-MINUS).
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);
// 7. If second = 0 and subSecondNanoseconds = 0, let precision be MINUTE; else, let precision be AUTO.
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));
}
// 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));
}
// 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)
{
// 1. If temporalTimeZoneLike is an Object and temporalTimeZoneLike has an [[InitializedTemporalZonedDateTime]]
// internal slot, return temporalTimeZoneLike.[[TimeZone]].
if (auto const* zoned_date_time = temporal_time_zone_like.as_if<ZonedDateTime>())
return zoned_date_time->time_zone();
// 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);
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)
{
// 3. Let parseResult be ? ParseTemporalTimeZoneString(temporalTimeZoneLike).
auto parse_result = TRY(parse_temporal_time_zone_string(vm, temporal_time_zone_like));
// 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;
}
// 11.1.9 GetOffsetNanosecondsFor ( timeZone, epochNs ), https://tc39.es/proposal-temporal/#sec-temporal-getoffsetnanosecondsfor
i64 get_offset_nanoseconds_for(String const& time_zone, Crypto::SignedBigInteger const& epoch_nanoseconds)
{
// 1. Let parseResult be ! ParseTimeZoneIdentifier(timeZone).
auto parse_result = parse_time_zone_identifier(time_zone);
// 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
ISODateTime get_iso_date_time_for(String const& time_zone, Crypto::SignedBigInteger const& epoch_nanoseconds)
{
// 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));
}
// 11.1.11 GetEpochNanosecondsFor ( timeZone, isoDateTime, disambiguation ), https://tc39.es/proposal-temporal/#sec-temporal-getepochnanosecondsfor
ThrowCompletionOr<Crypto::SignedBigInteger> get_epoch_nanoseconds_for(VM& vm, String const& time_zone, ISODateTime const& iso_date_time, Disambiguation disambiguation)
{
// 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
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)
{
// 1. Let n be the number of elements in possibleEpochNs.
auto n = possible_epoch_ns.size();
// 2. If n = 1, return the sole element of possibleEpochNs.
if (n == 1)
return move(possible_epoch_ns[0]);
// 3. If n โ‰  0, then
if (n != 0) {
// a. If disambiguation is either EARLIER or COMPATIBLE, return possibleEpochNs[0].
if (disambiguation == Disambiguation::Earlier || disambiguation == Disambiguation::Compatible)
return move(possible_epoch_ns[0]);
// b. If disambiguation is LATER, return possibleEpochNs[n - 1].
if (disambiguation == Disambiguation::Later)
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);
// 5. If disambiguation is REJECT, throw a RangeError exception.
if (disambiguation == Disambiguation::Reject)
return vm.throw_completion<RangeError>(ErrorType::TemporalDisambiguatePossibleEpochNSRejectZero);
// FIXME: GetNamedTimeZoneEpochNanoseconds currently does not produce zero instants.
(void)time_zone;
(void)iso_date_time;
TODO();
}
// 11.1.13 GetPossibleEpochNanoseconds ( timeZone, isoDateTime ), https://tc39.es/proposal-temporal/#sec-temporal-getpossibleepochnanoseconds
ThrowCompletionOr<Vector<Crypto::SignedBigInteger>> get_possible_epoch_nanoseconds(VM& vm, String const& time_zone, ISODateTime const& iso_date_time)
{
Vector<Crypto::SignedBigInteger> possible_epoch_nanoseconds;
// 1. Let parseResult be ! ParseTimeZoneIdentifier(timeZone).
auto parse_result = parse_time_zone_identifier(time_zone);
// 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 {
// a. Let possibleEpochNanoseconds be GetNamedTimeZoneEpochNanoseconds(parseResult.[[Name]], isoDateTime).
possible_epoch_nanoseconds = get_named_time_zone_epoch_nanoseconds(*parse_result.name, iso_date_time);
}
// 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;
}
// 11.1.14 GetStartOfDay ( timeZone, isoDate ), https://tc39.es/proposal-temporal/#sec-temporal-getstartofday
ThrowCompletionOr<Crypto::SignedBigInteger> get_start_of_day(VM& vm, String const& time_zone, ISODate iso_date)
{
// 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]);
// FIXME: GetNamedTimeZoneEpochNanoseconds currently does not produce zero instants.
TODO();
}
// 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;
// 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);
// 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()) {
// 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);
// 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;
}
// 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.
return false;
}
// 11.1.16 ParseTimeZoneIdentifier ( identifier ), https://tc39.es/proposal-temporal/#sec-parsetimezoneidentifier
ThrowCompletionOr<ParsedTimeZoneIdentifier> parse_time_zone_identifier(VM& vm, String const& identifier)
{
// 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);
return parse_time_zone_identifier(*parse_result);
}
// 11.1.16 ParseTimeZoneIdentifier ( identifier ), https://tc39.es/proposal-temporal/#sec-parsetimezoneidentifier
ParsedTimeZoneIdentifier parse_time_zone_identifier(String const& identifier)
{
// OPTIMIZATION: Some callers can assume that parsing will succeed.
// 1. Let parseResult be ParseText(StringToCodePoints(identifier), TimeZoneIdentifier).
auto parse_result = parse_iso8601(Production::TimeZoneIdentifier, identifier);
VERIFY(parse_result.has_value());
return parse_time_zone_identifier(*parse_result);
}
// 11.1.16 ParseTimeZoneIdentifier ( identifier ), https://tc39.es/proposal-temporal/#sec-parsetimezoneidentifier
ParsedTimeZoneIdentifier parse_time_zone_identifier(ParseResult const& parse_result)
{
// 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.
// 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 = {} };
}
// 4. Assert: parseResult contains a UTCOffset[~SubMinutePrecision] Parse Node.
VERIFY(parse_result.time_zone_offset.has_value());
// 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);
// 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) };
}
}