ladybird/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.cpp
2022-11-03 19:15:50 +00:00

1591 lines
48 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/CharacterTypes.h>
#include <LibJS/Runtime/Temporal/ISO8601.h>
namespace JS::Temporal {
namespace Detail {
// https://tc39.es/proposal-temporal/#prod-DecimalDigits
bool ISO8601Parser::parse_decimal_digits()
{
// DecimalDigits[Sep] ::
// DecimalDigit
// DecimalDigits[?Sep] DecimalDigit
// [+Sep] DecimalDigits[+Sep] NumericLiteralSeparator DecimalDigit
// NOTE: Temporal exclusively uses the variant without a separator ([~Sep])
if (!parse_decimal_digit())
return false;
while (parse_decimal_digit())
;
return true;
}
// https://tc39.es/proposal-temporal/#prod-DecimalDigit
bool ISO8601Parser::parse_decimal_digit()
{
// DecimalDigit : one of
// 0 1 2 3 4 5 6 7 8 9
if (m_state.lexer.next_is(is_ascii_digit)) {
m_state.lexer.consume();
return true;
}
return false;
}
// https://tc39.es/proposal-temporal/#prod-NonZeroDigit
bool ISO8601Parser::parse_non_zero_digit()
{
// NonZeroDigit : one of
// 1 2 3 4 5 6 7 8 9
if (m_state.lexer.next_is(is_ascii_digit) && !m_state.lexer.next_is('0')) {
m_state.lexer.consume();
return true;
}
return false;
}
// https://tc39.es/proposal-temporal/#prod-ASCIISign
bool ISO8601Parser::parse_ascii_sign()
{
// ASCIISign : one of
// + -
return m_state.lexer.consume_specific('+')
|| m_state.lexer.consume_specific('-');
}
// https://tc39.es/proposal-temporal/#prod-Sign
bool ISO8601Parser::parse_sign()
{
// Sign :
// ASCIISign
// U+2212
StateTransaction transaction { *this };
auto success = parse_ascii_sign()
|| m_state.lexer.consume_specific("\xE2\x88\x92"sv);
if (!success)
return false;
m_state.parse_result.sign = transaction.parsed_string_view();
transaction.commit();
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()
{
// Hour :
// 0 DecimalDigit
// 1 DecimalDigit
// 20
// 21
// 22
// 23
StateTransaction transaction { *this };
if (m_state.lexer.consume_specific('0') || m_state.lexer.consume_specific('1')) {
if (!parse_decimal_digit())
return false;
} else {
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)
return false;
}
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-MinuteSecond
bool ISO8601Parser::parse_minute_second()
{
// MinuteSecond :
// 0 DecimalDigit
// 1 DecimalDigit
// 2 DecimalDigit
// 3 DecimalDigit
// 4 DecimalDigit
// 5 DecimalDigit
StateTransaction transaction { *this };
auto success = m_state.lexer.consume_specific('0')
|| m_state.lexer.consume_specific('1')
|| 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 (!success)
return false;
if (!parse_decimal_digit())
return false;
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-DecimalSeparator
bool ISO8601Parser::parse_decimal_separator()
{
// DecimalSeparator : one of
// . ,
return m_state.lexer.consume_specific('.')
|| m_state.lexer.consume_specific(',');
}
// https://tc39.es/proposal-temporal/#prod-DaysDesignator
bool ISO8601Parser::parse_days_designator()
{
// DaysDesignator : one of
// D d
return m_state.lexer.consume_specific('D')
|| m_state.lexer.consume_specific('d');
}
// https://tc39.es/proposal-temporal/#prod-HoursDesignator
bool ISO8601Parser::parse_hours_designator()
{
// HoursDesignator : one of
// H h
return m_state.lexer.consume_specific('H')
|| m_state.lexer.consume_specific('h');
}
// https://tc39.es/proposal-temporal/#prod-MinutesDesignator
bool ISO8601Parser::parse_minutes_designator()
{
// MinutesDesignator : one of
// M m
return m_state.lexer.consume_specific('M')
|| m_state.lexer.consume_specific('m');
}
// https://tc39.es/proposal-temporal/#prod-MonthsDesignator
bool ISO8601Parser::parse_months_designator()
{
// MonthsDesignator : one of
// M m
return m_state.lexer.consume_specific('M')
|| m_state.lexer.consume_specific('m');
}
// https://tc39.es/proposal-temporal/#prod-DurationDesignator
bool ISO8601Parser::parse_duration_designator()
{
// DurationDesignator : one of
// P p
return m_state.lexer.consume_specific('P')
|| m_state.lexer.consume_specific('p');
}
// https://tc39.es/proposal-temporal/#prod-SecondsDesignator
bool ISO8601Parser::parse_seconds_designator()
{
// SecondsDesignator : one of
// S s
return m_state.lexer.consume_specific('S')
|| m_state.lexer.consume_specific('s');
}
// https://tc39.es/proposal-temporal/#prod-DateTimeSeparator
bool ISO8601Parser::parse_date_time_separator()
{
// DateTimeSeparator :
// <SP>
// T
// t
return m_state.lexer.consume_specific(' ')
|| m_state.lexer.consume_specific('T')
|| m_state.lexer.consume_specific('t');
}
// https://tc39.es/proposal-temporal/#prod-TimeDesignator
bool ISO8601Parser::parse_time_designator()
{
// TimeDesignator : one of
// T t
return m_state.lexer.consume_specific('T')
|| m_state.lexer.consume_specific('t');
}
// https://tc39.es/proposal-temporal/#prod-WeeksDesignator
bool ISO8601Parser::parse_weeks_designator()
{
// WeeksDesignator : one of
// W w
return m_state.lexer.consume_specific('W')
|| m_state.lexer.consume_specific('w');
}
// https://tc39.es/proposal-temporal/#prod-YearsDesignator
bool ISO8601Parser::parse_years_designator()
{
// YearsDesignator : one of
// Y y
return m_state.lexer.consume_specific('Y')
|| m_state.lexer.consume_specific('y');
}
// https://tc39.es/proposal-temporal/#prod-UTCDesignator
bool ISO8601Parser::parse_utc_designator()
{
// UTCDesignator : one of
// Z z
StateTransaction transaction { *this };
auto success = m_state.lexer.consume_specific('Z')
|| m_state.lexer.consume_specific('z');
if (!success)
return false;
m_state.parse_result.utc_designator = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-AnnotationCriticalFlag
bool ISO8601Parser::parse_annotation_critical_flag()
{
// AnnotationCriticalFlag :
// !
return m_state.lexer.consume_specific('!');
}
// https://tc39.es/proposal-temporal/#prod-DateYear
bool ISO8601Parser::parse_date_year()
{
// DateFourDigitYear :
// DecimalDigit DecimalDigit DecimalDigit DecimalDigit
// DateExtendedYear :
// Sign DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit
// DateYear :
// DateFourDigitYear
// DateExtendedYear
StateTransaction transaction { *this };
if (parse_sign()) {
for (size_t i = 0; i < 6; ++i) {
if (!parse_decimal_digit())
return false;
}
} else {
for (size_t i = 0; i < 4; ++i) {
if (!parse_decimal_digit())
return false;
}
}
// It is a Syntax Error if DateExtendedYear is "-000000" or "000000" (U+2212 MINUS SIGN followed by 000000).
if (transaction.parsed_string_view().is_one_of("-000000"sv, "000000"sv))
return false;
m_state.parse_result.date_year = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-DateMonth
bool ISO8601Parser::parse_date_month()
{
// DateMonth :
// 0 NonZeroDigit
// 10
// 11
// 12
StateTransaction transaction { *this };
if (m_state.lexer.consume_specific('0')) {
if (!parse_non_zero_digit())
return false;
} else {
auto success = m_state.lexer.consume_specific("10"sv)
|| m_state.lexer.consume_specific("11"sv)
|| m_state.lexer.consume_specific("12"sv);
if (!success)
return false;
}
m_state.parse_result.date_month = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-DateMonthWithThirtyOneDays
bool ISO8601Parser::parse_date_month_with_thirty_days()
{
// DateMonthWithThirtyOneDays : one of
// 01 03 05 07 08 10 12
StateTransaction transaction { *this };
auto success = m_state.lexer.consume_specific("01"sv)
|| m_state.lexer.consume_specific("03"sv)
|| m_state.lexer.consume_specific("05"sv)
|| m_state.lexer.consume_specific("07"sv)
|| m_state.lexer.consume_specific("08"sv)
|| m_state.lexer.consume_specific("10"sv)
|| m_state.lexer.consume_specific("12"sv);
if (!success)
return false;
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-DateDay
bool ISO8601Parser::parse_date_day()
{
// DateDay :
// 0 NonZeroDigit
// 1 DecimalDigit
// 2 DecimalDigit
// 30
// 31
StateTransaction transaction { *this };
if (m_state.lexer.consume_specific('0')) {
if (!parse_non_zero_digit())
return false;
} else if (m_state.lexer.consume_specific('1') || m_state.lexer.consume_specific('2')) {
if (!parse_decimal_digit())
return false;
} else {
auto success = m_state.lexer.consume_specific("30"sv)
|| m_state.lexer.consume_specific("31"sv);
if (!success)
return false;
}
m_state.parse_result.date_day = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-DateSpecYearMonth
bool ISO8601Parser::parse_date_spec_year_month()
{
// DateSpecYearMonth :
// DateYear -[opt] DateMonth
StateTransaction transaction { *this };
if (!parse_date_year())
return false;
m_state.lexer.consume_specific('-');
if (!parse_date_month())
return false;
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-DateSpecMonthDay
bool ISO8601Parser::parse_date_spec_month_day()
{
// TwoDashes :
// --
// DateSpecMonthDay :
// TwoDashes[opt] DateMonth -[opt] DateDay
StateTransaction transaction { *this };
m_state.lexer.consume_specific("--"sv);
if (!parse_date_month())
return false;
m_state.lexer.consume_specific('-');
if (!parse_date_day())
return false;
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-ValidMonthDay
bool ISO8601Parser::parse_valid_month_day()
{
// ValidMonthDay :
// DateMonth -[opt] 0 NonZeroDigit
// DateMonth -[opt] 1 DecimalDigit
// DateMonth -[opt] 2 DecimalDigit
// DateMonth -[opt] 30 but not one of 0230 or 02-30
// DateMonthWithThirtyOneDays -[opt] 31
StateTransaction transaction { *this };
if (parse_date_month()) {
m_state.lexer.consume_specific('-');
if (m_state.lexer.consume_specific('0')) {
if (!parse_non_zero_digit())
return false;
} else if (m_state.lexer.consume_specific('1') || m_state.lexer.consume_specific('2')) {
if (!parse_decimal_digit())
return false;
} else if (m_state.lexer.consume_specific("30"sv)) {
if (transaction.parsed_string_view().is_one_of("0230"sv, "02-30"sv))
return false;
} else {
return false;
}
} else if (parse_date_month_with_thirty_days()) {
m_state.lexer.consume_specific('-');
if (!m_state.lexer.consume_specific("31"sv))
return false;
} else {
return false;
}
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-Date
bool ISO8601Parser::parse_date()
{
// Date :
// DateYear - DateMonth - DateDay
// DateYear DateMonth DateDay
StateTransaction transaction { *this };
if (!parse_date_year())
return false;
auto with_dashes = m_state.lexer.consume_specific('-');
if (!parse_date_month())
return false;
if (with_dashes && !m_state.lexer.consume_specific('-'))
return false;
if (!parse_date_day())
return false;
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-TimeHour
bool ISO8601Parser::parse_time_hour()
{
// TimeHour :
// Hour
StateTransaction transaction { *this };
if (!parse_hour())
return false;
m_state.parse_result.time_hour = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-TimeMinute
bool ISO8601Parser::parse_time_minute()
{
// TimeMinute :
// MinuteSecond
StateTransaction transaction { *this };
if (!parse_minute_second())
return false;
m_state.parse_result.time_minute = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-TimeSecond
bool ISO8601Parser::parse_time_second()
{
// TimeSecond :
// MinuteSecond
// 60
StateTransaction transaction { *this };
auto success = parse_minute_second()
|| m_state.lexer.consume_specific("60"sv);
if (!success)
return false;
m_state.parse_result.time_second = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-FractionalPart
bool ISO8601Parser::parse_fractional_part()
{
// FractionalPart :
// DecimalDigit DecimalDigit[opt] DecimalDigit[opt] DecimalDigit[opt] DecimalDigit[opt] DecimalDigit[opt] DecimalDigit[opt] DecimalDigit[opt] DecimalDigit[opt]
if (!parse_decimal_digit())
return false;
for (size_t i = 0; i < 8; ++i) {
if (!parse_decimal_digit())
break;
}
return true;
}
// https://tc39.es/proposal-temporal/#prod-Fraction
bool ISO8601Parser::parse_fraction()
{
// Fraction :
// DecimalSeparator FractionalPart
StateTransaction transaction { *this };
if (!parse_decimal_separator())
return false;
if (!parse_fractional_part())
return false;
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-TimeFraction
bool ISO8601Parser::parse_time_fraction()
{
// TimeFraction :
// Fraction
StateTransaction transaction { *this };
if (!parse_fraction())
return false;
m_state.parse_result.time_fraction = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-TimeZoneUTCOffsetSign
bool ISO8601Parser::parse_time_zone_utc_offset_sign()
{
// TimeZoneUTCOffsetSign :
// Sign
StateTransaction transaction { *this };
if (!parse_sign())
return false;
m_state.parse_result.time_zone_utc_offset_sign = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-TimeZoneUTCOffsetHour
bool ISO8601Parser::parse_time_zone_utc_offset_hour()
{
// TimeZoneUTCOffsetHour :
// Hour
StateTransaction transaction { *this };
if (!parse_hour())
return false;
m_state.parse_result.time_zone_utc_offset_hour = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-TimeZoneUTCOffsetMinute
bool ISO8601Parser::parse_time_zone_utc_offset_minute()
{
// TimeZoneUTCOffsetMinute :
// MinuteSecond
StateTransaction transaction { *this };
if (!parse_minute_second())
return false;
m_state.parse_result.time_zone_utc_offset_minute = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-TimeZoneUTCOffsetSecond
bool ISO8601Parser::parse_time_zone_utc_offset_second()
{
// TimeZoneUTCOffsetSecond :
// MinuteSecond
StateTransaction transaction { *this };
if (!parse_minute_second())
return false;
m_state.parse_result.time_zone_utc_offset_second = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-TimeZoneUTCOffsetFractionalPart
bool ISO8601Parser::parse_time_zone_utc_offset_fractional_part()
{
// TimeZoneUTCOffsetFractionalPart :
// FractionalPart
return parse_fractional_part();
}
// https://tc39.es/proposal-temporal/#prod-TimeZoneUTCOffsetFraction
bool ISO8601Parser::parse_time_zone_utc_offset_fraction()
{
// TimeZoneUTCOffsetFraction :
// DecimalSeparator TimeZoneUTCOffsetFractionalPart
StateTransaction transaction { *this };
if (!parse_decimal_separator())
return false;
if (!parse_time_zone_utc_offset_fractional_part())
return false;
m_state.parse_result.time_zone_utc_offset_fraction = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-TimeZoneNumericUTCOffset
bool ISO8601Parser::parse_time_zone_numeric_utc_offset()
{
// TimeZoneNumericUTCOffset :
// TimeZoneUTCOffsetSign TimeZoneUTCOffsetHour
// TimeZoneUTCOffsetSign TimeZoneUTCOffsetHour : TimeZoneUTCOffsetMinute
// TimeZoneUTCOffsetSign TimeZoneUTCOffsetHour TimeZoneUTCOffsetMinute
// TimeZoneUTCOffsetSign TimeZoneUTCOffsetHour : TimeZoneUTCOffsetMinute : TimeZoneUTCOffsetSecond TimeZoneUTCOffsetFraction[opt]
// TimeZoneUTCOffsetSign TimeZoneUTCOffsetHour TimeZoneUTCOffsetMinute TimeZoneUTCOffsetSecond TimeZoneUTCOffsetFraction[opt]
StateTransaction transaction { *this };
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()) {
if (parse_time_zone_utc_offset_second())
(void)parse_time_zone_utc_offset_fraction();
}
m_state.parse_result.time_zone_numeric_utc_offset = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-TimeZoneUTCOffset
bool ISO8601Parser::parse_time_zone_utc_offset()
{
// TimeZoneUTCOffset :
// TimeZoneNumericUTCOffset
// UTCDesignator
return parse_time_zone_numeric_utc_offset()
|| parse_utc_designator();
}
// https://tc39.es/proposal-temporal/#prod-TimeZoneUTCOffsetName
bool ISO8601Parser::parse_time_zone_utc_offset_name()
{
// TimeZoneUTCOffsetName :
// Sign Hour
// Sign Hour : MinuteSecond
// Sign Hour MinuteSecond
// Sign Hour : MinuteSecond : MinuteSecond Fraction[opt]
// Sign Hour MinuteSecond MinuteSecond Fraction[opt]
StateTransaction transaction { *this };
if (!parse_sign())
return false;
if (!parse_hour())
return false;
if (m_state.lexer.consume_specific(':')) {
if (!parse_minute_second())
return false;
if (m_state.lexer.consume_specific(':')) {
if (!parse_minute_second())
return false;
(void)parse_fraction();
}
} else if (parse_minute_second()) {
if (parse_minute_second())
(void)parse_fraction();
}
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-TZLeadingChar
bool ISO8601Parser::parse_tz_leading_char()
{
// TZLeadingChar :
// Alpha
// .
// _
if (m_state.lexer.next_is(is_ascii_alpha)) {
m_state.lexer.consume();
return true;
}
return m_state.lexer.consume_specific('.')
|| m_state.lexer.consume_specific('_');
}
// https://tc39.es/proposal-temporal/#prod-TZChar
bool ISO8601Parser::parse_tz_char()
{
// TZChar :
// Alpha
// .
// -
// _
if (m_state.lexer.next_is(is_ascii_alpha)) {
m_state.lexer.consume();
return true;
}
return m_state.lexer.consume_specific('.')
|| m_state.lexer.consume_specific('-')
|| m_state.lexer.consume_specific('_');
}
// https://tc39.es/proposal-temporal/#prod-TimeZoneIANANameComponent
bool ISO8601Parser::parse_time_zone_iana_component()
{
// TimeZoneIANANameComponent :
// TZLeadingChar TZChar[opt] TZChar[opt] TZChar[opt] TZChar[opt] TZChar[opt] TZChar[opt] TZChar[opt] TZChar[opt] TZChar[opt] TZChar[opt] TZChar[opt] TZChar[opt] TZChar[opt] but not one of . or ..
StateTransaction transaction { *this };
if (!parse_tz_leading_char())
return false;
for (size_t i = 0; i < 13; ++i) {
if (!parse_tz_char())
break;
}
if (transaction.parsed_string_view().is_one_of("."sv, ".."sv))
return false;
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-TimeZoneIANANameTail
bool ISO8601Parser::parse_time_zone_iana_name_tail()
{
// TimeZoneIANANameTail :
// TimeZoneIANANameComponent
// TimeZoneIANANameComponent / TimeZoneIANANameTail
StateTransaction transaction { *this };
if (!parse_time_zone_iana_component())
return false;
while (m_state.lexer.next_is('/')) {
m_state.lexer.consume();
if (!parse_time_zone_iana_component())
return false;
}
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-TimeZoneIANALegacyName
bool ISO8601Parser::parse_time_zone_iana_legacy_name()
{
// TimeZoneIANALegacyName :
// Etc/GMT0
// GMT0
// GMT-0
// GMT+0
// EST5EDT
// CST6CDT
// MST7MDT
// PST8PDT
return m_state.lexer.consume_specific("Etc/GMT0"sv)
|| m_state.lexer.consume_specific("GMT0"sv)
|| m_state.lexer.consume_specific("GMT-0"sv)
|| m_state.lexer.consume_specific("GMT+0"sv)
|| m_state.lexer.consume_specific("EST5EDT"sv)
|| m_state.lexer.consume_specific("CST6CDT"sv)
|| m_state.lexer.consume_specific("MST7MDT"sv)
|| m_state.lexer.consume_specific("PST8PDT"sv);
}
// https://tc39.es/proposal-temporal/#prod-TimeZoneIANAName
bool ISO8601Parser::parse_time_zone_iana_name()
{
// TimeZoneIANAName :
// Etc/GMT ASCIISign UnpaddedHour
// TimeZoneIANANameTail
// TimeZoneIANALegacyName
// NOTE: Reverse order here because `TimeZoneIANANameTail` can be a subset of `TimeZoneIANALegacyName`,
// so we'd not attempt to parse that but may not exhaust the input string.
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;
};
return parse_etc_gmt_with_offset()
|| parse_time_zone_iana_legacy_name()
|| parse_time_zone_iana_name_tail();
}
// https://tc39.es/proposal-temporal/#prod-TimeZoneIdentifier
bool ISO8601Parser::parse_time_zone_identifier()
{
// TimeZoneIdentifier :
// TimeZoneIANAName
// TimeZoneUTCOffsetName
StateTransaction transaction { *this };
if (parse_time_zone_iana_name()) {
// no-op.
} else if (!parse_time_zone_utc_offset_name()) {
return false;
}
m_state.parse_result.time_zone_identifier = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-TimeZoneBracketedAnnotation
bool ISO8601Parser::parse_time_zone_bracketed_annotation()
{
// TimeZoneBracketedAnnotation :
// [ AnnotationCriticalFlag[opt] TimeZoneIdentifier ]
StateTransaction transaction { *this };
if (!m_state.lexer.consume_specific('['))
return false;
(void)parse_annotation_critical_flag();
if (!parse_time_zone_identifier())
return false;
if (!m_state.lexer.consume_specific(']'))
return false;
m_state.parse_result.time_zone_bracketed_annotation = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-TimeZoneOffsetRequired
bool ISO8601Parser::parse_time_zone_offset_required()
{
// TimeZoneOffsetRequired :
// TimeZoneUTCOffset TimeZoneBracketedAnnotation[opt]
StateTransaction transaction { *this };
if (!parse_time_zone_utc_offset())
return false;
(void)parse_time_zone_bracketed_annotation();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-TimeZoneNameRequired
bool ISO8601Parser::parse_time_zone_name_required()
{
// TimeZoneNameRequired :
// TimeZoneUTCOffset[opt] TimeZoneBracketedAnnotation
StateTransaction transaction { *this };
(void)parse_time_zone_utc_offset();
if (!parse_time_zone_bracketed_annotation())
return false;
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-TimeZone
bool ISO8601Parser::parse_time_zone()
{
// TimeZone :
// TimeZoneUTCOffset TimeZoneBracketedAnnotation[opt]
// TimeZoneBracketedAnnotation
StateTransaction transaction { *this };
if (parse_time_zone_utc_offset())
(void)parse_time_zone_bracketed_annotation();
else if (!parse_time_zone_bracketed_annotation())
return false;
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-AKeyLeadingChar
bool ISO8601Parser::parse_a_key_leading_char()
{
// AKeyLeadingChar :
// LowercaseAlpha
// _
if (m_state.lexer.next_is(is_ascii_lower_alpha)) {
m_state.lexer.consume();
return true;
}
return m_state.lexer.consume_specific('_');
}
// https://tc39.es/proposal-temporal/#prod-AKeyChar
bool ISO8601Parser::parse_a_key_char()
{
// AKeyChar :
// AKeyLeadingChar
// DecimalDigit
// -
if (parse_a_key_leading_char())
return true;
if (parse_decimal_digit())
return true;
return m_state.lexer.consume_specific('-');
}
// https://tc39.es/proposal-temporal/#prod-AValChar
bool ISO8601Parser::parse_a_val_char()
{
// AValChar :
// Alpha
// DecimalDigit
if (m_state.lexer.next_is(is_ascii_alpha)) {
m_state.lexer.consume();
return true;
}
return parse_decimal_digit();
}
// https://tc39.es/proposal-temporal/#prod-AnnotationKeyTail
bool ISO8601Parser::parse_annotation_key_tail()
{
// AnnotationKeyTail :
// AKeyChar AnnotationKeyTail[opt]
if (!parse_a_key_char())
return false;
// This is implemented without recursion to prevent stack overflow with annotation key tails that have many characters.
while (parse_a_key_char())
;
return true;
}
// https://tc39.es/proposal-temporal/#prod-AnnotationKey
bool ISO8601Parser::parse_annotation_key()
{
// AnnotationKey :
// AKeyLeadingChar AnnotationKeyTail[opt]
StateTransaction transaction { *this };
if (!parse_a_key_leading_char()) {
m_state.parse_result.annotation_key = Optional<StringView> {};
return false;
}
(void)parse_annotation_key_tail();
m_state.parse_result.annotation_key = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-AnnotationValueComponent
bool ISO8601Parser::parse_annotation_value_component()
{
// AnnotationValueComponent :
// AValChar AnnotationValueComponent[opt]
if (!parse_a_val_char())
return false;
// This is implemented without recursion to prevent stack overflow with annotation value components that have many characters.
while (parse_a_val_char())
;
return true;
}
// https://tc39.es/proposal-temporal/#prod-AnnotationValueTail
bool ISO8601Parser::parse_annotation_value_tail()
{
// AnnotationValueTail :
// AnnotationValueComponent
// AnnotationValueComponent - AnnotationValueTail
// This is implemented without recursion to prevent stack overflow with annotation values that have many dashes.
for (;;) {
if (!parse_annotation_value_component())
return false;
if (!m_state.lexer.consume_specific('-'))
break;
}
return true;
}
// https://tc39.es/proposal-temporal/#prod-AnnotationValue
bool ISO8601Parser::parse_annotation_value()
{
// AnnotationValue :
// AnnotationValueTail
StateTransaction transaction { *this };
if (!parse_annotation_value_tail()) {
m_state.parse_result.annotation_value = Optional<StringView> {};
return false;
}
m_state.parse_result.annotation_value = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-Annotation
bool ISO8601Parser::parse_annotation()
{
// Annotation :
// [ AnnotationCriticalFlag[opt] AnnotationKey = AnnotationValue ]
StateTransaction transaction { *this };
if (!m_state.lexer.consume_specific('['))
return false;
Annotation annotation;
annotation.critical = parse_annotation_critical_flag();
if (!parse_annotation_key())
return false;
annotation.key = m_state.parse_result.annotation_key.value();
if (!m_state.lexer.consume_specific('='))
return false;
if (!parse_annotation_value())
return false;
annotation.value = m_state.parse_result.annotation_value.value();
if (!m_state.lexer.consume_specific(']'))
return false;
m_state.parse_result.annotations.append(annotation);
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-Annotations
bool ISO8601Parser::parse_annotations()
{
// Annotations :
// Annotation Annotations[opt]
if (!parse_annotation())
return false;
// This is implemented without recursion to prevent stack overflow with ISO strings that have many annotations.
while (parse_annotation())
;
return true;
}
// https://tc39.es/proposal-temporal/#prod-TimeSpec
bool ISO8601Parser::parse_time_spec()
{
// TimeSpec :
// TimeHour
// TimeHour : TimeMinute
// TimeHour TimeMinute
// TimeHour : TimeMinute : TimeSecond TimeFraction[opt]
// TimeHour TimeMinute TimeSecond TimeFraction[opt]
StateTransaction transaction { *this };
if (!parse_time_hour())
return false;
if (m_state.lexer.consume_specific(':')) {
if (!parse_time_minute())
return false;
if (m_state.lexer.consume_specific(':')) {
if (!parse_time_second())
return false;
(void)parse_time_fraction();
}
} else if (parse_time_minute()) {
if (parse_time_second())
(void)parse_time_fraction();
}
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-TimeSpecWithOptionalTimeZoneNotAmbiguous
bool ISO8601Parser::parse_time_spec_with_optional_time_zone_not_ambiguous()
{
// TimeSpecWithOptionalTimeZoneNotAmbiguous :
// TimeSpec TimeZone[opt] but not one of ValidMonthDay or DateSpecYearMonth
{
StateTransaction transaction { *this };
if (parse_valid_month_day() || parse_date_spec_year_month())
return false;
}
StateTransaction transaction { *this };
if (!parse_time_spec())
return false;
(void)parse_time_zone();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-TimeSpecSeparator
bool ISO8601Parser::parse_time_spec_separator()
{
// TimeSpecSeparator :
// DateTimeSeparator TimeSpec
StateTransaction transaction { *this };
if (!parse_date_time_separator())
return false;
if (!parse_time_spec())
return false;
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-DateTime
bool ISO8601Parser::parse_date_time()
{
// DateTime :
// Date TimeSpecSeparator[opt] TimeZone[opt]
if (!parse_date())
return false;
(void)parse_time_spec_separator();
(void)parse_time_zone();
return true;
}
// https://tc39.es/proposal-temporal/#prod-AnnotatedTime
bool ISO8601Parser::parse_annotated_time()
{
// AnnotatedTime :
// TimeDesignator TimeSpec TimeZone[opt] Annotations[opt]
// TimeSpecWithOptionalTimeZoneNotAmbiguous Annotations[opt]
{
StateTransaction transaction { *this };
if (parse_time_designator() && parse_time_spec()) {
(void)parse_time_zone();
(void)parse_annotations();
transaction.commit();
return true;
}
}
StateTransaction transaction { *this };
if (!parse_time_spec_with_optional_time_zone_not_ambiguous())
return false;
(void)parse_annotations();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-AnnotatedDateTime
bool ISO8601Parser::parse_annotated_date_time()
{
// AnnotatedDateTime :
// DateTime Annotations[opt]
if (!parse_date_time())
return false;
(void)parse_annotations();
return true;
}
// https://tc39.es/proposal-temporal/#prod-AnnotatedDateTimeTimeRequired
bool ISO8601Parser::parse_annotated_date_time_time_required()
{
// AnnotatedDateTimeTimeRequired :
// Date TimeSpecSeparator TimeZone[opt] Annotations[opt]
StateTransaction transaction { *this };
if (!parse_date())
return false;
if (!parse_time_spec_separator())
return false;
(void)parse_time_zone();
(void)parse_annotations();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-DurationWholeSeconds
bool ISO8601Parser::parse_duration_whole_seconds()
{
// DurationWholeSeconds :
// DecimalDigits[~Sep]
StateTransaction transaction { *this };
if (!parse_decimal_digits())
return false;
m_state.parse_result.duration_whole_seconds = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-DurationSecondsFraction
bool ISO8601Parser::parse_duration_seconds_fraction()
{
// DurationSecondsFraction :
// TimeFraction
StateTransaction transaction { *this };
if (!parse_time_fraction())
return false;
m_state.parse_result.duration_seconds_fraction = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-DurationSecondsPart
bool ISO8601Parser::parse_duration_seconds_part()
{
// DurationSecondsPart :
// DurationWholeSeconds DurationSecondsFraction[opt] SecondsDesignator
StateTransaction transaction { *this };
if (!parse_duration_whole_seconds())
return false;
(void)parse_duration_seconds_fraction();
if (!parse_seconds_designator())
return false;
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-DurationWholeMinutes
bool ISO8601Parser::parse_duration_whole_minutes()
{
// DurationWholeMinutes :
// DecimalDigits[~Sep]
StateTransaction transaction { *this };
if (!parse_decimal_digits())
return false;
m_state.parse_result.duration_whole_minutes = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-DurationMinutesFraction
bool ISO8601Parser::parse_duration_minutes_fraction()
{
// DurationMinutesFraction :
// TimeFraction
StateTransaction transaction { *this };
if (!parse_time_fraction())
return false;
m_state.parse_result.duration_minutes_fraction = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-DurationMinutesPart
bool ISO8601Parser::parse_duration_minutes_part()
{
// DurationMinutesPart :
// DurationWholeMinutes DurationMinutesFraction[opt] MinutesDesignator DurationSecondsPart[opt]
StateTransaction transaction { *this };
if (!parse_duration_whole_minutes())
return false;
(void)parse_duration_minutes_fraction();
if (!parse_minutes_designator())
return false;
(void)parse_duration_seconds_part();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-DurationWholeHours
bool ISO8601Parser::parse_duration_whole_hours()
{
// DurationWholeHours :
// DecimalDigits[~Sep]
StateTransaction transaction { *this };
if (!parse_decimal_digits())
return false;
m_state.parse_result.duration_whole_hours = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-DurationHoursFraction
bool ISO8601Parser::parse_duration_hours_fraction()
{
// DurationHoursFraction :
// TimeFraction
StateTransaction transaction { *this };
if (!parse_time_fraction())
return false;
m_state.parse_result.duration_hours_fraction = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-DurationHoursPart
bool ISO8601Parser::parse_duration_hours_part()
{
// DurationHoursPart :
// DurationWholeHours DurationHoursFraction[opt] HoursDesignator DurationMinutesPart
// DurationWholeHours DurationHoursFraction[opt] HoursDesignator DurationSecondsPart[opt]
StateTransaction transaction { *this };
if (!parse_duration_whole_hours())
return false;
(void)parse_duration_hours_fraction();
if (!parse_hours_designator())
return false;
(void)(parse_duration_minutes_part()
|| parse_duration_seconds_part());
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-DurationTime
bool ISO8601Parser::parse_duration_time()
{
// DurationTime :
// TimeDesignator DurationHoursPart
// TimeDesignator DurationMinutesPart
// TimeDesignator DurationSecondsPart
StateTransaction transaction { *this };
if (!parse_time_designator())
return false;
auto success = parse_duration_hours_part()
|| parse_duration_minutes_part()
|| parse_duration_seconds_part();
if (!success)
return false;
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-DurationDays
bool ISO8601Parser::parse_duration_days()
{
// DurationDays :
// DecimalDigits[~Sep]
StateTransaction transaction { *this };
if (!parse_decimal_digits())
return false;
m_state.parse_result.duration_days = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-DurationDaysPart
bool ISO8601Parser::parse_duration_days_part()
{
// DurationDaysPart :
// DurationDays DaysDesignator
StateTransaction transaction { *this };
if (!parse_duration_days())
return false;
if (!parse_days_designator())
return false;
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-DurationWeeks
bool ISO8601Parser::parse_duration_weeks()
{
// DurationWeeks :
// DecimalDigits[~Sep]
StateTransaction transaction { *this };
if (!parse_decimal_digits())
return false;
m_state.parse_result.duration_weeks = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-DurationWeeksPart
bool ISO8601Parser::parse_duration_weeks_part()
{
// DurationWeeksPart :
// DurationWeeks WeeksDesignator DurationDaysPart[opt]
StateTransaction transaction { *this };
if (!parse_duration_weeks())
return false;
if (!parse_weeks_designator())
return false;
(void)parse_duration_days_part();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-DurationMonths
bool ISO8601Parser::parse_duration_months()
{
// DurationMonths :
// DecimalDigits[~Sep]
StateTransaction transaction { *this };
if (!parse_decimal_digits())
return false;
m_state.parse_result.duration_months = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-DurationMonthsPart
bool ISO8601Parser::parse_duration_months_part()
{
// DurationMonthsPart :
// DurationMonths MonthsDesignator DurationWeeksPart
// DurationMonths MonthsDesignator DurationDaysPart[opt]
StateTransaction transaction { *this };
if (!parse_duration_months())
return false;
if (!parse_months_designator())
return false;
(void)(parse_duration_weeks_part()
|| parse_duration_days_part());
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-DurationYears
bool ISO8601Parser::parse_duration_years()
{
// DurationYears :
// DecimalDigits[~Sep]
StateTransaction transaction { *this };
if (!parse_decimal_digits())
return false;
m_state.parse_result.duration_years = transaction.parsed_string_view();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-DurationYearsPart
bool ISO8601Parser::parse_duration_years_part()
{
// DurationYearsPart :
// DurationYears YearsDesignator DurationMonthsPart
// DurationYears YearsDesignator DurationWeeksPart
// DurationYears YearsDesignator DurationDaysPart[opt]
StateTransaction transaction { *this };
if (!parse_duration_years())
return false;
if (!parse_years_designator())
return false;
(void)(parse_duration_months_part()
|| parse_duration_weeks_part()
|| parse_duration_days_part());
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-DurationDate
bool ISO8601Parser::parse_duration_date()
{
// DurationDate :
// DurationYearsPart DurationTime[opt]
// DurationMonthsPart DurationTime[opt]
// DurationWeeksPart DurationTime[opt]
// DurationDaysPart DurationTime[opt]
auto success = parse_duration_years_part()
|| parse_duration_months_part()
|| parse_duration_weeks_part()
|| parse_duration_days_part();
if (!success)
return false;
(void)parse_duration_time();
return true;
}
// https://tc39.es/proposal-temporal/#prod-Duration
bool ISO8601Parser::parse_duration()
{
// Duration :
// Sign[opt] DurationDesignator DurationDate
// Sign[opt] DurationDesignator DurationTime
StateTransaction transaction { *this };
(void)parse_sign();
if (!parse_duration_designator())
return false;
auto success = parse_duration_date()
|| parse_duration_time();
if (!success)
return false;
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-TemporalInstantString
bool ISO8601Parser::parse_temporal_instant_string()
{
// TemporalInstantString :
// Date TimeSpecSeparator[opt] TimeZoneOffsetRequired Annotations[opt]
StateTransaction transaction { *this };
if (!parse_date())
return false;
(void)parse_time_spec_separator();
if (!parse_time_zone_offset_required())
return false;
(void)parse_annotations();
transaction.commit();
return true;
}
// https://tc39.es/proposal-temporal/#prod-TemporalDateTimeString
bool ISO8601Parser::parse_temporal_date_time_string()
{
// TemporalDateTimeString :
// AnnotatedDateTime
return parse_annotated_date_time();
}
// https://tc39.es/proposal-temporal/#prod-TemporalDurationString
bool ISO8601Parser::parse_temporal_duration_string()
{
// TemporalDurationString :
// Duration
return parse_duration();
}
// https://tc39.es/proposal-temporal/#prod-TemporalMonthDayString
bool ISO8601Parser::parse_temporal_month_day_string()
{
// TemporalMonthDayString :
// DateSpecMonthDay
// AnnotatedDateTime
// NOTE: Reverse order here because `DateSpecMonthDay` can be a subset of `AnnotatedDateTime`,
// so we'd not attempt to parse that but may not exhaust the input string.
return parse_annotated_date_time()
|| parse_date_spec_month_day();
}
// https://tc39.es/proposal-temporal/#prod-TemporalTimeString
bool ISO8601Parser::parse_temporal_time_string()
{
// TemporalTimeString :
// AnnotatedTime
// AnnotatedDateTimeTimeRequired
// NOTE: Reverse order here because `AnnotatedTime` can be a subset of `AnnotatedDateTimeTimeRequired`,
// so we'd not attempt to parse that but may not exhaust the input string.
return parse_annotated_date_time_time_required()
|| parse_annotated_time();
}
// https://tc39.es/proposal-temporal/#prod-TemporalYearMonthString
bool ISO8601Parser::parse_temporal_year_month_string()
{
// TemporalYearMonthString :
// DateSpecYearMonth
// AnnotatedDateTime
// NOTE: Reverse order here because `DateSpecYearMonth` can be a subset of `AnnotatedDateTime`,
// so we'd not attempt to parse that but may not exhaust the input string.
return parse_annotated_date_time()
|| parse_date_spec_year_month();
}
// https://tc39.es/proposal-temporal/#prod-TemporalZonedDateTimeString
bool ISO8601Parser::parse_temporal_zoned_date_time_string()
{
// TemporalZonedDateTimeString :
// Date TimeSpecSeparator[opt] TimeZoneNameRequired Annotations[opt]
StateTransaction transaction { *this };
if (!parse_date())
return false;
(void)parse_time_spec_separator();
if (!parse_time_zone_name_required())
return false;
(void)parse_annotations();
transaction.commit();
return true;
}
}
#define JS_ENUMERATE_ISO8601_PRODUCTION_PARSERS \
__JS_ENUMERATE(TemporalInstantString, parse_temporal_instant_string) \
__JS_ENUMERATE(TemporalDateTimeString, parse_temporal_date_time_string) \
__JS_ENUMERATE(TemporalDurationString, parse_temporal_duration_string) \
__JS_ENUMERATE(TemporalMonthDayString, parse_temporal_month_day_string) \
__JS_ENUMERATE(TemporalTimeString, parse_temporal_time_string) \
__JS_ENUMERATE(TemporalYearMonthString, parse_temporal_year_month_string) \
__JS_ENUMERATE(TemporalZonedDateTimeString, parse_temporal_zoned_date_time_string) \
__JS_ENUMERATE(TimeZoneIdentifier, parse_time_zone_identifier) \
__JS_ENUMERATE(TimeZoneNumericUTCOffset, parse_time_zone_numeric_utc_offset) \
__JS_ENUMERATE(AnnotationValue, parse_annotation_value) \
__JS_ENUMERATE(DateMonth, parse_date_month)
Optional<ParseResult> parse_iso8601(Production production, StringView input)
{
auto parser = Detail::ISO8601Parser { input };
switch (production) {
#define __JS_ENUMERATE(ProductionName, parse_production) \
case Production::ProductionName: \
if (!parser.parse_production()) \
return {}; \
break;
JS_ENUMERATE_ISO8601_PRODUCTION_PARSERS
#undef __JS_ENUMERATE
default:
VERIFY_NOT_REACHED();
}
// If we parsed successfully but didn't reach the end, the string doesn't match the given production.
if (!parser.lexer().is_eof())
return {};
return parser.parse_result();
}
}