diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index 52e729d8528..46bf6524c12 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h @@ -224,6 +224,7 @@ M(TemporalInvalidPlainTimeLikeObject, "Invalid plain time-like object") \ M(TemporalInvalidPlainYearMonth, "Invalid plain year month") \ M(TemporalInvalidTime, "Invalid time") \ + M(TemporalInvalidTimeString, "Invalid time string '{}'") \ M(TemporalInvalidTimeZoneName, "Invalid time zone name") \ M(TemporalInvalidUnitRange, "Invalid unit range, {} is larger than {}") \ M(TemporalInvalidZonedDateTimeOffset, "Invalid offset for the provided date and time in the current time zone") \ diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp index 6b230cc4a35..4268b2b5df9 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp @@ -1346,16 +1346,21 @@ ThrowCompletionOr parse_temporal_relative_to_string(Globa } // 13.43 ParseTemporalTimeString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaltimestring -ThrowCompletionOr parse_temporal_time_string(GlobalObject& global_object, [[maybe_unused]] String const& iso_string) +ThrowCompletionOr parse_temporal_time_string(GlobalObject& global_object, String const& iso_string) { + auto& vm = global_object.vm(); + // 1. Assert: Type(isoString) is String. // 2. If isoString does not satisfy the syntax of a TemporalTimeString (see 13.33), then - // a. Throw a RangeError exception. - // TODO + auto parse_result = parse_iso8601(Production::TemporalTimeString, iso_string); + if (!parse_result.has_value()) { + // a. Throw a RangeError exception. + return vm.throw_completion(global_object, ErrorType::TemporalInvalidTimeString, iso_string); + } // 3. Let result be ? ParseISODateTime(isoString). - auto result = TRY(parse_iso_date_time(global_object, {})); + auto result = TRY(parse_iso_date_time(global_object, *parse_result)); // 4. Return the Record { [[Hour]]: result.[[Hour]], [[Minute]]: result.[[Minute]], [[Second]]: result.[[Second]], [[Millisecond]]: result.[[Millisecond]], [[Microsecond]]: result.[[Microsecond]], [[Nanosecond]]: result.[[Nanosecond]], [[Calendar]]: result.[[Calendar]] }. return TemporalTime { .hour = result.hour, .minute = result.minute, .second = result.second, .millisecond = result.millisecond, .microsecond = result.microsecond, .nanosecond = result.nanosecond, .calendar = move(result.calendar) }; diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.cpp index f3b21b1942e..1ba58b773d0 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.cpp @@ -422,6 +422,17 @@ bool ISO8601Parser::parse_time_spec() return true; } +// https://tc39.es/proposal-temporal/#prod-Time +bool ISO8601Parser::parse_time() +{ + // Time : + // TimeSpec TimeZone[opt] + if (!parse_time_spec()) + return false; + (void)parse_time_zone(); + return true; +} + // https://tc39.es/proposal-temporal/#prod-TimeSpecSeparator bool ISO8601Parser::parse_time_spec_separator() { @@ -475,11 +486,24 @@ bool ISO8601Parser::parse_temporal_date_time_string() return parse_calendar_date_time(); } +// https://tc39.es/proposal-temporal/#prod-TemporalTimeString +bool ISO8601Parser::parse_temporal_time_string() +{ + // TemporalTimeString : + // Time + // DateTime + // NOTE: Reverse order here because `Time` can be a subset of `DateTime`, + // so we'd not attempt to parse that but may not exhaust the input string. + return parse_date_time() + || parse_time(); } -#define JS_ENUMERATE_ISO8601_PRODUCTION_PARSERS \ - __JS_ENUMERATE(TemporalDateString, parse_temporal_date_string) \ - __JS_ENUMERATE(TemporalDateTimeString, parse_temporal_date_time_string) +} + +#define JS_ENUMERATE_ISO8601_PRODUCTION_PARSERS \ + __JS_ENUMERATE(TemporalDateString, parse_temporal_date_string) \ + __JS_ENUMERATE(TemporalDateTimeString, parse_temporal_date_time_string) \ + __JS_ENUMERATE(TemporalTimeString, parse_temporal_time_string) Optional parse_iso8601(Production production, StringView input) { diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.h b/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.h index fde46751c54..4d1bed1f894 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.h @@ -28,6 +28,7 @@ struct ParseResult { enum class Production { TemporalDateString, TemporalDateTimeString, + TemporalTimeString, }; Optional parse_iso8601(Production, StringView); @@ -73,11 +74,13 @@ public: [[nodiscard]] bool parse_calendar_name(); [[nodiscard]] bool parse_calendar(); [[nodiscard]] bool parse_time_spec(); + [[nodiscard]] bool parse_time(); [[nodiscard]] bool parse_time_spec_separator(); [[nodiscard]] bool parse_date_time(); [[nodiscard]] bool parse_calendar_date_time(); [[nodiscard]] bool parse_temporal_date_string(); [[nodiscard]] bool parse_temporal_date_time_string(); + [[nodiscard]] bool parse_temporal_time_string(); private: struct State { diff --git a/Userland/Libraries/LibJS/Tests/builtins/Temporal/ZonedDateTime/ZonedDateTime.prototype.withPlainTime.js b/Userland/Libraries/LibJS/Tests/builtins/Temporal/ZonedDateTime/ZonedDateTime.prototype.withPlainTime.js index c2e0b65c910..a8a40615fcc 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Temporal/ZonedDateTime/ZonedDateTime.prototype.withPlainTime.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Temporal/ZonedDateTime/ZonedDateTime.prototype.withPlainTime.js @@ -98,8 +98,7 @@ describe("correct behavior", () => { ); }); - // FIXME: This currently yields an incorrect result (epochNanoseconds = 1635984000000000000) - test.skip("from plain time string", () => { + test("from plain time string", () => { const plainDateTime = new Temporal.PlainDateTime(2021, 11, 4, 21, 16, 56, 100, 200, 300); const timeZone = new Temporal.TimeZone("UTC"); const zonedDateTime = plainDateTime.toZonedDateTime(timeZone);