diff options
author | Linus Groh <mail@linusgroh.de> | 2022-01-13 00:34:12 +0100 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2022-01-13 10:08:34 +0100 |
commit | b9093dd0abac81d750ee19a229f185fff0c9af8e (patch) | |
tree | cf6bd074e6e10753313e20cc59133733a1795992 | |
parent | 6394ff4ea8483f73897d8a62fb1f6b1260d12246 (diff) | |
download | serenity-b9093dd0abac81d750ee19a229f185fff0c9af8e.zip |
LibJS: Don't validate time zone name when parsing Instant string
This is normative change in the Temporal spec.
See: https://github.com/tc39/proposal-temporal/commit/2a81fbc
5 files changed, 119 insertions, 76 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index 555ae46ced..715db75ff1 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h @@ -254,6 +254,7 @@ M(TemporalObjectMustNotHave, "Object must not have a defined {} property") \ M(TemporalPropertyMustBeFinite, "Property must not be Infinity") \ M(TemporalPropertyMustBePositiveInteger, "Property must be a positive integer") \ + M(TemporalTimeZoneOffsetStringMismatch, "Time zone offset string mismatch: '{}' is not equal to '{}'") \ M(TemporalZonedDateTimeRoundZeroLengthDay, "Cannot round a ZonedDateTime in a calendar that has zero-length days") \ M(ThisHasNotBeenInitialized, "|this| has not been initialized") \ M(ThisIsAlreadyInitialized, "|this| is already initialized") \ diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp index b24bc03a15..1416dcdb82 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp @@ -632,7 +632,13 @@ ThrowCompletionOr<Value> to_relative_temporal_object(GlobalObject& global_object // j. Let timeZone be ? Get(value, "timeZone"). time_zone = TRY(value_object.get(vm.names.timeZone)); - // k. If offsetString is undefined, then + // k. If timeZone is not undefined, then + if (!time_zone.is_undefined()) { + // i. Set timeZone to ? ToTemporalTimeZone(timeZone). + time_zone = TRY(to_temporal_time_zone(global_object, time_zone)); + } + + // l. If offsetString is undefined, then if (offset_string.is_undefined()) { // i. Set offsetBehaviour to wall. offset_behavior = OffsetBehavior::Wall; @@ -655,21 +661,44 @@ ThrowCompletionOr<Value> to_relative_temporal_object(GlobalObject& global_object // d. Let offsetString be result.[[TimeZoneOffset]]. offset_string = parsed_result.time_zone.offset.has_value() ? js_string(vm, *parsed_result.time_zone.offset) : js_undefined(); - // e. Let timeZone be result.[[TimeZoneIANAName]]. - time_zone = parsed_result.time_zone.name.has_value() ? js_string(vm, *parsed_result.time_zone.name) : js_undefined(); + // e. Let timeZoneName be result.[[TimeZoneIANAName]]. + auto time_zone_name = parsed_result.time_zone.name; + + // f. If timeZoneName is not undefined, then + if (time_zone_name.has_value()) { + // i. If ParseText(! StringToCodePoints(timeZoneName), TimeZoneNumericUTCOffset) is not a List of errors, then + // FIXME: Logic error in the spec (check for no errors -> check for errors). + // See: https://github.com/tc39/proposal-temporal/pull/2000 + if (!is_valid_time_zone_numeric_utc_offset_syntax(*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<RangeError>(global_object, ErrorType::TemporalInvalidTimeZoneName, *time_zone_name); + + // 2. Set timeZoneName to ! CanonicalizeTimeZoneName(timeZoneName). + time_zone_name = canonicalize_time_zone_name(*time_zone_name); + } + + // ii. Let timeZone be ! CreateTemporalTimeZone(timeZoneName). + time_zone = MUST(create_temporal_time_zone(global_object, *time_zone_name)); + } + // g. Else, + else { + // i. Let timeZone be undefined. + time_zone = js_undefined(); + } - // f. If result.[[TimeZoneZ]] is true, then + // h. If result.[[TimeZoneZ]] is true, then if (parsed_result.time_zone.z) { // i. Set offsetBehaviour to exact. offset_behavior = OffsetBehavior::Exact; } - // g. Else if offsetString is undefined, then + // i. Else if offsetString is undefined, then else if (offset_string.is_undefined()) { // i. Set offsetBehaviour to wall. offset_behavior = OffsetBehavior::Wall; } - // h. Set matchBehaviour to match minutes. + // j. Set matchBehaviour to match minutes. match_behavior = MatchBehavior::MatchMinutes; // See NOTE above about why this is done. @@ -678,12 +707,9 @@ ThrowCompletionOr<Value> to_relative_temporal_object(GlobalObject& global_object // 8. If timeZone is not undefined, then if (!time_zone.is_undefined()) { - // a. Set timeZone to ? ToTemporalTimeZone(timeZone). - time_zone = TRY(to_temporal_time_zone(global_object, time_zone)); - double offset_ns; - // b. If offsetBehaviour is option, then + // a. If offsetBehaviour is option, then if (offset_behavior == OffsetBehavior::Option) { // i. Set offsetString to ? ToString(offsetString). // 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. @@ -692,16 +718,16 @@ ThrowCompletionOr<Value> to_relative_temporal_object(GlobalObject& global_object // ii. Let offsetNs be ? ParseTimeZoneOffsetString(offsetString). offset_ns = TRY(parse_time_zone_offset_string(global_object, actual_offset_string)); } - // c. Else, + // b. Else, else { // i. Let offsetNs be 0. offset_ns = 0; } - // d. Let epochNanoseconds be ? InterpretISODateTimeOffset(result.[[Year]], result.[[Month]], result.[[Day]], result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]], offsetBehaviour, offsetNs, timeZone, "compatible", "reject", matchBehaviour). + // c. Let epochNanoseconds be ? InterpretISODateTimeOffset(result.[[Year]], result.[[Month]], result.[[Day]], result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]], offsetBehaviour, offsetNs, timeZone, "compatible", "reject", matchBehaviour). auto* epoch_nanoseconds = TRY(interpret_iso_date_time_offset(global_object, result.year, result.month, result.day, result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond, offset_behavior, offset_ns, time_zone, "compatible"sv, "reject"sv, match_behavior)); - // e. Return ! CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar). + // d. Return ! CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar). return MUST(create_temporal_zoned_date_time(global_object, *epoch_nanoseconds, time_zone.as_object(), *calendar)); } @@ -1681,19 +1707,8 @@ ThrowCompletionOr<TemporalTimeZone> parse_temporal_time_zone_string(GlobalObject offset = format_time_zone_offset_string(offset_nanoseconds); } - Optional<String> name; - // 7. If name is not undefined, then - if (name_part.has_value()) { - // a. If ! IsValidTimeZoneName(name) is false, throw a RangeError exception. - if (!is_valid_time_zone_name(*name_part)) - return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidTimeZoneName, *name_part); - - // b. Set name to ! CanonicalizeTimeZoneName(name). - name = canonicalize_time_zone_name(*name_part); - } - - // 8. Return the Record { [[Z]]: false, [[OffsetString]]: offsetString, [[Name]]: name }. - return TemporalTimeZone { .z = false, .offset = offset, .name = name }; + // 7. Return the Record { [[Z]]: false, [[OffsetString]]: offsetString, [[Name]]: name }. + return TemporalTimeZone { .z = false, .offset = offset, .name = name_part.has_value() ? String { *name_part } : Optional<String> {} }; } // 13.44 ParseTemporalYearMonthString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalyearmonthstring diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp index b0c8e015b2..6d604ae84e 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp @@ -63,27 +63,7 @@ String default_time_zone() return ::TimeZone::current_time_zone(); } -// 11.6.1 ParseTemporalTimeZone ( string ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaltimezone -ThrowCompletionOr<String> parse_temporal_time_zone(GlobalObject& global_object, String const& string) -{ - // 1. Assert: Type(string) is String. - - // 2. Let result be ? ParseTemporalTimeZoneString(string). - auto result = TRY(parse_temporal_time_zone_string(global_object, string)); - - // 3. If result.[[Name]] is not undefined, return result.[[Name]]. - if (result.name.has_value()) - return *result.name; - - // 4. If result.[[Z]] is true, return "UTC". - if (result.z) - return String { "UTC" }; - - // 5. Return result.[[OffsetString]]. - return *result.offset; -} - -// 11.6.2 CreateTemporalTimeZone ( identifier [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporaltimezone +// 11.6.1 CreateTemporalTimeZone ( identifier [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporaltimezone ThrowCompletionOr<TimeZone*> create_temporal_time_zone(GlobalObject& global_object, String const& identifier, FunctionObject const* new_target) { // 1. If newTarget is not present, set it to %Temporal.TimeZone%. @@ -112,7 +92,7 @@ ThrowCompletionOr<TimeZone*> create_temporal_time_zone(GlobalObject& global_obje return object; } -// 11.6.3 GetISOPartsFromEpoch ( epochNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-getisopartsfromepoch +// 11.6.2 GetISOPartsFromEpoch ( epochNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-getisopartsfromepoch ISODateTime get_iso_parts_from_epoch(BigInt const& epoch_nanoseconds) { // 1. Assert: epochNanoseconds is an integer. @@ -156,7 +136,7 @@ ISODateTime get_iso_parts_from_epoch(BigInt const& epoch_nanoseconds) return { .year = year, .month = month, .day = day, .hour = hour, .minute = minute, .second = second, .millisecond = millisecond, .microsecond = static_cast<u16>(microsecond), .nanosecond = static_cast<u16>(nanosecond) }; } -// 11.6.4 GetIANATimeZoneEpochValue ( timeZoneIdentifier, year, month, day, hour, minute, second, millisecond, microsecond, nanosecond ), https://tc39.es/proposal-temporal/#sec-temporal-getianatimezoneepochvalue +// 11.6.3 GetIANATimeZoneEpochValue ( timeZoneIdentifier, year, month, day, hour, minute, second, millisecond, microsecond, nanosecond ), https://tc39.es/proposal-temporal/#sec-temporal-getianatimezoneepochvalue MarkedValueList get_iana_time_zone_epoch_value(GlobalObject& global_object, [[maybe_unused]] StringView time_zone_identifier, i32 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond) { // The abstract operation GetIANATimeZoneEpochValue is an implementation-defined algorithm that returns a List of integers. Each integer in the List represents a number of nanoseconds since the Unix epoch in UTC that may correspond to the given calendar date and wall-clock time in the IANA time zone identified by timeZoneIdentifier. @@ -170,7 +150,7 @@ MarkedValueList get_iana_time_zone_epoch_value(GlobalObject& global_object, [[ma return list; } -// 11.6.5 GetIANATimeZoneOffsetNanoseconds ( epochNanoseconds, timeZoneIdentifier ), https://tc39.es/proposal-temporal/#sec-temporal-getianatimezoneoffsetnanoseconds +// 11.6.4 GetIANATimeZoneOffsetNanoseconds ( epochNanoseconds, timeZoneIdentifier ), https://tc39.es/proposal-temporal/#sec-temporal-getianatimezoneoffsetnanoseconds i64 get_iana_time_zone_offset_nanoseconds(BigInt const& epoch_nanoseconds, String const& time_zone_identifier) { // The abstract operation GetIANATimeZoneOffsetNanoseconds is an implementation-defined algorithm that returns an integer representing the offset of the IANA time zone identified by timeZoneIdentifier from UTC, at the instant corresponding to epochNanoseconds. @@ -201,7 +181,7 @@ i64 get_iana_time_zone_offset_nanoseconds(BigInt const& epoch_nanoseconds, Strin return *offset_seconds * 1'000'000'000; } -// 11.6.6 GetIANATimeZoneNextTransition ( epochNanoseconds, timeZoneIdentifier ), https://tc39.es/proposal-temporal/#sec-temporal-getianatimezonenexttransition +// 11.6.5 GetIANATimeZoneNextTransition ( epochNanoseconds, timeZoneIdentifier ), https://tc39.es/proposal-temporal/#sec-temporal-getianatimezonenexttransition BigInt* get_iana_time_zone_next_transition(GlobalObject&, [[maybe_unused]] BigInt const& epoch_nanoseconds, [[maybe_unused]] StringView time_zone_identifier) { // The abstract operation GetIANATimeZoneNextTransition is an implementation-defined algorithm that returns an integer representing the number of nanoseconds since the Unix epoch in UTC that corresponds to the first time zone transition after epochNanoseconds in the IANA time zone identified by timeZoneIdentifier or null if no such transition exists. @@ -210,7 +190,7 @@ BigInt* get_iana_time_zone_next_transition(GlobalObject&, [[maybe_unused]] BigIn return nullptr; } -// 11.6.7 GetIANATimeZonePreviousTransition ( epochNanoseconds, timeZoneIdentifier ), https://tc39.es/proposal-temporal/#sec-temporal-getianatimezoneprevioustransition +// 11.6.6 GetIANATimeZonePreviousTransition ( epochNanoseconds, timeZoneIdentifier ), https://tc39.es/proposal-temporal/#sec-temporal-getianatimezoneprevioustransition BigInt* get_iana_time_zone_previous_transition(GlobalObject&, [[maybe_unused]] BigInt const& epoch_nanoseconds, [[maybe_unused]] StringView time_zone_identifier) { // The abstract operation GetIANATimeZonePreviousTransition is an implementation-defined algorithm that returns an integer representing the number of nanoseconds since the Unix epoch in UTC that corresponds to the last time zone transition before epochNanoseconds in the IANA time zone identified by timeZoneIdentifier or null if no such transition exists. @@ -262,7 +242,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.8 ParseTimeZoneOffsetString ( offsetString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetimezoneoffsetstring +// 11.6.7 ParseTimeZoneOffsetString ( offsetString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetimezoneoffsetstring ThrowCompletionOr<double> parse_time_zone_offset_string(GlobalObject& global_object, String const& offset_string) { auto& vm = global_object.vm(); @@ -324,7 +304,7 @@ ThrowCompletionOr<double> parse_time_zone_offset_string(GlobalObject& global_obj return sign * (((hours * 60 + minutes) * 60 + seconds) * 1000000000.0 + nanoseconds); } -// 11.6.9 FormatTimeZoneOffsetString ( offsetNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-formattimezoneoffsetstring +// 11.6.8 FormatTimeZoneOffsetString ( offsetNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-formattimezoneoffsetstring String format_time_zone_offset_string(double offset_nanoseconds) { auto offset = static_cast<i64>(offset_nanoseconds); @@ -378,7 +358,7 @@ String format_time_zone_offset_string(double offset_nanoseconds) return builder.to_string(); } -// 11.6.10 FormatISOTimeZoneOffsetString ( offsetNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-formatisotimezoneoffsetstring +// 11.6.9 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. @@ -405,7 +385,7 @@ String format_iso_time_zone_offset_string(double offset_nanoseconds) return String::formatted("{}{:02}:{:02}", sign, (u32)hours, (u32)minutes); } -// 11.6.11 ToTemporalTimeZone ( temporalTimeZoneLike ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaltimezone +// 11.6.10 ToTemporalTimeZone ( temporalTimeZoneLike ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaltimezone ThrowCompletionOr<Object*> to_temporal_time_zone(GlobalObject& global_object, Value temporal_time_zone_like) { auto& vm = global_object.vm(); @@ -435,14 +415,43 @@ ThrowCompletionOr<Object*> to_temporal_time_zone(GlobalObject& global_object, Va // 2. Let identifier be ? ToString(temporalTimeZoneLike). auto identifier = TRY(temporal_time_zone_like.to_string(global_object)); - // 3. Let result be ? ParseTemporalTimeZone(identifier). - auto result = TRY(parse_temporal_time_zone(global_object, identifier)); + // 3. Let parseResult be ? ParseTemporalTimeZoneString(identifier). + auto parse_result = TRY(parse_temporal_time_zone_string(global_object, identifier)); + + // TODO: This currently cannot be tested as ParseTemporalTimeZoneString only considers + // TimeZoneIANAName for the returned [[Name]] slot, not TimeZoneUTCOffsetName. + // So when we provide a numeric time zone offset, this branch won't be executed, + // and if we provide an IANA name, it won't be a valid TimeZoneNumericUTCOffset. + // This should be fixed by: https://github.com/tc39/proposal-temporal/pull/1941 + + // 4. If parseResult.[[Name]] is not undefined, then + if (parse_result.name.has_value()) { + // a. If ParseText(! StringToCodePoints(parseResult.[[Name]], TimeZoneNumericUTCOffset)) is not a List of errors, then + if (is_valid_time_zone_numeric_utc_offset_syntax(*parse_result.name)) { + // i. If parseResult.[[OffsetString]] is not undefined, and ! ParseTimeZoneOffsetString(parseResult.[[OffsetString]]) ≠ ! ParseTimeZoneOffsetString(parseResult.[[Name]]), throw a RangeError exception. + if (parse_result.offset.has_value() && (MUST(parse_time_zone_offset_string(global_object, *parse_result.offset)) != MUST(parse_time_zone_offset_string(global_object, *parse_result.name)))) + return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalTimeZoneOffsetStringMismatch, *parse_result.offset, *parse_result.name); + } + // b. Else, + else { + // i. If ! IsValidTimeZoneName(parseResult.[[Name]]) is false, throw a RangeError exception. + if (!is_valid_time_zone_name(*parse_result.name)) + return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidTimeZoneName, *parse_result.name); + } + + // c. Return ! CreateTemporalTimeZone(! CanonicalizeTimeZoneName(parseResult.[[Name]])). + return MUST(create_temporal_time_zone(global_object, canonicalize_time_zone_name(*parse_result.name))); + } + + // 5. If parseResult.[[Z]] is true, return ! CreateTemporalTimeZone("UTC"). + if (parse_result.z) + return MUST(create_temporal_time_zone(global_object, "UTC"sv)); - // 4. Return ? CreateTemporalTimeZone(result). - return TRY(create_temporal_time_zone(global_object, result)); + // 6. Return ! CreateTemporalTimeZone(parseResult.[[OffsetString]]). + return MUST(create_temporal_time_zone(global_object, *parse_result.offset)); } -// 11.6.12 GetOffsetNanosecondsFor ( timeZone, instant ), https://tc39.es/proposal-temporal/#sec-temporal-getoffsetnanosecondsfor +// 11.6.11 GetOffsetNanosecondsFor ( timeZone, instant ), https://tc39.es/proposal-temporal/#sec-temporal-getoffsetnanosecondsfor ThrowCompletionOr<double> get_offset_nanoseconds_for(GlobalObject& global_object, Value time_zone, Instant& instant) { auto& vm = global_object.vm(); @@ -472,7 +481,7 @@ ThrowCompletionOr<double> get_offset_nanoseconds_for(GlobalObject& global_object return offset_nanoseconds; } -// 11.6.13 BuiltinTimeZoneGetOffsetStringFor ( timeZone, instant ), https://tc39.es/proposal-temporal/#sec-temporal-builtintimezonegetoffsetstringfor +// 11.6.12 BuiltinTimeZoneGetOffsetStringFor ( timeZone, instant ), https://tc39.es/proposal-temporal/#sec-temporal-builtintimezonegetoffsetstringfor ThrowCompletionOr<String> builtin_time_zone_get_offset_string_for(GlobalObject& global_object, Value time_zone, Instant& instant) { // 1. Let offsetNanoseconds be ? GetOffsetNanosecondsFor(timeZone, instant). @@ -482,7 +491,7 @@ ThrowCompletionOr<String> builtin_time_zone_get_offset_string_for(GlobalObject& return format_time_zone_offset_string(offset_nanoseconds); } -// 11.6.14 BuiltinTimeZoneGetPlainDateTimeFor ( timeZone, instant, calendar ), https://tc39.es/proposal-temporal/#sec-temporal-builtintimezonegetplaindatetimefor +// 11.6.13 BuiltinTimeZoneGetPlainDateTimeFor ( timeZone, instant, calendar ), https://tc39.es/proposal-temporal/#sec-temporal-builtintimezonegetplaindatetimefor ThrowCompletionOr<PlainDateTime*> builtin_time_zone_get_plain_date_time_for(GlobalObject& global_object, Value time_zone, Instant& instant, Object& calendar) { // 1. Assert: instant has an [[InitializedTemporalInstant]] internal slot. @@ -500,7 +509,7 @@ ThrowCompletionOr<PlainDateTime*> builtin_time_zone_get_plain_date_time_for(Glob return create_temporal_date_time(global_object, result.year, result.month, result.day, result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond, calendar); } -// 11.6.15 BuiltinTimeZoneGetInstantFor ( timeZone, dateTime, disambiguation ), https://tc39.es/proposal-temporal/#sec-temporal-builtintimezonegetinstantfor +// 11.6.14 BuiltinTimeZoneGetInstantFor ( timeZone, dateTime, disambiguation ), https://tc39.es/proposal-temporal/#sec-temporal-builtintimezonegetinstantfor ThrowCompletionOr<Instant*> builtin_time_zone_get_instant_for(GlobalObject& global_object, Value time_zone, PlainDateTime& date_time, StringView disambiguation) { // 1. Assert: dateTime has an [[InitializedTemporalDateTime]] internal slot. @@ -512,7 +521,7 @@ ThrowCompletionOr<Instant*> builtin_time_zone_get_instant_for(GlobalObject& glob return disambiguate_possible_instants(global_object, possible_instants, time_zone, date_time, disambiguation); } -// 11.6.16 DisambiguatePossibleInstants ( possibleInstants, timeZone, dateTime, disambiguation ), https://tc39.es/proposal-temporal/#sec-temporal-disambiguatepossibleinstants +// 11.6.15 DisambiguatePossibleInstants ( possibleInstants, timeZone, dateTime, disambiguation ), https://tc39.es/proposal-temporal/#sec-temporal-disambiguatepossibleinstants ThrowCompletionOr<Instant*> disambiguate_possible_instants(GlobalObject& global_object, Vector<Value> const& possible_instants, Value time_zone, PlainDateTime& date_time, StringView disambiguation) { // TODO: MarkedValueList<T> would be nice, then we could pass a Vector<Instant*> here and wouldn't need the casts... @@ -625,7 +634,7 @@ ThrowCompletionOr<Instant*> disambiguate_possible_instants(GlobalObject& global_ return &static_cast<Instant&>(const_cast<Object&>(instant.as_object())); } -// 11.6.17 GetPossibleInstantsFor ( timeZone, dateTime ), https://tc39.es/proposal-temporal/#sec-temporal-getpossibleinstantsfor +// 11.6.16 GetPossibleInstantsFor ( timeZone, dateTime ), https://tc39.es/proposal-temporal/#sec-temporal-getpossibleinstantsfor ThrowCompletionOr<MarkedValueList> get_possible_instants_for(GlobalObject& global_object, Value time_zone, PlainDateTime& date_time) { auto& vm = global_object.vm(); @@ -672,7 +681,7 @@ ThrowCompletionOr<MarkedValueList> get_possible_instants_for(GlobalObject& globa return { move(list) }; } -// 11.6.18 TimeZoneEquals ( one, two ), https://tc39.es/proposal-temporal/#sec-temporal-timezoneequals +// 11.6.17 TimeZoneEquals ( one, two ), https://tc39.es/proposal-temporal/#sec-temporal-timezoneequals ThrowCompletionOr<bool> time_zone_equals(GlobalObject& global_object, Object& one, Object& two) { // 1. If one and two are the same Object value, return true. diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp index f92eee6582..d915d0a38d 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp @@ -198,33 +198,46 @@ ThrowCompletionOr<ZonedDateTime*> to_temporal_zoned_date_time(GlobalObject& glob // NOTE: The ISODateTime struct inside parsed_result will be moved into `result` at the end of this path to avoid mismatching names. // Thus, all remaining references to `result` in this path actually refers to `parsed_result`. - // d. Assert: result.[[TimeZoneName]] is not undefined. - VERIFY(parsed_result.time_zone.name.has_value()); + // d. Let timeZoneName be result.[[TimeZoneName]]. + auto time_zone_name = parsed_result.time_zone.name; - // e. Let offsetString be result.[[TimeZoneOffsetString]]. + // 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)) { + // i. If ! IsValidTimeZoneName(timeZoneName) is false, throw a RangeError exception. + if (!is_valid_time_zone_name(*time_zone_name)) + return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidTimeZoneName, *time_zone_name); + + // ii. Set timeZoneName to ! CanonicalizeTimeZoneName(timeZoneName). + time_zone_name = canonicalize_time_zone_name(*time_zone_name); + } + + // g. Let offsetString be result.[[TimeZoneOffsetString]]. offset_string = move(parsed_result.time_zone.offset); - // f. If result.[[TimeZoneZ]] is true, then + // h. If result.[[TimeZoneZ]] is true, then if (parsed_result.time_zone.z) { // i. Set offsetBehaviour to exact. offset_behavior = OffsetBehavior::Exact; } - // g. Else if offsetString is undefined, then + // i. Else if offsetString is undefined, then else if (!offset_string.has_value()) { // i. Set offsetBehaviour to wall. offset_behavior = OffsetBehavior::Wall; } - // h. Let timeZone be ? CreateTemporalTimeZone(result.[[TimeZoneName]]). - time_zone = TRY(create_temporal_time_zone(global_object, *parsed_result.time_zone.name)); + // j. Let timeZone be ! CreateTemporalTimeZone(timeZoneName). + time_zone = MUST(create_temporal_time_zone(global_object, *time_zone_name)); - // i. Let calendar be ? ToTemporalCalendarWithISODefault(result.[[Calendar]]). + // k. Let calendar be ? ToTemporalCalendarWithISODefault(result.[[Calendar]]). auto temporal_calendar_like = parsed_result.date_time.calendar.has_value() ? js_string(vm, parsed_result.date_time.calendar.value()) : js_undefined(); calendar = TRY(to_temporal_calendar_with_iso_default(global_object, temporal_calendar_like)); - // j. Set matchBehaviour to match minutes. + // l. Set matchBehaviour to match minutes. match_behavior = MatchBehavior::MatchMinutes; // See NOTE above about why this is done. diff --git a/Userland/Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.from.js b/Userland/Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.from.js index 72c67e68bc..4192ab867c 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.from.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.from.js @@ -18,6 +18,11 @@ describe("correct behavior", () => { expect(Temporal.Instant.from("1975-02-02T14:25:36.123456789Z").epochNanoseconds).toBe( 160583136123456789n ); + // Time zone is not validated + expect( + Temporal.Instant.from("1975-02-02T14:25:36.123456789Z[Custom/TimeZone]") + .epochNanoseconds + ).toBe(160583136123456789n); }); }); |