diff --git a/Libraries/LibJS/Runtime/Date.cpp b/Libraries/LibJS/Runtime/Date.cpp index 8a2a45d359b..48e571a5c60 100644 --- a/Libraries/LibJS/Runtime/Date.cpp +++ b/Libraries/LibJS/Runtime/Date.cpp @@ -1,20 +1,19 @@ /* * Copyright (c) 2020-2023, Linus Groh - * Copyright (c) 2022-2024, Tim Flynn + * Copyright (c) 2022-2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ -#include -#include #include -#include #include #include #include #include #include #include +#include +#include #include namespace JS { @@ -457,7 +456,7 @@ String system_time_zone_identifier() // time zone identifier or an offset time zone identifier. auto system_time_zone_string = Unicode::current_time_zone(); - if (!is_time_zone_offset_string(system_time_zone_string)) { + if (!is_offset_time_zone_identifier(system_time_zone_string)) { auto time_zone_identifier = Intl::get_available_named_time_zone_identifier(system_time_zone_string); if (!time_zone_identifier.has_value()) return "UTC"_string; @@ -476,46 +475,55 @@ void clear_system_time_zone_cache() } // 21.4.1.25 LocalTime ( t ), https://tc39.es/ecma262/#sec-localtime +// 14.5.6 LocalTime ( t ), https://tc39.es/proposal-temporal/#sec-localtime double local_time(double time) { // 1. Let systemTimeZoneIdentifier be SystemTimeZoneIdentifier(). auto system_time_zone_identifier = JS::system_time_zone_identifier(); + // 2. Let parseResult be ! ParseTimeZoneIdentifier(systemTimeZoneIdentifier). + auto parse_result = Temporal::parse_time_zone_identifier(system_time_zone_identifier); + double offset_nanoseconds { 0 }; - // 2. If IsTimeZoneOffsetString(systemTimeZoneIdentifier) is true, then - if (is_time_zone_offset_string(system_time_zone_identifier)) { - // a. Let offsetNs be ParseTimeZoneOffsetString(systemTimeZoneIdentifier). - offset_nanoseconds = parse_time_zone_offset_string(system_time_zone_identifier); + // 3. If parseResult.[[OffsetMinutes]] is not EMPTY, then + if (parse_result.offset_minutes.has_value()) { + // a. Let offsetNs be parseResult.[[OffsetMinutes]] × (60 × 10**9). + offset_nanoseconds = static_cast(*parse_result.offset_minutes) * 60'000'000'000; } - // 3. Else, + // 4. Else, else { // a. Let offsetNs be GetNamedTimeZoneOffsetNanoseconds(systemTimeZoneIdentifier, ℤ(ℝ(t) × 10^6)). auto offset = get_named_time_zone_offset_milliseconds(system_time_zone_identifier, time); offset_nanoseconds = static_cast(offset.offset.to_nanoseconds()); } - // 4. Let offsetMs be truncate(offsetNs / 10^6). + // 5. Let offsetMs be truncate(offsetNs / 10^6). auto offset_milliseconds = trunc(offset_nanoseconds / 1e6); - // 5. Return t + 𝔽(offsetMs). + // 6. Return t + 𝔽(offsetMs). return time + offset_milliseconds; } // 21.4.1.26 UTC ( t ), https://tc39.es/ecma262/#sec-utc-t +// 14.5.7 UTC ( t ), https://tc39.es/proposal-temporal/#sec-localtime +// FIXME: Update the rest of this AO for Temporal once we have the required Temporal objects. double utc_time(double time) { // 1. Let systemTimeZoneIdentifier be SystemTimeZoneIdentifier(). auto system_time_zone_identifier = JS::system_time_zone_identifier(); + // 2. Let parseResult be ! ParseTimeZoneIdentifier(systemTimeZoneIdentifier). + auto parse_result = Temporal::parse_time_zone_identifier(system_time_zone_identifier); + double offset_nanoseconds { 0 }; - // 2. If IsTimeZoneOffsetString(systemTimeZoneIdentifier) is true, then - if (is_time_zone_offset_string(system_time_zone_identifier)) { - // a. Let offsetNs be ParseTimeZoneOffsetString(systemTimeZoneIdentifier). - offset_nanoseconds = parse_time_zone_offset_string(system_time_zone_identifier); + // 3. If parseResult.[[OffsetMinutes]] is not EMPTY, then + if (parse_result.offset_minutes.has_value()) { + // a. Let offsetNs be parseResult.[[OffsetMinutes]] × (60 × 10**9). + offset_nanoseconds = static_cast(*parse_result.offset_minutes) * 60'000'000'000; } - // 3. Else, + // 4. Else, else { // a. Let possibleInstants be GetNamedTimeZoneEpochNanoseconds(systemTimeZoneIdentifier, ℝ(YearFromTime(t)), ℝ(MonthFromTime(t)) + 1, ℝ(DateFromTime(t)), ℝ(HourFromTime(t)), ℝ(MinFromTime(t)), ℝ(SecFromTime(t)), ℝ(msFromTime(t)), 0, 0). auto possible_instants = get_named_time_zone_epoch_nanoseconds(system_time_zone_identifier, year_from_time(time), month_from_time(time) + 1, date_from_time(time), hour_from_time(time), min_from_time(time), sec_from_time(time), ms_from_time(time), 0, 0); @@ -544,10 +552,10 @@ double utc_time(double time) offset_nanoseconds = static_cast(offset.offset.to_nanoseconds()); } - // 4. Let offsetMs be truncate(offsetNs / 10^6). + // 5. Let offsetMs be truncate(offsetNs / 10^6). auto offset_milliseconds = trunc(offset_nanoseconds / 1e6); - // 5. Return t - 𝔽(offsetMs). + // 6. Return t - 𝔽(offsetMs). return time - offset_milliseconds; } @@ -636,171 +644,12 @@ double time_clip(double time) return to_integer_or_infinity(time); } -// 21.4.1.33 Time Zone Offset String Format, https://tc39.es/ecma262/#sec-time-zone-offset-strings -Optional parse_utc_offset(StringView offset_string) -{ - GenericLexer lexer { offset_string }; - UTCOffset parse_result; - - // https://tc39.es/ecma262/#prod-ASCIISign - auto parse_ascii_sign = [&]() { - // ASCIISign ::: one of - // + - - if (lexer.next_is(is_any_of("+-"sv))) { - parse_result.sign = lexer.consume(); - return true; - } - - return false; - }; - - auto parse_two_digits = [&](size_t max_value) -> Optional { - if (auto digits = lexer.peek_string(2); digits.has_value()) { - auto number = digits->to_number(TrimWhitespace::No); - - if (number.has_value() && *number <= max_value) { - lexer.ignore(2); - return *number; - } - } - - return {}; - }; - - // https://tc39.es/ecma262/#prod-Hour - auto parse_hour = [&]() { - // Hour ::: - // 0 DecimalDigit - // 1 DecimalDigit - // 20 - // 21 - // 22 - // 23 - parse_result.hour = parse_two_digits(23); - return parse_result.hour.has_value(); - }; - - // https://tc39.es/ecma262/#prod-TimeSeparator - auto parse_time_separator = [&](auto extended) { - // TimeSeparator[Extended] ::: - // [+Extended] : - // [~Extended] [empty] - if (extended) - return lexer.consume_specific(':'); - return true; - }; - - // https://tc39.es/ecma262/#prod-MinuteSecond - auto parse_minute_second = [&](auto& result) { - // MinuteSecond ::: - // 0 DecimalDigit - // 1 DecimalDigit - // 2 DecimalDigit - // 3 DecimalDigit - // 4 DecimalDigit - // 5 DecimalDigit - result = parse_two_digits(59); - return result.has_value(); - }; - - // https://tc39.es/ecma262/#prod-TemporalDecimalSeparator - auto parse_temporal_decimal_separator = [&]() { - // TemporalDecimalSeparator ::: one of - // . , - return lexer.consume_specific('.') || lexer.consume_specific(','); - }; - - // https://tc39.es/ecma262/#prod-TemporalDecimalFraction - auto parse_temporal_decimal_fraction = [&]() { - // TemporalDecimalFraction ::: - // TemporalDecimalSeparator DecimalDigit - // TemporalDecimalSeparator DecimalDigit DecimalDigit - // TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit - // TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit DecimalDigit - // TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit - // TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit - // TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit - // TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit - // TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit - auto position = lexer.tell(); - - if (!parse_temporal_decimal_separator()) - return false; - - for (size_t i = 0; i < 9; ++i) { - if (!lexer.next_is(is_ascii_digit)) - break; - lexer.ignore(); - } - - if (auto fraction = lexer.input().substring_view(position, lexer.tell() - position); fraction.length() > 1) { - parse_result.fraction = fraction; - return true; - } - - return false; - }; - - // https://tc39.es/ecma262/#prod-HourSubcomponents - auto parse_hour_subcomponents = [&](auto extended) { - // HourSubcomponents[Extended] ::: - // TimeSeparator[?Extended] MinuteSecond - // TimeSeparator[?Extended] MinuteSecond TimeSeparator[?Extended] MinuteSecond TemporalDecimalFraction[opt] - ArmedScopeGuard guard { [&, position = lexer.tell()]() { lexer.retreat(lexer.tell() - position); } }; - - if (!parse_time_separator(extended)) - return false; - if (!parse_minute_second(parse_result.minute)) - return false; - - if (lexer.is_eof()) { - guard.disarm(); - return true; - } - - if (!parse_time_separator(extended)) - return false; - if (!parse_minute_second(parse_result.second)) - return false; - - if (lexer.is_eof()) { - guard.disarm(); - return true; - } - - if (!parse_temporal_decimal_fraction()) - return false; - - guard.disarm(); - return true; - }; - - // https://tc39.es/ecma262/#prod-UTCOffset - // UTCOffset ::: - // ASCIISign Hour - // ASCIISign Hour HourSubcomponents[+Extended] - // ASCIISign Hour HourSubcomponents[~Extended] - if (!parse_ascii_sign()) - return {}; - if (!parse_hour()) - return {}; - - if (lexer.is_eof()) - return parse_result; - - if (!parse_hour_subcomponents(true) && !parse_hour_subcomponents(false)) - return {}; - if (lexer.is_eof()) - return parse_result; - - return {}; -} - // 21.4.1.33.1 IsTimeZoneOffsetString ( offsetString ), https://tc39.es/ecma262/#sec-istimezoneoffsetstring -bool is_time_zone_offset_string(StringView offset_string) +// 14.5.10 IsOffsetTimeZoneIdentifier ( offsetString ), https://tc39.es/proposal-temporal/#sec-isoffsettimezoneidentifier +bool is_offset_time_zone_identifier(StringView offset_string) { - // 1. Let parseResult be ParseText(StringToCodePoints(offsetString), UTCOffset). - auto parse_result = parse_utc_offset(offset_string); + // 1. Let parseResult be ParseText(StringToCodePoints(offsetString), UTCOffset[~SubMinutePrecision]). + auto parse_result = Temporal::parse_utc_offset(offset_string, Temporal::SubMinutePrecision::No); // 2. If parseResult is a List of errors, return false. // 3. Return true. @@ -808,66 +657,83 @@ bool is_time_zone_offset_string(StringView offset_string) } // 21.4.1.33.2 ParseTimeZoneOffsetString ( offsetString ), https://tc39.es/ecma262/#sec-parsetimezoneoffsetstring -double parse_time_zone_offset_string(StringView offset_string) +// 14.5.11 ParseDateTimeUTCOffset ( offsetString ), https://tc39.es/proposal-temporal/#sec-parsedatetimeutcoffset +ThrowCompletionOr parse_date_time_utc_offset(VM& vm, StringView offset_string) { - // 1. Let parseResult be ParseText(offsetString, UTCOffset). - auto parse_result = parse_utc_offset(offset_string); + // 1. Let parseResult be ParseText(offsetString, UTCOffset[+SubMinutePrecision]). + auto parse_result = Temporal::parse_utc_offset(offset_string, Temporal::SubMinutePrecision::Yes); - // 2. Assert: parseResult is not a List of errors. + // 2. If parseResult is a List of errors, throw a RangeError exception. + if (!parse_result.has_value()) + return vm.throw_completion(ErrorType::TemporalInvalidTimeZoneString, offset_string); + + return parse_date_time_utc_offset(*parse_result); +} + +// 21.4.1.33.2 ParseTimeZoneOffsetString ( offsetString ), https://tc39.es/ecma262/#sec-parsetimezoneoffsetstring +// 14.5.11 ParseDateTimeUTCOffset ( offsetString ), https://tc39.es/proposal-temporal/#sec-parsedatetimeutcoffset +double parse_date_time_utc_offset(StringView offset_string) +{ + // OPTIMIZATION: Some callers can assume that parsing will succeed. + + // 1. Let parseResult be ParseText(offsetString, UTCOffset[+SubMinutePrecision]). + auto parse_result = Temporal::parse_utc_offset(offset_string, Temporal::SubMinutePrecision::Yes); VERIFY(parse_result.has_value()); + return parse_date_time_utc_offset(*parse_result); +} + +// 21.4.1.33.2 ParseTimeZoneOffsetString ( offsetString ), https://tc39.es/ecma262/#sec-parsetimezoneoffsetstring +// 14.5.11 ParseDateTimeUTCOffset ( offsetString ), https://tc39.es/proposal-temporal/#sec-parsedatetimeutcoffset +double parse_date_time_utc_offset(Temporal::TimeZoneOffset const& parse_result) +{ + // OPTIMIZATION: Some callers will have already parsed and validated the time zone identifier. + // 3. Assert: parseResult contains a ASCIISign Parse Node. - VERIFY(parse_result->sign.has_value()); + VERIFY(parse_result.sign.has_value()); // 4. Let parsedSign be the source text matched by the ASCIISign Parse Node contained within parseResult. - auto parsed_sign = *parse_result->sign; - i8 sign { 0 }; - // 5. If parsedSign is the single code point U+002D (HYPHEN-MINUS), then - if (parsed_sign == '-') { - // a. Let sign be -1. - sign = -1; - } + // a. Let sign be -1. // 6. Else, - else { - // a. Let sign be 1. - sign = 1; - } + // a. Let sign be 1. + auto sign = parse_result.sign == '-' ? -1 : 1; - // 7. NOTE: Applications of StringToNumber below do not lose precision, since each of the parsed values is guaranteed to be a sufficiently short string of decimal digits. + // 7. NOTE: Applications of StringToNumber below do not lose precision, since each of the parsed values is guaranteed + // to be a sufficiently short string of decimal digits. // 8. Assert: parseResult contains an Hour Parse Node. - VERIFY(parse_result->hour.has_value()); + VERIFY(parse_result.hours.has_value()); // 9. Let parsedHours be the source text matched by the Hour Parse Node contained within parseResult. // 10. Let hours be ℝ(StringToNumber(CodePointsToString(parsedHours))). - auto hours = *parse_result->hour; + auto hours = parse_result.hours->to_number().value(); // 11. If parseResult does not contain a MinuteSecond Parse Node, then // a. Let minutes be 0. // 12. Else, // a. Let parsedMinutes be the source text matched by the first MinuteSecond Parse Node contained within parseResult. // b. Let minutes be ℝ(StringToNumber(CodePointsToString(parsedMinutes))). - double minutes = parse_result->minute.value_or(0); + double minutes = parse_result.minutes.has_value() ? parse_result.minutes->to_number().value() : 0; // 13. If parseResult does not contain two MinuteSecond Parse Nodes, then // a. Let seconds be 0. // 14. Else, // a. Let parsedSeconds be the source text matched by the second secondSecond Parse Node contained within parseResult. // b. Let seconds be ℝ(StringToNumber(CodePointsToString(parsedSeconds))). - double seconds = parse_result->second.value_or(0); + double seconds = parse_result.seconds.has_value() ? parse_result.seconds->to_number().value() : 0; double nanoseconds = 0; // 15. If parseResult does not contain a TemporalDecimalFraction Parse Node, then - if (!parse_result->fraction.has_value()) { + if (!parse_result.fraction.has_value()) { // a. Let nanoseconds be 0. nanoseconds = 0; } // 16. Else, else { // a. Let parsedFraction be the source text matched by the TemporalDecimalFraction Parse Node contained within parseResult. - auto parsed_fraction = *parse_result->fraction; + auto parsed_fraction = *parse_result.fraction; // b. Let fraction be the string-concatenation of CodePointsToString(parsedFraction) and "000000000". auto fraction = ByteString::formatted("{}000000000", parsed_fraction); diff --git a/Libraries/LibJS/Runtime/Date.h b/Libraries/LibJS/Runtime/Date.h index 91036948e83..dd522773e3c 100644 --- a/Libraries/LibJS/Runtime/Date.h +++ b/Libraries/LibJS/Runtime/Date.h @@ -1,6 +1,6 @@ /* * Copyright (c) 2020-2022, Linus Groh - * Copyright (c) 2022-2024, Tim Flynn + * Copyright (c) 2022-2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ @@ -58,14 +58,6 @@ constexpr inline double ms_per_day = 86'400'000; constexpr inline double ns_per_day = 86'400'000'000'000; extern Crypto::SignedBigInteger const ns_per_day_bigint; -struct UTCOffset { - Optional sign; - Optional hour; - Optional minute; - Optional second; - Optional fraction; -}; - double day(double); double time_within_day(double); u16 days_in_year(i32); @@ -93,8 +85,9 @@ double make_time(double hour, double min, double sec, double ms); double make_day(double year, double month, double date); double make_date(double day, double time); double time_clip(double time); -Optional parse_utc_offset(StringView); -bool is_time_zone_offset_string(StringView offset_string); -double parse_time_zone_offset_string(StringView offset_string); +bool is_offset_time_zone_identifier(StringView offset_string); +ThrowCompletionOr parse_date_time_utc_offset(VM&, StringView offset_string); +double parse_date_time_utc_offset(StringView offset_string); +double parse_date_time_utc_offset(Temporal::TimeZoneOffset const&); } diff --git a/Libraries/LibJS/Runtime/DatePrototype.cpp b/Libraries/LibJS/Runtime/DatePrototype.cpp index 9f12a403535..14740241eb8 100644 --- a/Libraries/LibJS/Runtime/DatePrototype.cpp +++ b/Libraries/LibJS/Runtime/DatePrototype.cpp @@ -2,7 +2,7 @@ * Copyright (c) 2020-2023, Linus Groh * Copyright (c) 2021, Petróczi Zoltán * Copyright (c) 2021, Idan Horowitz - * Copyright (c) 2022, Tim Flynn + * Copyright (c) 2022-2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ @@ -21,6 +21,8 @@ #include #include #include +#include +#include #include #include #include @@ -1066,19 +1068,14 @@ JS_DEFINE_NATIVE_FUNCTION(DatePrototype::to_string) } // 21.4.4.41.1 TimeString ( tv ), https://tc39.es/ecma262/#sec-timestring +// 14.5.8 TimeString ( tv ), https://tc39.es/proposal-temporal/#sec-timestring ByteString time_string(double time) { - // 1. Let hour be ToZeroPaddedDecimalString(ℝ(HourFromTime(tv)), 2). - auto hour = hour_from_time(time); + // 1. Let timeString be FormatTimeString(ℝ(HourFromTime(tv)), ℝ(MinFromTime(tv)), ℝ(SecFromTime(tv)), 0, 0). + auto time_string = Temporal::format_time_string(hour_from_time(time), min_from_time(time), sec_from_time(time), 0, 0); - // 2. Let minute be ToZeroPaddedDecimalString(ℝ(MinFromTime(tv)), 2). - auto minute = min_from_time(time); - - // 3. Let second be ToZeroPaddedDecimalString(ℝ(SecFromTime(tv)), 2). - auto second = sec_from_time(time); - - // 4. Return the string-concatenation of hour, ":", minute, ":", second, the code unit 0x0020 (SPACE), and "GMT". - return ByteString::formatted("{:02}:{:02}:{:02} GMT", hour, minute, second); + // 4. Return the string-concatenation of timeString, the code unit 0x0020 (SPACE), and "GMT". + return ByteString::formatted("{} GMT", time_string); } // 21.4.4.41.2 DateString ( tv ), https://tc39.es/ecma262/#sec-datestring @@ -1105,62 +1102,40 @@ ByteString date_string(double time) } // 21.4.4.41.3 TimeZoneString ( tv ), https://tc39.es/ecma262/#sec-timezoneestring +// 14.5.9 TimeZoneString ( tv ), https://tc39.es/proposal-temporal/#sec-timezoneestring ByteString time_zone_string(double time) { // 1. Let systemTimeZoneIdentifier be SystemTimeZoneIdentifier(). auto system_time_zone_identifier = JS::system_time_zone_identifier(); - double offset_nanoseconds { 0 }; + // 2. Let offsetMinutes be ! ParseTimeZoneIdentifier(systemTimeZoneIdentifier).[[OffsetMinutes]]. + auto offset_minutes = Temporal::parse_time_zone_identifier(system_time_zone_identifier).offset_minutes; auto in_dst = Unicode::TimeZoneOffset::InDST::No; - // 2. If IsTimeZoneOffsetString(systemTimeZoneIdentifier) is true, then - if (is_time_zone_offset_string(system_time_zone_identifier)) { - // a. Let offsetNs be ParseTimeZoneOffsetString(systemTimeZoneIdentifier). - offset_nanoseconds = parse_time_zone_offset_string(system_time_zone_identifier); - } - // 3. Else, - else { + // 2. If offsetMinutes is EMPTY, then + if (!offset_minutes.has_value()) { // a. Let offsetNs be GetNamedTimeZoneOffsetNanoseconds(systemTimeZoneIdentifier, ℤ(ℝ(tv) × 10^6)). auto offset = get_named_time_zone_offset_milliseconds(system_time_zone_identifier, time); - - offset_nanoseconds = static_cast(offset.offset.to_nanoseconds()); in_dst = offset.in_dst; + + // b. Set offsetMinutes to truncate(offsetNs / (60 × 10**9)). + offset_minutes = trunc(static_cast(offset.offset.to_nanoseconds()) / 60'000'000'000.0); } - // 4. Let offset be 𝔽(truncate(offsetNs / 106)). - auto offset = trunc(offset_nanoseconds / 1e6); + // 3. Let offsetString be FormatOffsetTimeZoneIdentifier(offsetMinutes, UNSEPARATED). + auto offset_string = Temporal::format_offset_time_zone_identifier(*offset_minutes, Temporal::TimeStyle::Unseparated); - StringView offset_sign; - - // 5. If offset is +0𝔽 or offset > +0𝔽, then - if (offset >= 0) { - // a. Let offsetSign be "+". - offset_sign = "+"sv; - // b. Let absOffset be offset. - } - // 6. Else, - else { - // a. Let offsetSign be "-". - offset_sign = "-"sv; - // b. Let absOffset be -offset. - offset *= -1; - } - - // 7. Let offsetMin be ToZeroPaddedDecimalString(ℝ(MinFromTime(absOffset)), 2). - auto offset_min = min_from_time(offset); - - // 8. Let offsetHour be ToZeroPaddedDecimalString(ℝ(HourFromTime(absOffset)), 2). - auto offset_hour = hour_from_time(offset); - - // 9. Let tzName be an implementation-defined string that is either the empty String or the string-concatenation of the code unit 0x0020 (SPACE), the code unit 0x0028 (LEFT PARENTHESIS), an implementation-defined timezone name, and the code unit 0x0029 (RIGHT PARENTHESIS). + // 5. Let tzName be an implementation-defined string that is either the empty String or the string-concatenation of + // the code unit 0x0020 (SPACE), the code unit 0x0028 (LEFT PARENTHESIS), an implementation-defined timezone name, + // and the code unit 0x0029 (RIGHT PARENTHESIS). auto tz_name = Unicode::current_time_zone(); // Most implementations seem to prefer the long-form display name of the time zone. Not super important, but we may as well match that behavior. if (auto name = Unicode::time_zone_display_name(Unicode::default_locale(), tz_name, in_dst, time); name.has_value()) tz_name = name.release_value(); - // 10. Return the string-concatenation of offsetSign, offsetHour, offsetMin, and tzName. - return ByteString::formatted("{}{:02}{:02} ({})", offset_sign, offset_hour, offset_min, tz_name); + // 10. Return the string-concatenation of offsetString and tzName. + return ByteString::formatted("{} ({})", offset_string, tz_name); } // 21.4.4.41.4 ToDateString ( tv ), https://tc39.es/ecma262/#sec-todatestring diff --git a/Libraries/LibJS/Runtime/Intl/DateTimeFormatConstructor.cpp b/Libraries/LibJS/Runtime/Intl/DateTimeFormatConstructor.cpp index 64a9187ae6f..a6e946d394f 100644 --- a/Libraries/LibJS/Runtime/Intl/DateTimeFormatConstructor.cpp +++ b/Libraries/LibJS/Runtime/Intl/DateTimeFormatConstructor.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2024, Tim Flynn + * Copyright (c) 2021-2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -81,6 +82,8 @@ JS_DEFINE_NATIVE_FUNCTION(DateTimeFormatConstructor::supported_locales_of) } // 11.1.2 CreateDateTimeFormat ( newTarget, locales, options, required, defaults ), https://tc39.es/ecma402/#sec-createdatetimeformat +// 15.7.1 CreateDateTimeFormat ( newTarget, locales, options, required, defaults [ , toLocaleStringTimeZone ] ), https://tc39.es/proposal-temporal/#sec-createdatetimeformat +// FIXME: Update the rest of this AO for Temporal once we have the required Temporal objects. ThrowCompletionOr> create_date_time_format(VM& vm, FunctionObject& new_target, Value locales_value, Value options_value, OptionRequired required, OptionDefaults defaults) { // 1. Let dateTimeFormat be ? OrdinaryCreateFromConstructor(newTarget, "%Intl.DateTimeFormat.prototype%", « [[InitializedDateTimeFormat]], [[Locale]], [[Calendar]], [[NumberingSystem]], [[TimeZone]], [[HourCycle]], [[DateStyle]], [[TimeStyle]], [[DateTimeFormat]], [[BoundFormat]] »). @@ -200,29 +203,25 @@ ThrowCompletionOr> create_date_time_format(VM& vm, Funct } // 29. If IsTimeZoneOffsetString(timeZone) is true, then - bool is_time_zone_offset_string = JS::is_time_zone_offset_string(time_zone); + bool is_time_zone_offset_string = JS::is_offset_time_zone_identifier(time_zone); if (is_time_zone_offset_string) { - // a. Let parseResult be ParseText(StringToCodePoints(timeZone), UTCOffset). - auto parse_result = parse_utc_offset(time_zone); + // a. Let parseResult be ParseText(StringToCodePoints(timeZone), UTCOffset[~SubMinutePrecision]). + auto parse_result = Temporal::parse_utc_offset(time_zone, Temporal::SubMinutePrecision::No); // b. Assert: parseResult is a Parse Node. VERIFY(parse_result.has_value()); - // c. If parseResult contains more than one MinuteSecond Parse Node, throw a RangeError exception. - if (parse_result->second.has_value()) - return vm.throw_completion(ErrorType::OptionIsNotValidValue, time_zone, vm.names.timeZone); + // c. Let offsetNanoseconds be ? ParseDateTimeUTCOffset(timeZone). + auto offset_nanoseconds = TRY(parse_date_time_utc_offset(vm, time_zone)); - // d. Let offsetNanoseconds be ParseTimeZoneOffsetString(timeZone). - auto offset_nanoseconds = parse_time_zone_offset_string(time_zone); - - // e. Let offsetMinutes be offsetNanoseconds / (6 × 10**10). + // d. Let offsetMinutes be offsetNanoseconds / (6 × 10**10). auto offset_minutes = offset_nanoseconds / 60'000'000'000; - // f. Assert: offsetMinutes is an integer. + // e. Assert: offsetMinutes is an integer. VERIFY(trunc(offset_minutes) == offset_minutes); - // g. Set timeZone to FormatOffsetTimeZoneIdentifier(offsetMinutes). + // f. Set timeZone to FormatOffsetTimeZoneIdentifier(offsetMinutes). time_zone = format_offset_time_zone_identifier(offset_minutes); } // 30. Else, diff --git a/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp b/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp index e838f97d469..f1ac2c6e7a6 100644 --- a/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp +++ b/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp @@ -1366,10 +1366,7 @@ ThrowCompletionOr to_offset_string(VM& vm, Value argument) return vm.throw_completion(ErrorType::TemporalInvalidTimeZoneString, offset); // 3. Perform ? ParseDateTimeUTCOffset(offset). - // FIXME: ParseTimeZoneOffsetString should be renamed to ParseDateTimeUTCOffset and updated for Temporal. For now, we - // can just check with the ISO8601 parser directly. - if (!parse_utc_offset(argument.as_string().utf8_string_view(), SubMinutePrecision::Yes).has_value()) - return vm.throw_completion(ErrorType::TemporalInvalidTimeZoneString, offset); + TRY(parse_date_time_utc_offset(vm, offset.as_string().utf8_string_view())); // 4. Return offset. return offset.as_string().utf8_string(); diff --git a/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp b/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp index 33cc50020ac..31bc53655af 100644 --- a/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp +++ b/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp @@ -115,8 +115,7 @@ TimeZone parse_time_zone_identifier(ParseResult const& parse_result) // b. Let offsetString be the source text matched by the UTCOffset[~SubMinutePrecision] Parse Node contained within parseResult. // c. Let offsetNanoseconds be ! ParseDateTimeUTCOffset(offsetString). - // FIXME: ParseTimeZoneOffsetString should be renamed to ParseDateTimeUTCOffset and updated for Temporal. - auto offset_nanoseconds = parse_time_zone_offset_string(parse_result.time_zone_offset->source_text); + auto offset_nanoseconds = parse_date_time_utc_offset(parse_result.time_zone_offset->source_text); // d. Let offsetMinutes be offsetNanoseconds / (60 × 10**9). auto offset_minutes = offset_nanoseconds / 60'000'000'000;