LibJS: Move ambiguous Temporal time string handling to a separate parser

This is an editorial change in the Temporal proposal. See:
fa3d0b9
This commit is contained in:
Timothy Flynn 2025-07-20 12:52:53 -04:00 committed by Andreas Kling
commit 3f75cf270a
Notes: github-actions[bot] 2025-07-23 20:06:36 +00:00
4 changed files with 21 additions and 18 deletions

View file

@ -150,6 +150,15 @@ public:
return parse_annotated_date_time(Zoned::No, TimeRequired::Yes) || parse_annotated_time();
}
// https://tc39.es/proposal-temporal/#prod-AmbiguousTemporalTimeString
[[nodiscard]] bool parse_ambiguous_temporal_time_string()
{
// AmbiguousTemporalTimeString :::
// DateSpecMonthDay TimeZoneAnnotation[opt] Annotations[opt]
// DateSpecYearMonth TimeZoneAnnotation[opt] Annotations[opt]
return parse_annotated_month_day() || parse_annotated_year_month();
}
// https://tc39.es/proposal-temporal/#prod-TemporalYearMonthString
[[nodiscard]] bool parse_temporal_year_month_string()
{
@ -199,16 +208,7 @@ public:
// AnnotatedTime :::
// TimeDesignator Time DateTimeUTCOffset[~Z][opt] TimeZoneAnnotation[opt] Annotations[opt]
// Time DateTimeUTCOffset[~Z][opt] TimeZoneAnnotation[opt] Annotations[opt]
auto has_time_designator = parse_time_designator();
if (!has_time_designator) {
StateTransaction transaction { *this };
// It is a Syntax Error if ParseText(Time DateTimeUTCOffset[~Z], DateSpecMonthDay) is a Parse Node.
// It is a Syntax Error if ParseText(Time DateTimeUTCOffset[~Z], DateSpecYearMonth) is a Parse Node.
if (parse_date_spec_month_day() || parse_date_spec_year_month())
return false;
}
(void)parse_time_designator();
if (!parse_time())
return false;
@ -1303,6 +1303,7 @@ private:
};
#define JS_ENUMERATE_ISO8601_PRODUCTION_PARSERS \
__JS_ENUMERATE(AmbiguousTemporalTimeString, parse_ambiguous_temporal_time_string) \
__JS_ENUMERATE(AnnotationValue, parse_annotation_value) \
__JS_ENUMERATE(DateMonth, parse_date_month) \
__JS_ENUMERATE(TemporalDateTimeString, parse_temporal_date_time_string) \

View file

@ -1,6 +1,6 @@
/*
* Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
* Copyright (c) 2024-2025, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -60,6 +60,7 @@ struct ParseResult {
};
enum class Production {
AmbiguousTemporalTimeString,
AnnotationValue,
DateMonth,
TemporalDateTimeString,

View file

@ -1,7 +1,7 @@
/*
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
* Copyright (c) 2024-2025, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -169,15 +169,16 @@ ThrowCompletionOr<GC::Ref<PlainTime>> to_temporal_time(VM& vm, Value item, Value
// b. Let parseResult be ? ParseISODateTime(item, « TemporalTimeString »).
auto parse_result = TRY(parse_iso_date_time(vm, item.as_string().utf8_string_view(), { { Production::TemporalTimeString } }));
// c. Assert: parseResult.[[Time]] is not START-OF-DAY.
// c. If ParseText(StringToCodePoints(item), AmbiguousTemporalTimeString) is a Parse Node, throw a RangeError exception.
if (parse_iso8601(Production::AmbiguousTemporalTimeString, item.as_string().utf8_string_view()).has_value())
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidPlainTime);
// d. Assert: parseResult.[[Time]] is not START-OF-DAY.
VERIFY(!parse_result.time.has<ParsedISODateTime::StartOfDay>());
// d. Set result to parseResult.[[Time]].
// e. Set result to parseResult.[[Time]].
time = parse_result.time.get<Time>();
// e. NOTE: A successful parse using TemporalTimeString guarantees absence of ambiguity with respect to any
// ISO 8601 date-only, year-month, or month-day representation.
// f. Let resolvedOptions be ? GetOptionsObject(options).
auto resolved_options = TRY(get_options_object(vm, options));

View file

@ -68,7 +68,7 @@ describe("errors", () => {
for (const value of values) {
expect(() => {
Temporal.PlainTime.from(value);
}).toThrowWithMessage(RangeError, "Invalid ISO date time");
}).toThrowWithMessage(RangeError, "Invalid plain time");
expect(() => {
Temporal.PlainTime.from(`T${value}`);