diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index 8f2929293a9..1fd4a9c879b 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h @@ -215,6 +215,7 @@ M(TemporalInvalidDurationPropertyValueNonIntegral, "Invalid value for duration property '{}': must be an integer, got {}") \ M(TemporalInvalidDurationPropertyValueNonZero, "Invalid value for duration property '{}': must be zero, got {}") \ M(TemporalInvalidEpochNanoseconds, "Invalid epoch nanoseconds value, must be in range -86400 * 10^17 to 86400 * 10^17") \ + M(TemporalInvalidInstantString, "Invalid instant string '{}'") \ M(TemporalInvalidISODate, "Invalid ISO date") \ M(TemporalInvalidMonthCode, "Invalid month code") \ M(TemporalInvalidMonthDayString, "Invalid month day string '{}'") \ diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp index 482718ec3b4..1ae9d5fe00e 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp @@ -1161,14 +1161,19 @@ ThrowCompletionOr parse_iso_date_time(GlobalObject& global_object, // 13.35 ParseTemporalInstantString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalinstantstring ThrowCompletionOr parse_temporal_instant_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 TemporalInstantString (see 13.33), then - // a. Throw a RangeError exception. - // TODO + auto parse_result = parse_iso8601(Production::TemporalInstantString, iso_string); + if (!parse_result.has_value()) { + // a. Throw a RangeError exception. + return vm.throw_completion(global_object, ErrorType::TemporalInvalidInstantString, iso_string); + } // 3. Let result be ! ParseISODateTime(isoString). - auto result = MUST(parse_iso_date_time(global_object, {})); + auto result = MUST(parse_iso_date_time(global_object, *parse_result)); // 4. Let timeZoneResult be ? ParseTemporalTimeZoneString(isoString). auto time_zone_result = TRY(parse_temporal_time_zone_string(global_object, iso_string)); diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.cpp index 697174f727c..eec40e1e3e1 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.cpp @@ -746,6 +746,27 @@ bool ISO8601Parser::parse_calendar_date_time() return true; } +// https://tc39.es/proposal-temporal/#prod-TemporalInstantString +bool ISO8601Parser::parse_temporal_instant_string() +{ + // TemporalInstantString : + // Date TimeZoneOffsetRequired + // Date DateTimeSeparator TimeSpec TimeZoneOffsetRequired + StateTransaction transaction { *this }; + if (!parse_date()) + return false; + if (!parse_time_zone_offset_required()) { + if (!parse_date_time_separator()) + return false; + if (!parse_time_spec()) + return false; + if (!parse_time_zone_offset_required()) + return false; + } + transaction.commit(); + return true; +} + // https://tc39.es/proposal-temporal/#prod-TemporalDateString bool ISO8601Parser::parse_temporal_date_string() { @@ -874,6 +895,7 @@ bool ISO8601Parser::parse_temporal_relative_to_string() } #define JS_ENUMERATE_ISO8601_PRODUCTION_PARSERS \ + __JS_ENUMERATE(TemporalInstantString, parse_temporal_instant_string) \ __JS_ENUMERATE(TemporalDateString, parse_temporal_date_string) \ __JS_ENUMERATE(TemporalDateTimeString, parse_temporal_date_time_string) \ __JS_ENUMERATE(TemporalMonthDayString, parse_temporal_month_day_string) \ diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.h b/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.h index bb019f168ca..cf238aa235d 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.h @@ -33,6 +33,7 @@ struct ParseResult { }; enum class Production { + TemporalInstantString, TemporalDateString, TemporalDateTimeString, TemporalMonthDayString, @@ -106,6 +107,7 @@ public: [[nodiscard]] bool parse_time_spec_separator(); [[nodiscard]] bool parse_date_time(); [[nodiscard]] bool parse_calendar_date_time(); + [[nodiscard]] bool parse_temporal_instant_string(); [[nodiscard]] bool parse_temporal_date_string(); [[nodiscard]] bool parse_temporal_date_time_string(); [[nodiscard]] bool parse_temporal_month_day_string(); 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 e182a916203..72c67e68bc6 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.from.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.from.js @@ -14,10 +14,17 @@ describe("correct behavior", () => { expect(Temporal.Instant.from(zonedDateTime).epochNanoseconds).toBe(123n); }); - // Un-skip once ParseISODateTime & ParseTemporalTimeZoneString are implemented - test.skip("Instant string argument", () => { + test("Instant string argument", () => { expect(Temporal.Instant.from("1975-02-02T14:25:36.123456789Z").epochNanoseconds).toBe( 160583136123456789n ); }); }); + +describe("errors", () => { + test("invalid instant string", () => { + expect(() => { + Temporal.Instant.from("foo"); + }).toThrowWithMessage(RangeError, "Invalid instant string 'foo'"); + }); +});