From 443ffab9dc01ac74fab1d681921a301f07643a1a Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Fri, 14 Oct 2022 11:42:12 -0400 Subject: [PATCH] LibJS: Use infallible ParseTimeZoneOffsetString This is an editorial change in the Temporal spec. See: https://github.com/tc39/proposal-temporal/commit/654e300 --- .../Runtime/Temporal/AbstractOperations.cpp | 53 ++++---- .../LibJS/Runtime/Temporal/Instant.cpp | 8 +- .../LibJS/Runtime/Temporal/TimeZone.cpp | 123 ++++-------------- .../LibJS/Runtime/Temporal/TimeZone.h | 1 - .../Runtime/Temporal/TimeZoneConstructor.cpp | 8 +- .../LibJS/Runtime/Temporal/ZonedDateTime.cpp | 10 +- .../Temporal/ZonedDateTimePrototype.cpp | 18 ++- 7 files changed, 83 insertions(+), 138 deletions(-) diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp index 71fbafc5ab..535be421fd 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -621,10 +622,15 @@ ThrowCompletionOr to_relative_temporal_object(VM& vm, Object const& optio // e. Let timeZoneName be result.[[TimeZone]].[[Name]]. auto time_zone_name = result.time_zone.name; - // f. If timeZoneName is not undefined, then - if (time_zone_name.has_value()) { - // i. If ParseText(StringToCodePoints(timeZoneName), TimeZoneNumericUTCOffset) is a List of errors, then - if (!is_valid_time_zone_numeric_utc_offset_syntax(*time_zone_name)) { + // f. If timeZoneName is undefined, then + if (!time_zone_name.has_value()) { + // i. Let timeZone be undefined. + time_zone = js_undefined(); + } + // g. Else, + else { + // i. If IsTimeZoneOffsetString(timeZoneName) is false, then + if (!is_time_zone_offset_string(*time_zone_name)) { // 1. If IsValidTimeZoneName(timeZoneName) is false, throw a RangeError exception. if (!is_valid_time_zone_name(*time_zone_name)) return vm.throw_completion(ErrorType::TemporalInvalidTimeZoneName, *time_zone_name); @@ -635,26 +641,21 @@ ThrowCompletionOr to_relative_temporal_object(VM& vm, Object const& optio // ii. Let timeZone be ! CreateTemporalTimeZone(timeZoneName). time_zone = MUST(create_temporal_time_zone(vm, *time_zone_name)); - } - // g. Else, - else { - // i. Let timeZone be undefined. - time_zone = js_undefined(); - } - // h. If result.[[TimeZone]].[[Z]] is true, then - if (result.time_zone.z) { - // i. Set offsetBehaviour to exact. - offset_behavior = OffsetBehavior::Exact; - } - // i. Else if offsetString is undefined, then - else if (offset_string.is_undefined()) { - // i. Set offsetBehaviour to wall. - offset_behavior = OffsetBehavior::Wall; - } + // iii. If result.[[TimeZone]].[[Z]] is true, then + if (result.time_zone.z) { + // 1. Set offsetBehaviour to exact. + offset_behavior = OffsetBehavior::Exact; + } + // iv. Else if offsetString is undefined, then + else if (offset_string.is_undefined()) { + // 1. Set offsetBehaviour to wall. + offset_behavior = OffsetBehavior::Wall; + } - // j. Set matchBehaviour to match minutes. - match_behavior = MatchBehavior::MatchMinutes; + // v. Set matchBehaviour to match minutes. + match_behavior = MatchBehavior::MatchMinutes; + } } // 8. If timeZone is not undefined, then @@ -667,8 +668,12 @@ ThrowCompletionOr to_relative_temporal_object(VM& vm, Object const& optio // NOTE: offsetString is not used after this path, so we don't need to put this into the original offset_string which is of type JS::Value. auto actual_offset_string = TRY(offset_string.to_string(vm)); - // ii. Let offsetNs be ? ParseTimeZoneOffsetString(offsetString). - offset_ns = TRY(parse_time_zone_offset_string(vm, actual_offset_string)); + // b. If IsTimeZoneOffsetString(offsetString) is false, throw a RangeError exception. + if (!is_time_zone_offset_string(actual_offset_string)) + return vm.throw_completion(ErrorType::TemporalInvalidTimeZoneName, actual_offset_string); + + // c. Let offsetNs be ParseTimeZoneOffsetString(offsetString). + offset_ns = parse_time_zone_offset_string(actual_offset_string); } // b. Else, else { diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp index c8da9ebb02..2d636ed7b8 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp @@ -125,8 +125,12 @@ ThrowCompletionOr parse_temporal_instant(VM& vm, String const& iso_stri // 5. Let utc be GetUTCEpochNanoseconds(result.[[Year]], result.[[Month]], result.[[Day]], result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]]). auto utc = get_utc_epoch_nanoseconds(result.year, result.month, result.day, result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond); - // 6. Let offsetNanoseconds be ? ParseTimeZoneOffsetString(offsetString). - auto offset_nanoseconds = TRY(parse_time_zone_offset_string(vm, *offset_string)); + // 6. If IsTimeZoneOffsetString(offsetString) is false, throw a RangeError exception. + if (!is_time_zone_offset_string(*offset_string)) + return vm.throw_completion(ErrorType::TemporalInvalidTimeZoneName, *offset_string); + + // 7. Let offsetNanoseconds be ParseTimeZoneOffsetString(offsetString). + auto offset_nanoseconds = parse_time_zone_offset_string(*offset_string); // 7. Let result be utc - ℤ(offsetNanoseconds). auto result_ns = utc.minus(Crypto::SignedBigInteger { offset_nanoseconds }); diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp index af824bafc9..ea46236c0b 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp @@ -66,11 +66,19 @@ ThrowCompletionOr create_temporal_time_zone(VM& vm, String const& ide // 2. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.TimeZone.prototype%", « [[InitializedTemporalTimeZone]], [[Identifier]], [[OffsetNanoseconds]] »). auto* object = TRY(ordinary_create_from_constructor(vm, *new_target, &Intrinsics::temporal_time_zone_prototype)); - // 3. Let offsetNanosecondsResult be Completion(ParseTimeZoneOffsetString(identifier)). - auto offset_nanoseconds_result = parse_time_zone_offset_string(vm, identifier); + // 3. If IsTimeZoneOffsetString(identifier) is true, then + if (is_time_zone_offset_string(identifier)) { + // a. Let offsetNanosecondsResult be ParseTimeZoneOffsetString(identifier). + auto offset_nanoseconds_result = parse_time_zone_offset_string(identifier); - // 4. If offsetNanosecondsResult is an abrupt completion, then - if (offset_nanoseconds_result.is_throw_completion()) { + // b. Set object.[[Identifier]] to ! FormatTimeZoneOffsetString(offsetNanosecondsResult). + object->set_identifier(format_time_zone_offset_string(offset_nanoseconds_result)); + + // c. Set object.[[OffsetNanoseconds]] to offsetNanosecondsResult. + object->set_offset_nanoseconds(offset_nanoseconds_result); + } + // 4. Else, + else { // a. Assert: ! CanonicalizeTimeZoneName(identifier) is identifier. VERIFY(canonicalize_time_zone_name(identifier) == identifier); @@ -80,16 +88,8 @@ ThrowCompletionOr create_temporal_time_zone(VM& vm, String const& ide // c. Set object.[[OffsetNanoseconds]] to undefined. // NOTE: No-op. } - // 5. Else, - else { - // a. Set object.[[Identifier]] to ! FormatTimeZoneOffsetString(offsetNanosecondsResult.[[Value]]). - object->set_identifier(format_time_zone_offset_string(offset_nanoseconds_result.value())); - // b. Set object.[[OffsetNanoseconds]] to offsetNanosecondsResult.[[Value]]. - object->set_offset_nanoseconds(offset_nanoseconds_result.value()); - } - - // 6. Return object. + // 5. Return object. return object; } @@ -206,80 +206,7 @@ bool is_valid_time_zone_numeric_utc_offset_syntax(String const& offset_string) return parse_time_zone_numeric_utc_offset_syntax(offset_string, discarded, discarded, optionally_discarded, optionally_discarded, optionally_discarded); } -// 11.6.5 ParseTimeZoneOffsetString ( offsetString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetimezoneoffsetstring -ThrowCompletionOr parse_time_zone_offset_string(VM& vm, String const& offset_string) -{ - // 1. Let parseResult be ParseText(StringToCodePoints(offsetString), TimeZoneNumericUTCOffset). - auto parse_result = parse_iso8601(Production::TimeZoneNumericUTCOffset, offset_string); - - // 2. If parseResult is a List of errors, throw a RangeError exception. - if (!parse_result.has_value()) - return vm.throw_completion(ErrorType::InvalidFormat, "TimeZone offset"); - - // 3. Let each of sign, hours, minutes, seconds, and fSeconds be the source text matched by the respective TimeZoneUTCOffsetSign, TimeZoneUTCOffsetHour, TimeZoneUTCOffsetMinute, TimeZoneUTCOffsetSecond, and TimeZoneUTCOffsetFraction Parse Node contained within parseResult, or an empty sequence of code points if not present. - auto sign = parse_result->time_zone_utc_offset_sign; - auto hours = parse_result->time_zone_utc_offset_hour; - auto minutes = parse_result->time_zone_utc_offset_minute; - auto seconds = parse_result->time_zone_utc_offset_second; - auto f_seconds = parse_result->time_zone_utc_offset_fraction; - - // 4. Assert: sign is not empty. - VERIFY(sign.has_value()); - - i8 factor; - - // 5. If sign contains the code point U+002D (HYPHEN-MINUS) or U+2212 (MINUS SIGN), then - if (sign->is_one_of("-", "\xE2\x88\x92")) { - // a. Let factor be -1. - factor = -1; - } - // 6. Else, - else { - // a. Let factor be 1. - factor = 1; - } - - // 7. Assert: hours is not empty. - VERIFY(hours.has_value()); - - // 8. Let hoursMV be ! ToIntegerOrInfinity(CodePointsToString(hours)). - auto hours_mv = *hours->to_uint(); - - // 9. Let minutesMV be ! ToIntegerOrInfinity(CodePointsToString(minutes)). - auto minutes_mv = *minutes.value_or("0"sv).to_uint(); - - // 10. Let secondsMV be ! ToIntegerOrInfinity(CodePointsToString(seconds)). - auto seconds_mv = *seconds.value_or("0"sv).to_uint(); - - u32 nanoseconds_mv; - - // 11. If fSeconds is not empty, then - if (f_seconds.has_value()) { - // a. Let fSecondsDigits be the substring of CodePointsToString(fSeconds) from 1. - auto f_seconds_digits = f_seconds->substring_view(1); - - // b. Let fSecondsDigitsExtended be the string-concatenation of fSecondsDigits and "000000000". - auto f_seconds_digits_extended = String::formatted("{}000000000", f_seconds_digits); - - // c. Let nanosecondsDigits be the substring of fSecondsDigitsExtended from 0 to 9. - auto nanoseconds_digits = f_seconds_digits_extended.substring_view(0, 9); - - // d. Let nanosecondsMV be ! ToIntegerOrInfinity(nanosecondsDigits). - nanoseconds_mv = *nanoseconds_digits.to_uint(); - } - // 12. Else, - else { - // a. Let nanosecondsMV be 0. - nanoseconds_mv = 0; - } - - // 13. Return factor × (((hoursMV × 60 + minutesMV) × 60 + secondsMV) × 10^9 + nanosecondsMV). - // NOTE: Using scientific notation (1e9) ensures the result of this expression is a double, - // which is important - otherwise it's all integers and the result overflows! - return factor * (((hours_mv * 60 + minutes_mv) * 60 + seconds_mv) * 1e9 + nanoseconds_mv); -} - -// 11.6.6 FormatTimeZoneOffsetString ( offsetNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-formattimezoneoffsetstring +// 11.6.5 FormatTimeZoneOffsetString ( offsetNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-formattimezoneoffsetstring String format_time_zone_offset_string(double offset_nanoseconds) { auto offset = static_cast(offset_nanoseconds); @@ -333,7 +260,7 @@ String format_time_zone_offset_string(double offset_nanoseconds) return builder.to_string(); } -// 11.6.7 FormatISOTimeZoneOffsetString ( offsetNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-formatisotimezoneoffsetstring +// 11.6.6 FormatISOTimeZoneOffsetString ( offsetNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-formatisotimezoneoffsetstring String format_iso_time_zone_offset_string(double offset_nanoseconds) { // 1. Assert: offsetNanoseconds is an integer. @@ -360,7 +287,7 @@ String format_iso_time_zone_offset_string(double offset_nanoseconds) return String::formatted("{}{:02}:{:02}", sign, (u32)hours, (u32)minutes); } -// 11.6.8 ToTemporalTimeZone ( temporalTimeZoneLike ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaltimezone +// 11.6.7 ToTemporalTimeZone ( temporalTimeZoneLike ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaltimezone ThrowCompletionOr to_temporal_time_zone(VM& vm, Value temporal_time_zone_like) { // 1. If Type(temporalTimeZoneLike) is Object, then @@ -396,8 +323,8 @@ ThrowCompletionOr to_temporal_time_zone(VM& vm, Value temporal_time_zon // a. Let name be parseResult.[[Name]]. auto name = parse_result.name.release_value(); - // b. If ParseText(StringToCodePoints(name), TimeZoneNumericUTCOffset) is a List of errors, then - if (!is_valid_time_zone_numeric_utc_offset_syntax(name)) { + // b. If IsTimeZoneOffsetString(name) is false, then + if (!is_time_zone_offset_string(name)) { // i. If IsValidTimeZoneName(name) is false, throw a RangeError exception. if (!is_valid_time_zone_name(name)) return vm.throw_completion(ErrorType::TemporalInvalidTimeZoneName, name); @@ -418,7 +345,7 @@ ThrowCompletionOr to_temporal_time_zone(VM& vm, Value temporal_time_zon return MUST(create_temporal_time_zone(vm, *parse_result.offset_string)); } -// 11.6.9 GetOffsetNanosecondsFor ( timeZone, instant ), https://tc39.es/proposal-temporal/#sec-temporal-getoffsetnanosecondsfor +// 11.6.8 GetOffsetNanosecondsFor ( timeZone, instant ), https://tc39.es/proposal-temporal/#sec-temporal-getoffsetnanosecondsfor ThrowCompletionOr get_offset_nanoseconds_for(VM& vm, Value time_zone, Instant& instant) { // 1. Let getOffsetNanosecondsFor be ? GetMethod(timeZone, "getOffsetNanosecondsFor"). @@ -446,7 +373,7 @@ ThrowCompletionOr get_offset_nanoseconds_for(VM& vm, Value time_zone, In return offset_nanoseconds; } -// 11.6.10 BuiltinTimeZoneGetOffsetStringFor ( timeZone, instant ), https://tc39.es/proposal-temporal/#sec-temporal-builtintimezonegetoffsetstringfor +// 11.6.9 BuiltinTimeZoneGetOffsetStringFor ( timeZone, instant ), https://tc39.es/proposal-temporal/#sec-temporal-builtintimezonegetoffsetstringfor ThrowCompletionOr builtin_time_zone_get_offset_string_for(VM& vm, Value time_zone, Instant& instant) { // 1. Let offsetNanoseconds be ? GetOffsetNanosecondsFor(timeZone, instant). @@ -456,7 +383,7 @@ ThrowCompletionOr builtin_time_zone_get_offset_string_for(VM& vm, Value return format_time_zone_offset_string(offset_nanoseconds); } -// 11.6.11 BuiltinTimeZoneGetPlainDateTimeFor ( timeZone, instant, calendar ), https://tc39.es/proposal-temporal/#sec-temporal-builtintimezonegetplaindatetimefor +// 11.6.10 BuiltinTimeZoneGetPlainDateTimeFor ( timeZone, instant, calendar ), https://tc39.es/proposal-temporal/#sec-temporal-builtintimezonegetplaindatetimefor ThrowCompletionOr builtin_time_zone_get_plain_date_time_for(VM& vm, Value time_zone, Instant& instant, Object& calendar) { // 1. Assert: instant has an [[InitializedTemporalInstant]] internal slot. @@ -474,7 +401,7 @@ ThrowCompletionOr builtin_time_zone_get_plain_date_time_for(VM& return create_temporal_date_time(vm, result.year, result.month, result.day, result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond, calendar); } -// 11.6.12 BuiltinTimeZoneGetInstantFor ( timeZone, dateTime, disambiguation ), https://tc39.es/proposal-temporal/#sec-temporal-builtintimezonegetinstantfor +// 11.6.11 BuiltinTimeZoneGetInstantFor ( timeZone, dateTime, disambiguation ), https://tc39.es/proposal-temporal/#sec-temporal-builtintimezonegetinstantfor ThrowCompletionOr builtin_time_zone_get_instant_for(VM& vm, Value time_zone, PlainDateTime& date_time, StringView disambiguation) { // 1. Assert: dateTime has an [[InitializedTemporalDateTime]] internal slot. @@ -486,7 +413,7 @@ ThrowCompletionOr builtin_time_zone_get_instant_for(VM& vm, Value time return disambiguate_possible_instants(vm, possible_instants, time_zone, date_time, disambiguation); } -// 11.6.13 DisambiguatePossibleInstants ( possibleInstants, timeZone, dateTime, disambiguation ), https://tc39.es/proposal-temporal/#sec-temporal-disambiguatepossibleinstants +// 11.6.12 DisambiguatePossibleInstants ( possibleInstants, timeZone, dateTime, disambiguation ), https://tc39.es/proposal-temporal/#sec-temporal-disambiguatepossibleinstants ThrowCompletionOr disambiguate_possible_instants(VM& vm, MarkedVector const& possible_instants, Value time_zone, PlainDateTime& date_time, StringView disambiguation) { // 1. Assert: dateTime has an [[InitializedTemporalDateTime]] internal slot. @@ -604,7 +531,7 @@ ThrowCompletionOr disambiguate_possible_instants(VM& vm, MarkedVector< return possible_instants_[n - 1]; } -// 11.6.14 GetPossibleInstantsFor ( timeZone, dateTime ), https://tc39.es/proposal-temporal/#sec-temporal-getpossibleinstantsfor +// 11.6.13 GetPossibleInstantsFor ( timeZone, dateTime ), https://tc39.es/proposal-temporal/#sec-temporal-getpossibleinstantsfor ThrowCompletionOr> get_possible_instants_for(VM& vm, Value time_zone, PlainDateTime& date_time) { // 1. Assert: dateTime has an [[InitializedTemporalDateTime]] internal slot. @@ -649,7 +576,7 @@ ThrowCompletionOr> get_possible_instants_for(VM& vm, Valu return { move(list) }; } -// 11.6.15 TimeZoneEquals ( one, two ), https://tc39.es/proposal-temporal/#sec-temporal-timezoneequals +// 11.6.14 TimeZoneEquals ( one, two ), https://tc39.es/proposal-temporal/#sec-temporal-timezoneequals ThrowCompletionOr time_zone_equals(VM& vm, Object& one, Object& two) { // 1. If one and two are the same Object value, return true. diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h index a249317ac8..ff5dd5ac0f 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h @@ -42,7 +42,6 @@ ThrowCompletionOr create_temporal_time_zone(VM&, String const& identi ISODateTime get_iso_parts_from_epoch(VM&, Crypto::SignedBigInteger const& epoch_nanoseconds); BigInt* get_iana_time_zone_next_transition(VM&, BigInt const& epoch_nanoseconds, StringView time_zone_identifier); BigInt* get_iana_time_zone_previous_transition(VM&, BigInt const& epoch_nanoseconds, StringView time_zone_identifier); -ThrowCompletionOr parse_time_zone_offset_string(VM&, String const&); String format_time_zone_offset_string(double offset_nanoseconds); String format_iso_time_zone_offset_string(double offset_nanoseconds); ThrowCompletionOr to_temporal_time_zone(VM&, Value temporal_time_zone_like); diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZoneConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZoneConstructor.cpp index bd0967ddcc..7597417692 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZoneConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZoneConstructor.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include @@ -49,9 +50,8 @@ ThrowCompletionOr TimeZoneConstructor::construct(FunctionObject& new_ta // 2. Set identifier to ? ToString(identifier). auto identifier = TRY(vm.argument(0).to_string(vm)); - // 3. Let parseResult be ParseText(StringToCodePoints(identifier), TimeZoneNumericUTCOffset). - // 4. If parseResult is a List of errors, then - if (!is_valid_time_zone_numeric_utc_offset_syntax(identifier)) { + // 3. If IsTimeZoneOffsetString(identifier) is false, then + if (!is_time_zone_offset_string(identifier)) { // a. If IsValidTimeZoneName(identifier) is false, then if (!is_valid_time_zone_name(identifier)) { // i. Throw a RangeError exception. @@ -62,7 +62,7 @@ ThrowCompletionOr TimeZoneConstructor::construct(FunctionObject& new_ta identifier = canonicalize_time_zone_name(identifier); } - // 5. Return ? CreateTemporalTimeZone(identifier, NewTarget). + // 4. Return ? CreateTemporalTimeZone(identifier, NewTarget). return TRY(create_temporal_time_zone(vm, identifier, &new_target)); } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp index 1c695e6312..3e6b4a4256 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp @@ -197,8 +197,8 @@ ThrowCompletionOr to_temporal_zoned_date_time(VM& vm, Value item // e. Assert: timeZoneName is not undefined. VERIFY(time_zone_name.has_value()); - // f. If ParseText(StringToCodePoints(timeZoneName), TimeZoneNumericUTCOffset) is a List of errors, then - if (!is_valid_time_zone_numeric_utc_offset_syntax(*time_zone_name)) { + // f. If IsTimeZoneOffsetString(timeZoneName) is false, then + if (!is_time_zone_offset_string(*time_zone_name)) { // i. If IsValidTimeZoneName(timeZoneName) is false, throw a RangeError exception. if (!is_valid_time_zone_name(*time_zone_name)) return vm.throw_completion(ErrorType::TemporalInvalidTimeZoneName, *time_zone_name); @@ -239,8 +239,12 @@ ThrowCompletionOr to_temporal_zoned_date_time(VM& vm, Value item // 8. If offsetBehaviour is option, then if (offset_behavior == OffsetBehavior::Option) { + // a. If IsTimeZoneOffsetString(offsetString) is false, throw a RangeError exception. + if (!is_time_zone_offset_string(*offset_string)) + return vm.throw_completion(ErrorType::TemporalInvalidTimeZoneName, *offset_string); + // a. Set offsetNanoseconds to ? ParseTimeZoneOffsetString(offsetString). - offset_nanoseconds = TRY(parse_time_zone_offset_string(vm, *offset_string)); + offset_nanoseconds = parse_time_zone_offset_string(*offset_string); } // 9. Let disambiguation be ? ToTemporalDisambiguation(options). diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimePrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimePrototype.cpp index 2e57b226ba..bd385a2174 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimePrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimePrototype.cpp @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -772,21 +773,26 @@ JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::with) fields = TRY(prepare_temporal_fields(vm, *fields, field_names, Vector { "timeZone"sv, "offset"sv })); // 17. Let offsetString be ! Get(fields, "offset"). - auto offset_string = MUST(fields->get(vm.names.offset)); + auto offset_string_value = MUST(fields->get(vm.names.offset)); // 18. Assert: Type(offsetString) is String. - VERIFY(offset_string.is_string()); + VERIFY(offset_string_value.is_string()); + auto const& offset_string = offset_string_value.as_string().string(); // 19. Let dateTimeResult be ? InterpretTemporalDateTimeFields(calendar, fields, options). auto date_time_result = TRY(interpret_temporal_date_time_fields(vm, calendar, *fields, *options)); - // 20. Let offsetNanoseconds be ? ParseTimeZoneOffsetString(offsetString). - auto offset_nanoseconds = TRY(parse_time_zone_offset_string(vm, offset_string.as_string().string())); + // 20. If IsTimeZoneOffsetString(offsetString) is false, throw a RangeError exception. + if (!is_time_zone_offset_string(offset_string)) + return vm.throw_completion(ErrorType::TemporalInvalidTimeZoneName, offset_string); - // 21. Let epochNanoseconds be ? InterpretISODateTimeOffset(dateTimeResult.[[Year]], dateTimeResult.[[Month]], dateTimeResult.[[Day]], dateTimeResult.[[Hour]], dateTimeResult.[[Minute]], dateTimeResult.[[Second]], dateTimeResult.[[Millisecond]], dateTimeResult.[[Microsecond]], dateTimeResult.[[Nanosecond]], option, offsetNanoseconds, timeZone, disambiguation, offset, match exactly). + // 21. Let offsetNanoseconds be ParseTimeZoneOffsetString(offsetString). + auto offset_nanoseconds = parse_time_zone_offset_string(offset_string); + + // 22. Let epochNanoseconds be ? InterpretISODateTimeOffset(dateTimeResult.[[Year]], dateTimeResult.[[Month]], dateTimeResult.[[Day]], dateTimeResult.[[Hour]], dateTimeResult.[[Minute]], dateTimeResult.[[Second]], dateTimeResult.[[Millisecond]], dateTimeResult.[[Microsecond]], dateTimeResult.[[Nanosecond]], option, offsetNanoseconds, timeZone, disambiguation, offset, match exactly). auto* epoch_nanoseconds = TRY(interpret_iso_date_time_offset(vm, date_time_result.year, date_time_result.month, date_time_result.day, date_time_result.hour, date_time_result.minute, date_time_result.second, date_time_result.millisecond, date_time_result.microsecond, date_time_result.nanosecond, OffsetBehavior::Option, offset_nanoseconds, &time_zone, disambiguation, offset, MatchBehavior::MatchExactly)); - // 22. Return ! CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar). + // 23. Return ! CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar). return MUST(create_temporal_zoned_date_time(vm, *epoch_nanoseconds, time_zone, calendar)); }