diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.cpp index f55e215686e..cca5fb9370e 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.cpp @@ -75,6 +75,32 @@ bool ISO8601Parser::parse_sign() return true; } +// https://tc39.es/proposal-temporal/#prod-UnpaddedHour +bool ISO8601Parser::parse_unpadded_hour() +{ + // UnpaddedHour : + // DecimalDigit + // 1 DecimalDigit + // 20 + // 21 + // 22 + // 23 + StateTransaction transaction { *this }; + auto success = m_state.lexer.consume_specific("20"sv) + || m_state.lexer.consume_specific("21"sv) + || m_state.lexer.consume_specific("22"sv) + || m_state.lexer.consume_specific("23"sv); + if (!success) { + // This could be either of the first two productions. + if (m_state.lexer.consume_specific('1')) + (void)parse_decimal_digit(); + else if (!parse_decimal_digit()) + return false; + } + transaction.commit(); + return true; +} + // https://tc39.es/proposal-temporal/#prod-Hour bool ISO8601Parser::parse_hour() { @@ -900,10 +926,25 @@ bool ISO8601Parser::parse_time_zone_iana_name_tail() bool ISO8601Parser::parse_time_zone_iana_name() { // TimeZoneIANAName : - // TimeZoneIANANameTail + // Etc/GMT ASCIISign UnpaddedHour + // TimeZoneIANANameTail but not Etc/GMT ASCIISign UnpaddedHour + auto parse_etc_gmt_with_offset = [this] { + StateTransaction transaction { *this }; + if (!m_state.lexer.consume_specific("Etc/GMT"sv)) + return false; + if (!parse_ascii_sign()) + return false; + if (!parse_unpadded_hour()) + return false; + transaction.commit(); + return true; + }; StateTransaction transaction { *this }; - if (!parse_time_zone_iana_name_tail()) + if (parse_etc_gmt_with_offset()) { + // no-op. + } else if (!parse_time_zone_iana_name_tail()) { return false; + } m_state.parse_result.time_zone_iana_name = transaction.parsed_string_view(); transaction.commit(); return true; @@ -914,16 +955,10 @@ bool ISO8601Parser::parse_time_zone_bracketed_name() { // TimeZoneBracketedName : // TimeZoneIANAName - // Etc/GMT ASCIISign Hour // TimeZoneUTCOffsetName StateTransaction transaction { *this }; if (parse_time_zone_iana_name()) { // no-op. - } else if (m_state.lexer.consume_specific("Etc/GMT"sv)) { - if (!parse_ascii_sign()) - return false; - if (!parse_hour()) - return false; } else if (!parse_time_zone_utc_offset_name()) { return false; } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.h b/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.h index 5b631bac3dc..81425f3d890 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.h @@ -88,6 +88,7 @@ public: [[nodiscard]] bool parse_non_zero_digit(); [[nodiscard]] bool parse_ascii_sign(); [[nodiscard]] bool parse_sign(); + [[nodiscard]] bool parse_unpadded_hour(); [[nodiscard]] bool parse_hour(); [[nodiscard]] bool parse_minute_second(); [[nodiscard]] bool parse_decimal_separator(); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Temporal/TimeZone/TimeZone.from.js b/Userland/Libraries/LibJS/Tests/builtins/Temporal/TimeZone/TimeZone.from.js index 8ae6222b95b..9ac8382fa2d 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Temporal/TimeZone/TimeZone.from.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Temporal/TimeZone/TimeZone.from.js @@ -18,9 +18,10 @@ describe("normal behavior", () => { ["GMT", "UTC"], ["Etc/UTC", "UTC"], ["Etc/GMT", "UTC"], - // FIXME: https://github.com/tc39/proposal-temporal/issues/1993 - // ["Etc/GMT+12", "Etc/GMT+12"], - // ["Etc/GMT-12", "Etc/GMT-12"], + ["Etc/GMT+6", "Etc/GMT+6"], + ["Etc/GMT-6", "Etc/GMT-6"], + ["Etc/GMT+12", "Etc/GMT+12"], + ["Etc/GMT-12", "Etc/GMT-12"], ["Europe/London", "Europe/London"], ["Europe/Isle_of_Man", "Europe/London"], ["1970-01-01+01", "+01:00"],