mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2026-04-19 10:20:22 +00:00
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.
477 lines
25 KiB
C++
477 lines
25 KiB
C++
/*
|
||
* 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) };
|
||
}
|
||
|
||
}
|