LibJS: Require 'T' prefix for ambiguous time-only strings

This is a normative change in the Temporal spec.

See: https://github.com/tc39/proposal-temporal/commit/514ede3
This commit is contained in:
Linus Groh 2021-12-22 22:44:46 +01:00
parent a73c71e877
commit 3ab1c52e2b
Notes: sideshowbarker 2024-07-17 22:19:10 +09:00
4 changed files with 443 additions and 29 deletions

View file

@ -1079,60 +1079,83 @@ ThrowCompletionOr<ISODateTime> parse_iso_date_time(GlobalObject& global_object,
// 1. Assert: Type(isoString) is String.
// 2. Let year, month, day, hour, minute, second, fraction, and calendar be the parts of isoString produced respectively by the DateYear, DateMonth, DateDay, TimeHour, TimeMinute, TimeSecond, TimeFraction, and CalendarName productions, or undefined if not present.
// 2. Let year, month, day, fraction, and calendar be the parts of isoString produced respectively by the DateYear, DateMonth, DateDay, TimeFraction, and CalendarName productions, or undefined if not present.
auto year_part = parse_result.date_year;
auto month_part = parse_result.date_month;
auto day_part = parse_result.date_day;
auto hour_part = parse_result.time_hour;
auto minute_part = parse_result.time_minute;
auto second_part = parse_result.time_second;
auto fraction_part = parse_result.time_fraction;
auto calendar_part = parse_result.calendar_name;
// 3. If the first code unit of year is 0x2212 (MINUS SIGN), replace it with the code unit 0x002D (HYPHEN-MINUS).
// 3. Let year be the part of isoString produced by the DateYear production.
// NOTE: Duplicate assignment, already covered above (see https://github.com/tc39/proposal-temporal/pull/1987)
// 4. Let hour be the part of isoString produced by the TimeHour, TimeHourNotValidMonth, TimeHourNotThirtyOneDayMonth, or TimeHourTwoOnly productions, or undefined if none of those are present.
auto hour_part = parse_result.time_hour;
if (!hour_part.has_value())
hour_part = parse_result.time_hour_not_valid_month;
if (!hour_part.has_value())
hour_part = parse_result.time_hour_not_thirty_one_day_month;
if (!hour_part.has_value())
hour_part = parse_result.time_hour_two_only;
// 5. Let minute be the part of isoString produced by the TimeMinute, TimeMinuteNotValidDay, TimeMinuteThirtyOnly, or TimeMinuteThirtyOneOnly productions, or undefined if none of those are present.
auto minute_part = parse_result.time_minute;
if (!minute_part.has_value())
minute_part = parse_result.time_minute_not_valid_day;
if (!minute_part.has_value())
minute_part = parse_result.time_minute_thirty_only;
if (!minute_part.has_value())
minute_part = parse_result.time_minute_thirty_one_only;
// 6. Let second be the part of isoString produced by the TimeSecond or TimeSecondNotValidMonth productions, or undefined if neither of those are present.
auto second_part = parse_result.time_second;
if (!second_part.has_value())
second_part = parse_result.time_second_not_valid_month;
// 7. If the first code unit of year is 0x2212 (MINUS SIGN), replace it with the code unit 0x002D (HYPHEN-MINUS).
String normalized_year;
if (year_part.has_value() && year_part->starts_with("\xE2\x88\x92"sv))
normalized_year = String::formatted("-{}", year_part->substring_view(3));
else
normalized_year = year_part.value_or("0");
// 4. Set year to ! ToIntegerOrInfinity(year).
// 8. Set year to ! ToIntegerOrInfinity(year).
auto year = *normalized_year.to_int<i32>();
u8 month;
// 5. If month is undefined, then
// 9. If month is undefined, then
if (!month_part.has_value()) {
// a. Set month to 1.
month = 1;
}
// 6. Else,
// 10. Else,
else {
// a. Set month to ! ToIntegerOrInfinity(month).
month = *month_part->to_uint<u8>();
}
u8 day;
// 7. If day is undefined, then
// 11. If day is undefined, then
if (!day_part.has_value()) {
// a. Set day to 1.
day = 1;
}
// 8. Else,
// 12. Else,
else {
// a. Set day to ! ToIntegerOrInfinity(day).
day = *day_part->to_uint<u8>();
}
// 9. Set hour to ! ToIntegerOrInfinity(hour).
// 13. Set hour to ! ToIntegerOrInfinity(hour).
u8 hour = *hour_part.value_or("0"sv).to_uint<u8>();
// 10. Set minute to ! ToIntegerOrInfinity(minute).
// 14. Set minute to ! ToIntegerOrInfinity(minute).
u8 minute = *minute_part.value_or("0"sv).to_uint<u8>();
// 11. Set second to ! ToIntegerOrInfinity(second).
// 15. Set second to ! ToIntegerOrInfinity(second).
u8 second = *second_part.value_or("0"sv).to_uint<u8>();
// 12. If second is 60, then
// 16. If second is 60, then
if (second == 60) {
// a. Set second to 59.
second = 59;
@ -1141,7 +1164,7 @@ ThrowCompletionOr<ISODateTime> parse_iso_date_time(GlobalObject& global_object,
u16 millisecond;
u16 microsecond;
u16 nanosecond;
// 13. If fraction is not undefined, then
// 17. If fraction is not undefined, then
if (fraction_part.has_value()) {
// a. Set fraction to the string-concatenation of the previous value of fraction and the string "000000000".
auto fraction = String::formatted("{}000000000", *fraction_part);
@ -1155,7 +1178,7 @@ ThrowCompletionOr<ISODateTime> parse_iso_date_time(GlobalObject& global_object,
// g. Set nanosecond to ! ToIntegerOrInfinity(nanosecond).
nanosecond = *fraction.substring(7, 3).to_uint<u16>();
}
// 14. Else,
// 18. Else,
else {
// a. Let millisecond be 0.
millisecond = 0;
@ -1165,15 +1188,15 @@ ThrowCompletionOr<ISODateTime> parse_iso_date_time(GlobalObject& global_object,
nanosecond = 0;
}
// 15. If ! IsValidISODate(year, month, day) is false, throw a RangeError exception.
// 19. If ! IsValidISODate(year, month, day) is false, throw a RangeError exception.
if (!is_valid_iso_date(year, month, day))
return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidISODate);
// 16. If ! IsValidTime(hour, minute, second, millisecond, microsecond, nanosecond) is false, throw a RangeError exception.
// 20. If ! IsValidTime(hour, minute, second, millisecond, microsecond, nanosecond) is false, throw a RangeError exception.
if (!is_valid_time(hour, minute, second, millisecond, microsecond, nanosecond))
return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidTime);
// 17. Return the Record { [[Year]]: year, [[Month]]: month, [[Day]]: day, [[Hour]]: hour, [[Minute]]: minute, [[Second]]: second, [[Millisecond]]: millisecond, [[Microsecond]]: microsecond, [[Nanosecond]]: nanosecond, [[Calendar]]: calendar }.
// 21. Return the Record { [[Year]]: year, [[Month]]: month, [[Day]]: day, [[Hour]]: hour, [[Minute]]: minute, [[Second]]: second, [[Millisecond]]: millisecond, [[Microsecond]]: microsecond, [[Nanosecond]]: nanosecond, [[Calendar]]: calendar }.
return ISODateTime { .year = year, .month = month, .day = day, .hour = hour, .minute = minute, .second = second, .millisecond = millisecond, .microsecond = microsecond, .nanosecond = nanosecond, .calendar = calendar_part.has_value() ? *calendar_part : Optional<String>() };
}
@ -1567,7 +1590,11 @@ ThrowCompletionOr<TemporalTime> parse_temporal_time_string(GlobalObject& global_
// 4. Let result be ? ParseISODateTime(isoString).
auto result = TRY(parse_iso_date_time(global_object, *parse_result));
// 5. Return the Record { [[Hour]]: result.[[Hour]], [[Minute]]: result.[[Minute]], [[Second]]: result.[[Second]], [[Millisecond]]: result.[[Millisecond]], [[Microsecond]]: result.[[Microsecond]], [[Nanosecond]]: result.[[Nanosecond]], [[Calendar]]: result.[[Calendar]] }.
// 5. Assert: ParseText(! StringToCodePoints(isoString), CalendarDate) is a List of errors.
// 6. Assert: ParseText(! StringToCodePoints(isoString), DateSpecYearMonth) is a List of errors.
// 7. Assert: ParseText(! StringToCodePoints(isoString), DateSpecMonthDay) is a List of errors.
// 8. 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) };
}

View file

@ -416,6 +416,169 @@ bool ISO8601Parser::parse_time_second()
return true;
}
// https://tc39.es/proposal-temporal/#prod-TimeHourNotValidMonth
bool ISO8601Parser::parse_time_hour_not_valid_month()
{
// TimeHourNotValidMonth : one of
// 00 13 14 15 16 17 18 19 20 21 23
StateTransaction transaction { *this };
auto success = m_state.lexer.consume_specific("00"sv)
|| m_state.lexer.consume_specific("13"sv)
|| m_state.lexer.consume_specific("14"sv)
|| m_state.lexer.consume_specific("15"sv)
|| m_state.lexer.consume_specific("16"sv)
|| m_state.lexer.consume_specific("17"sv)
|| m_state.lexer.consume_specific("18"sv)
|| m_state.lexer.consume_specific("19"sv)
|| 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)
return false;
m_state.parse_result.time_hour_not_valid_month = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-TimeHourNotThirtyOneDayMonth
bool ISO8601Parser::parse_time_hour_not_thirty_one_day_month()
{
// TimeHourNotThirtyOneDayMonth : one of
// 02 04 06 09 11
StateTransaction transaction { *this };
auto success = m_state.lexer.consume_specific("02"sv)
|| m_state.lexer.consume_specific("04"sv)
|| m_state.lexer.consume_specific("06"sv)
|| m_state.lexer.consume_specific("09"sv)
|| m_state.lexer.consume_specific("11"sv);
if (!success)
return false;
m_state.parse_result.time_hour_not_thirty_one_day_month = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-TimeHourTwoOnly
bool ISO8601Parser::parse_time_hour_two_only()
{
// TimeHourTwoOnly :
// 02
StateTransaction transaction { *this };
if (!m_state.lexer.consume_specific("02"sv))
return false;
m_state.parse_result.time_hour_two_only = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-TimeMinuteNotValidDay
bool ISO8601Parser::parse_time_minute_not_valid_day()
{
// TimeMinuteNotValidDay :
// 00
// 32
// 33
// 34
// 35
// 36
// 37
// 38
// 39
// 4 DecimalDigit
// 5 DecimalDigit
// 60
StateTransaction transaction { *this };
if (m_state.lexer.consume_specific('4') || m_state.lexer.consume_specific('5')) {
if (!parse_decimal_digit())
return false;
} else {
auto success = m_state.lexer.consume_specific("00"sv)
|| m_state.lexer.consume_specific("32"sv)
|| m_state.lexer.consume_specific("33"sv)
|| m_state.lexer.consume_specific("34"sv)
|| m_state.lexer.consume_specific("35"sv)
|| m_state.lexer.consume_specific("36"sv)
|| m_state.lexer.consume_specific("37"sv)
|| m_state.lexer.consume_specific("38"sv)
|| m_state.lexer.consume_specific("39"sv)
|| m_state.lexer.consume_specific("60"sv);
if (!success)
return false;
}
m_state.parse_result.time_minute_not_valid_day = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-TimeMinuteThirtyOnly
bool ISO8601Parser::parse_time_minute_thirty_only()
{
// TimeMinuteThirtyOnly :
// 30
StateTransaction transaction { *this };
if (!m_state.lexer.consume_specific("30"sv))
return false;
m_state.parse_result.time_minute_thirty_only = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-TimeMinuteThirtyOneOnly
bool ISO8601Parser::parse_time_minute_thirty_one_only()
{
// TimeMinuteThirtyOneOnly :
// 31
StateTransaction transaction { *this };
if (!m_state.lexer.consume_specific("31"sv))
return false;
m_state.parse_result.time_minute_thirty_one_only = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-TimeSecondNotValidMonth
bool ISO8601Parser::parse_time_second_not_valid_month()
{
// TimeSecondNotValidMonth :
// 00
// 13
// 14
// 15
// 16
// 17
// 18
// 19
// 2 DecimalDigit
// 3 DecimalDigit
// 4 DecimalDigit
// 5 DecimalDigit
// 60
StateTransaction transaction { *this };
if (m_state.lexer.consume_specific('2')
|| m_state.lexer.consume_specific('3')
|| m_state.lexer.consume_specific('4')
|| m_state.lexer.consume_specific('5')) {
if (!parse_decimal_digit())
return false;
} else {
auto success = m_state.lexer.consume_specific("00"sv)
|| m_state.lexer.consume_specific("13"sv)
|| m_state.lexer.consume_specific("14"sv)
|| m_state.lexer.consume_specific("15"sv)
|| m_state.lexer.consume_specific("16"sv)
|| m_state.lexer.consume_specific("17"sv)
|| m_state.lexer.consume_specific("18"sv)
|| m_state.lexer.consume_specific("19"sv)
|| m_state.lexer.consume_specific("60"sv);
if (!success)
return false;
}
m_state.parse_result.time_second_not_valid_month = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-FractionalPart
bool ISO8601Parser::parse_fractional_part()
{
@ -576,6 +739,61 @@ bool ISO8601Parser::parse_time_zone_utc_offset()
|| parse_utc_designator();
}
// https://tc39.es/proposal-temporal/#prod-TimeZoneNumericUTCOffsetNotAmbiguous
bool ISO8601Parser::parse_time_zone_numeric_utc_offset_not_ambiguous()
{
// TimeZoneNumericUTCOffsetNotAmbiguous :
// + TimeZoneUTCOffsetHour
// U+2212 TimeZoneUTCOffsetHour
// TimeZoneUTCOffsetSign TimeZoneUTCOffsetHour : TimeZoneUTCOffsetMinute
// TimeZoneUTCOffsetSign TimeZoneUTCOffsetHour TimeZoneUTCOffsetMinute
// TimeZoneUTCOffsetSign TimeZoneUTCOffsetHour : TimeZoneUTCOffsetMinute : TimeZoneUTCOffsetSecond TimeZoneUTCOffsetFraction[opt]
// TimeZoneUTCOffsetSign TimeZoneUTCOffsetHour TimeZoneUTCOffsetMinute TimeZoneUTCOffsetSecond TimeZoneUTCOffsetFraction[opt]
StateTransaction transaction { *this };
if (m_state.lexer.consume_specific('+') || m_state.lexer.consume_specific("\xE2\x88\x92"sv)) {
if (!parse_time_zone_utc_offset_hour())
return false;
} else {
if (!parse_time_zone_utc_offset_sign())
return false;
if (!parse_time_zone_utc_offset_hour())
return false;
if (m_state.lexer.consume_specific(':')) {
if (!parse_time_zone_utc_offset_minute())
return false;
if (m_state.lexer.consume_specific(':')) {
if (!parse_time_zone_utc_offset_second())
return false;
(void)parse_time_zone_utc_offset_fraction();
}
} else {
if (!parse_time_zone_utc_offset_minute())
return false;
if (parse_time_zone_utc_offset_second())
(void)parse_time_zone_utc_offset_fraction();
}
}
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-TimeZoneNumericUTCOffsetNotAmbiguousAllowedNegativeHour
bool ISO8601Parser::parse_time_zone_numeric_utc_offset_not_ambiguous_allowed_negative_hour()
{
// TimeZoneNumericUTCOffsetNotAmbiguousAllowedNegativeHour :
// TimeZoneNumericUTCOffsetNotAmbiguous
// - TimeHourNotValidMonth
StateTransaction transaction { *this };
if (!parse_time_zone_numeric_utc_offset_not_ambiguous()) {
if (!m_state.lexer.consume_specific('-'))
return false;
if (!parse_time_hour_not_valid_month())
return false;
}
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-TimeZoneUTCOffsetName
bool ISO8601Parser::parse_time_zone_utc_offset_name()
{
@ -786,6 +1004,120 @@ bool ISO8601Parser::parse_time_spec()
return true;
}
// https://tc39.es/proposal-temporal/#prod-TimeHourMinuteBasicFormatNotAmbiguous
bool ISO8601Parser::parse_time_hour_minute_basic_format_not_ambiguous()
{
// TimeHourMinuteBasicFormatNotAmbiguous :
// TimeHourNotValidMonth TimeMinute
// TimeHour TimeMinuteNotValidDay
// TimeHourNotThirtyOneDayMonth TimeMinuteThirtyOneOnly
// TimeHourTwoOnly TimeMinuteThirtyOnly
{
StateTransaction transaction { *this };
if (parse_time_hour_not_valid_month() && parse_time_minute()) {
transaction.commit();
return true;
}
}
{
StateTransaction transaction { *this };
if (parse_time_hour() && parse_time_minute_not_valid_day()) {
transaction.commit();
return true;
}
}
{
StateTransaction transaction { *this };
if (parse_time_hour_not_thirty_one_day_month() && parse_time_minute_thirty_one_only()) {
transaction.commit();
return true;
}
}
{
StateTransaction transaction { *this };
if (parse_time_hour_two_only() && parse_time_minute_thirty_only()) {
transaction.commit();
return true;
}
}
return false;
}
// https://tc39.es/proposal-temporal/#prod-TimeSpecWithOptionalTimeZoneNotAmbiguous
bool ISO8601Parser::parse_time_spec_with_optional_time_zone_not_ambiguous()
{
// TimeSpecWithOptionalTimeZoneNotAmbiguous :
// TimeHour TimeZoneNumericUTCOffsetNotAmbiguous[opt] TimeZoneBracketedAnnotation[opt]
// TimeHourNotValidMonth TimeZone
// TimeHour : TimeMinute TimeZone[opt]
// TimeHourMinuteBasicFormatNotAmbiguous TimeZoneBracketedAnnotation[opt]
// TimeHour TimeMinute TimeZoneNumericUTCOffsetNotAmbiguousAllowedNegativeHour TimeZoneBracketedAnnotation[opt]
// TimeHour : TimeMinute : TimeSecond TimeFraction[opt] TimeZone[opt]
// TimeHour TimeMinute TimeSecondNotValidMonth TimeZone[opt]
// TimeHour TimeMinute TimeSecond TimeFraction TimeZone[opt]
{
StateTransaction transaction { *this };
if (parse_time_hour()) {
if (m_state.lexer.consume_specific(':')) {
if (parse_time_minute()) {
if (m_state.lexer.consume_specific(':')) {
if (parse_time_second()) {
(void)parse_time_fraction();
(void)parse_time_zone();
transaction.commit();
return true;
}
} else {
(void)parse_time_zone();
transaction.commit();
return true;
}
}
} else if (parse_time_minute()) {
if (parse_time_zone_numeric_utc_offset_not_ambiguous_allowed_negative_hour()) {
(void)parse_time_zone_bracketed_annotation();
transaction.commit();
return true;
}
{
StateTransaction sub_transaction { *this };
if (parse_time_second() && parse_time_fraction()) {
(void)parse_time_zone();
transaction.commit();
return true;
}
}
if (parse_time_second_not_valid_month()) {
(void)parse_time_zone();
transaction.commit();
return true;
}
} else {
(void)parse_time_zone_numeric_utc_offset_not_ambiguous();
(void)parse_time_zone_bracketed_annotation();
transaction.commit();
return true;
}
}
}
{
StateTransaction transaction { *this };
if (parse_time_hour_not_valid_month() && parse_time_zone()) {
transaction.commit();
return true;
}
}
{
StateTransaction transaction { *this };
if (parse_time_hour_minute_basic_format_not_ambiguous()) {
(void)parse_time_zone_bracketed_annotation();
transaction.commit();
return true;
}
}
return false;
}
// https://tc39.es/proposal-temporal/#prod-TimeSpecSeparator
bool ISO8601Parser::parse_time_spec_separator()
{
@ -816,15 +1148,29 @@ bool ISO8601Parser::parse_date_time()
bool ISO8601Parser::parse_calendar_time()
{
// CalendarTime :
// TimeDesignator[opt] TimeSpec TimeZone[opt] Calendar[opt]
StateTransaction transaction { *this };
(void)parse_time_designator();
if (!parse_time_spec())
return false;
(void)parse_time_zone();
(void)parse_calendar();
transaction.commit();
return true;
// TimeDesignator TimeSpec TimeZone[opt] Calendar[opt]
// TimeSpec TimeZone[opt] Calendar
// TimeSpecWithOptionalTimeZoneNotAmbiguous
{
StateTransaction transaction { *this };
if (parse_time_designator() && parse_time_spec()) {
(void)parse_time_zone();
(void)parse_calendar();
transaction.commit();
return true;
}
}
{
StateTransaction transaction { *this };
if (parse_time_spec()) {
(void)parse_time_zone();
if (parse_calendar()) {
transaction.commit();
return true;
}
}
}
return parse_time_spec_with_optional_time_zone_not_ambiguous();
}
// https://tc39.es/proposal-temporal/#prod-CalendarDateTime
@ -1336,4 +1682,5 @@ Optional<ParseResult> parse_iso8601(Production production, StringView input)
return parser.parse_result();
}
}

View file

@ -22,6 +22,13 @@ struct ParseResult {
Optional<StringView> time_minute;
Optional<StringView> time_second;
Optional<StringView> time_fraction;
Optional<StringView> time_hour_not_valid_month;
Optional<StringView> time_hour_not_thirty_one_day_month;
Optional<StringView> time_hour_two_only;
Optional<StringView> time_minute_not_valid_day;
Optional<StringView> time_minute_thirty_only;
Optional<StringView> time_minute_thirty_one_only;
Optional<StringView> time_second_not_valid_month;
Optional<StringView> calendar_name;
Optional<StringView> utc_designator;
Optional<StringView> time_zone_utc_offset_sign;
@ -103,6 +110,13 @@ public:
[[nodiscard]] bool parse_time_hour();
[[nodiscard]] bool parse_time_minute();
[[nodiscard]] bool parse_time_second();
[[nodiscard]] bool parse_time_hour_not_valid_month();
[[nodiscard]] bool parse_time_hour_not_thirty_one_day_month();
[[nodiscard]] bool parse_time_hour_two_only();
[[nodiscard]] bool parse_time_minute_not_valid_day();
[[nodiscard]] bool parse_time_minute_thirty_only();
[[nodiscard]] bool parse_time_minute_thirty_one_only();
[[nodiscard]] bool parse_time_second_not_valid_month();
[[nodiscard]] bool parse_fractional_part();
[[nodiscard]] bool parse_fraction();
[[nodiscard]] bool parse_time_fraction();
@ -114,6 +128,8 @@ public:
[[nodiscard]] bool parse_time_zone_utc_offset_fraction();
[[nodiscard]] bool parse_time_zone_numeric_utc_offset();
[[nodiscard]] bool parse_time_zone_utc_offset();
[[nodiscard]] bool parse_time_zone_numeric_utc_offset_not_ambiguous();
[[nodiscard]] bool parse_time_zone_numeric_utc_offset_not_ambiguous_allowed_negative_hour();
[[nodiscard]] bool parse_time_zone_utc_offset_name();
[[nodiscard]] bool parse_time_zone_iana_name();
[[nodiscard]] bool parse_time_zone_bracketed_name();
@ -124,6 +140,8 @@ public:
[[nodiscard]] bool parse_calendar_name();
[[nodiscard]] bool parse_calendar();
[[nodiscard]] bool parse_time_spec();
[[nodiscard]] bool parse_time_hour_minute_basic_format_not_ambiguous();
[[nodiscard]] bool parse_time_spec_with_optional_time_zone_not_ambiguous();
[[nodiscard]] bool parse_time_spec_separator();
[[nodiscard]] bool parse_date_time();
[[nodiscard]] bool parse_calendar_time();

View file

@ -64,4 +64,26 @@ describe("errors", () => {
"Invalid time string '2021-07-06T23:42:01Z': must not contain a UTC designator"
);
});
test("ambiguous string must contain a time designator", () => {
const values = [
// YYYY-MM or HHMM-UU
"2021-12",
// MMDD or HHMM
"1214",
"0229",
"1130",
// MM-DD or HH-UU
"12-14",
// YYYYMM or HHMMSS
"202112",
];
for (const value of values) {
expect(() => {
Temporal.PlainTime.from(value);
}).toThrowWithMessage(RangeError, `Invalid time string '${value}'`);
// Doesn't throw
Temporal.PlainTime.from(`T${value}`);
}
});
});