diff --git a/polyfill/test/validStrings.mjs b/polyfill/test/validStrings.mjs index c3cd03c76..7c5f1f6d9 100644 --- a/polyfill/test/validStrings.mjs +++ b/polyfill/test/validStrings.mjs @@ -76,6 +76,7 @@ class Literal { return this.str; } } +const empty = new Literal(''); class Optional { constructor(productionLike) { @@ -189,6 +190,8 @@ function seq(...productions) { // characters const temporalSign = character('+-−'); +const dateSeparator = (extended) => (extended ? character('-') : empty); +const timeSeparator = (extended) => (extended ? character(':') : empty); const hour = zeroPaddedInclusive(0, 23, 2); const minuteSecond = zeroPaddedInclusive(0, 59, 2); const temporalDecimalSeparator = character('.,'); @@ -242,22 +245,28 @@ const timeFraction = withCode(temporalDecimalFraction, (data, result) => { data.microsecond = +fraction.slice(3, 6); data.nanosecond = +fraction.slice(6, 9); }); + function saveOffset(data, result) { data.offset = ES.ParseDateTimeUTCOffset(result); } -const utcOffsetSubMinutePrecision = withCode( - seq( - temporalSign, - hour, +const utcOffset = (subMinutePrecision) => + seq(temporalSign, hour, [ choice( - [minuteSecond, [minuteSecond, [temporalDecimalFraction]]], - seq(':', minuteSecond, [':', minuteSecond, [temporalDecimalFraction]]) + seq( + timeSeparator(true), + minuteSecond, + subMinutePrecision ? [timeSeparator(true), minuteSecond, [temporalDecimalFraction]] : empty + ), + seq( + timeSeparator(false), + minuteSecond, + subMinutePrecision ? [timeSeparator(false), minuteSecond, [temporalDecimalFraction]] : empty + ) ) - ), - saveOffset -); -const dateTimeUTCOffset = choice(utcDesignator, utcOffsetSubMinutePrecision); -const timeZoneUTCOffsetName = seq(temporalSign, hour, choice([minuteSecond], seq(':', minuteSecond))); + ]); +const dateTimeUTCOffset = (z) => + z ? choice(utcDesignator, withCode(utcOffset(true), saveOffset)) : withCode(utcOffset(true), saveOffset); +const timeZoneUTCOffsetName = utcOffset(false); const timeZoneIANAName = choice(...timezoneNames); const timeZoneIdentifier = withCode( choice(timeZoneUTCOffsetName, timeZoneIANAName), @@ -281,42 +290,48 @@ const annotations = withSyntaxConstraints(oneOrMore(choice(calendarAnnotation, a throw new SyntaxError('more than one calendar annotation and at least one critical'); } }); -const timeSpec = seq( - timeHour, - choice([':', timeMinute, [':', timeSecond, [timeFraction]]], seq(timeMinute, [timeSecond, [timeFraction]])) -); -const timeSpecWithOptionalOffsetNotAmbiguous = withSyntaxConstraints(seq(timeSpec, [dateTimeUTCOffset]), (result) => { - if (/^(?:(?!02-?30)(?:0[1-9]|1[012])-?(?:0[1-9]|[12][0-9]|30)|(?:0[13578]|10|12)-?31)$/.test(result)) { - throw new SyntaxError('valid PlainMonthDay'); - } - if (/^(?![−-]000000)(?:[0-9]{4}|[+−-][0-9]{6})-?(?:0[1-9]|1[012])$/.test(result)) { - throw new SyntaxError('valid PlainYearMonth'); - } -}); +const timeSpec = (extended) => + seq(timeHour, [timeSeparator(extended), timeMinute, [timeSeparator(extended), timeSecond, [timeFraction]]]); +const time = choice(timeSpec(true), timeSpec(false)); function validateDayOfMonth(result, { year, month, day }) { if (day > ES.ISODaysInMonth(year, month)) throw SyntaxError('retry if bad day of month'); } -const dateSpecMonthDay = withSyntaxConstraints(seq(['--'], dateMonth, ['-'], dateDay), validateDayOfMonth); -const dateSpecYearMonth = seq(dateYear, ['-'], dateMonth); -const date = withSyntaxConstraints( - choice(seq(dateYear, '-', dateMonth, '-', dateDay), seq(dateYear, dateMonth, dateDay)), +const dateSpecMonthDay = withSyntaxConstraints( + seq(['--'], dateMonth, choice(dateSeparator(true), dateSeparator(false)), dateDay), validateDayOfMonth ); -const dateTime = seq(date, [dateTimeSeparator, timeSpec, [dateTimeUTCOffset]]); +const dateSpecYearMonth = seq(dateYear, choice(dateSeparator(true), dateSeparator(false)), dateMonth); +const dateSpec = (extended) => + withSyntaxConstraints( + seq(dateYear, dateSeparator(extended), dateMonth, dateSeparator(extended), dateDay), + validateDayOfMonth + ); +const date = choice(dateSpec(true), dateSpec(false)); +const dateTime = (z, timeRequired) => + seq( + date, + timeRequired + ? seq(dateTimeSeparator, time, [dateTimeUTCOffset(z)]) + : [dateTimeSeparator, time, [dateTimeUTCOffset(z)]] + ); const annotatedTime = choice( - seq(timeDesignator, timeSpec, [dateTimeUTCOffset], [timeZoneAnnotation], [annotations]), - seq(timeSpecWithOptionalOffsetNotAmbiguous, [timeZoneAnnotation], [annotations]) -); -const annotatedDateTime = seq(dateTime, [timeZoneAnnotation], [annotations]); -const annotatedDateTimeTimeRequired = seq( - date, - dateTimeSeparator, - timeSpec, - [dateTimeUTCOffset], - [timeZoneAnnotation], - [annotations] + seq(timeDesignator, time, [dateTimeUTCOffset(false)], [timeZoneAnnotation], [annotations]), + seq( + withSyntaxConstraints(seq(time, [dateTimeUTCOffset(false)]), (result) => { + if (/^(?:(?!02-?30)(?:0[1-9]|1[012])-?(?:0[1-9]|[12][0-9]|30)|(?:0[13578]|10|12)-?31)$/.test(result)) { + throw new SyntaxError('valid PlainMonthDay'); + } + if (/^(?![−-]000000)(?:[0-9]{4}|[+−-][0-9]{6})-?(?:0[1-9]|1[012])$/.test(result)) { + throw new SyntaxError('valid PlainYearMonth'); + } + }), + [timeZoneAnnotation], + [annotations] + ) ); +const annotatedDateTime = (zoned, timeRequired) => + seq(dateTime(zoned, timeRequired), zoned ? timeZoneAnnotation : [timeZoneAnnotation], [annotations]); const annotatedYearMonth = withSyntaxConstraints( seq(dateSpecYearMonth, [timeZoneAnnotation], [annotations]), (result, data) => { @@ -427,19 +442,19 @@ const duration = withSyntaxConstraints( } ); -const instant = seq(date, dateTimeSeparator, timeSpec, dateTimeUTCOffset, [timeZoneAnnotation], [annotations]); -const zonedDateTime = seq(dateTime, timeZoneAnnotation, [annotations]); +const instant = seq(date, dateTimeSeparator, time, dateTimeUTCOffset(true), [timeZoneAnnotation], [annotations]); +const zonedDateTime = annotatedDateTime(true, false); // goal elements const goals = { Instant: instant, - Date: annotatedDateTime, - DateTime: annotatedDateTime, + Date: annotatedDateTime(false, false), + DateTime: annotatedDateTime(false, false), Duration: duration, - MonthDay: choice(annotatedMonthDay, annotatedDateTime), - Time: choice(annotatedTime, annotatedDateTimeTimeRequired), + MonthDay: choice(annotatedMonthDay, annotatedDateTime(false, false)), + Time: choice(annotatedTime, annotatedDateTime(false, true)), TimeZone: choice(timeZoneIdentifier, zonedDateTime, instant), - YearMonth: choice(annotatedYearMonth, annotatedDateTime), + YearMonth: choice(annotatedYearMonth, annotatedDateTime(false, false)), ZonedDateTime: zonedDateTime }; @@ -467,16 +482,12 @@ const comparisonItems = { YearMonth: ['year', 'month', 'calendar'], ZonedDateTime: [...dateItems, ...timeItems, 'offset', 'z', 'tzAnnotation', 'calendar'] }; -const plainModes = ['Date', 'DateTime', 'MonthDay', 'Time', 'YearMonth']; function fuzzMode(mode) { console.log('// starting to fuzz ' + mode); for (let count = 0; count < 1000; count++) { - let generatedData, fuzzed; - do { - generatedData = {}; - fuzzed = goals[mode].generate(generatedData); - } while (plainModes.includes(mode) && /[0-9][zZ]/.test(fuzzed)); + const generatedData = {}; + const fuzzed = goals[mode].generate(generatedData); try { const parsingMethod = ES[`ParseTemporal${mode}StringRaw`] ?? ES[`ParseTemporal${mode}String`]; const parsed = parsingMethod(fuzzed); diff --git a/spec/abstractops.html b/spec/abstractops.html index 5e83e5c05..686e2ad13 100644 --- a/spec/abstractops.html +++ b/spec/abstractops.html @@ -1191,6 +1191,10 @@

ISO 8601 grammar

`a` `b` `c` `d` `e` `f` `g` `h` `i` `j` `k` `l` `m` `n` `o` `p` `q` `r` `s` `t` `u` `v` `w` `x` `y` `z` + DateSeparator[Extended] ::: + [+Extended] `-` + [~Extended] [empty] + DaysDesignator ::: one of `D` `d` @@ -1239,9 +1243,6 @@

ISO 8601 grammar

`11` `12` - DateMonthWithThirtyOneDays ::: one of - `01` `03` `05` `07` `08` `10` `12` - DateDay ::: `0` NonZeroDigit `1` DecimalDigit @@ -1250,22 +1251,19 @@

ISO 8601 grammar

`31` DateSpecYearMonth ::: - DateYear `-`? DateMonth + DateYear DateSeparator[+Extended] DateMonth + DateYear DateSeparator[~Extended] DateMonth DateSpecMonthDay ::: - `--` DateMonth `-`? DateDay - DateMonth `-`? DateDay + `--`? DateMonth DateSeparator[+Extended] DateDay + `--`? DateMonth DateSeparator[~Extended] DateDay - ValidMonthDay ::: - DateMonth `-`? `0` NonZeroDigit - DateMonth `-`? `1` DecimalDigit - DateMonth `-`? `2` DecimalDigit - DateMonth `-`? `30` but not one of `0230` or `02-30` - DateMonthWithThirtyOneDays `-`? `31` + DateSpec[Extended] ::: + DateYear DateSeparator[?Extended] DateMonth DateSeparator[?Extended] DateDay Date ::: - DateYear `-` DateMonth `-` DateDay - DateYear DateMonth DateDay + DateSpec[+Extended] + DateSpec[~Extended] TimeHour ::: Hour @@ -1280,28 +1278,22 @@

ISO 8601 grammar

TimeFraction ::: TemporalDecimalFraction - UTCOffsetWithSubMinuteComponents[Extended] ::: - TemporalSign Hour TimeSeparator[?Extended] MinuteSecond TimeSeparator[?Extended] MinuteSecond TemporalDecimalFraction? - NormalizedUTCOffset ::: - ASCIISign Hour `:` MinuteSecond + ASCIISign Hour TimeSeparator[+Extended] MinuteSecond - UTCOffsetMinutePrecision ::: + UTCOffset[SubMinutePrecision] ::: TemporalSign Hour TemporalSign Hour TimeSeparator[+Extended] MinuteSecond TemporalSign Hour TimeSeparator[~Extended] MinuteSecond + [+SubMinutePrecision] TemporalSign Hour TimeSeparator[+Extended] MinuteSecond TimeSeparator[+Extended] MinuteSecond TemporalDecimalFraction? + [+SubMinutePrecision] TemporalSign Hour TimeSeparator[~Extended] MinuteSecond TimeSeparator[~Extended] MinuteSecond TemporalDecimalFraction? - UTCOffsetSubMinutePrecision ::: - UTCOffsetMinutePrecision - UTCOffsetWithSubMinuteComponents[+Extended] - UTCOffsetWithSubMinuteComponents[~Extended] - - DateTimeUTCOffset ::: - UTCDesignator - UTCOffsetSubMinutePrecision + DateTimeUTCOffset[Z] ::: + [+Z] UTCDesignator + UTCOffset[+SubMinutePrecision] TimeZoneUTCOffsetName ::: - UTCOffsetMinutePrecision + UTCOffset[~SubMinutePrecision] TZLeadingChar ::: Alpha @@ -1356,30 +1348,26 @@

ISO 8601 grammar

Annotations ::: Annotation Annotations? - TimeSpec ::: + TimeSpec[Extended] ::: TimeHour - TimeHour `:` TimeMinute - TimeHour TimeMinute - TimeHour `:` TimeMinute `:` TimeSecond TimeFraction? - TimeHour TimeMinute TimeSecond TimeFraction? + TimeHour TimeSeparator[?Extended] TimeMinute + TimeHour TimeSeparator[?Extended] TimeMinute TimeSeparator[?Extended] TimeSecond TimeFraction? - TimeSpecWithOptionalOffsetNotAmbiguous ::: - TimeSpec DateTimeUTCOffset? but not one of ValidMonthDay or DateSpecYearMonth + Time ::: + TimeSpec[+Extended] + TimeSpec[~Extended] - DateTime ::: - Date - Date DateTimeSeparator TimeSpec DateTimeUTCOffset? + DateTime[Z, TimeRequired] ::: + [~TimeRequired] Date + Date DateTimeSeparator Time DateTimeUTCOffset[?Z]? AnnotatedTime ::: - TimeDesignator TimeSpec DateTimeUTCOffset? TimeZoneAnnotation? Annotations? - TimeSpecWithOptionalOffsetNotAmbiguous TimeZoneAnnotation? Annotations? - - AnnotatedDateTime[Zoned] ::: - [~Zoned] DateTime TimeZoneAnnotation? Annotations? - [+Zoned] DateTime TimeZoneAnnotation Annotations? + TimeDesignator Time DateTimeUTCOffset[~Z]? TimeZoneAnnotation? Annotations? + Time DateTimeUTCOffset[~Z]? TimeZoneAnnotation? Annotations? - AnnotatedDateTimeTimeRequired ::: - Date DateTimeSeparator TimeSpec DateTimeUTCOffset? TimeZoneAnnotation? Annotations? + AnnotatedDateTime[Zoned, TimeRequired] ::: + [~Zoned] DateTime[~Z, ?TimeRequired] TimeZoneAnnotation? Annotations? + [+Zoned] DateTime[+Z, ?TimeRequired] TimeZoneAnnotation Annotations? AnnotatedYearMonth ::: DateSpecYearMonth TimeZoneAnnotation? Annotations? @@ -1460,29 +1448,93 @@

ISO 8601 grammar

TemporalSign? DurationDesignator DurationTime TemporalInstantString ::: - Date DateTimeSeparator TimeSpec DateTimeUTCOffset TimeZoneAnnotation? Annotations? + Date DateTimeSeparator Time DateTimeUTCOffset[+Z] TimeZoneAnnotation? Annotations? TemporalDateTimeString[Zoned] ::: - AnnotatedDateTime[?Zoned] + AnnotatedDateTime[?Zoned, ~TimeRequired] TemporalDurationString ::: Duration TemporalMonthDayString ::: AnnotatedMonthDay - AnnotatedDateTime[~Zoned] + AnnotatedDateTime[~Zoned, ~TimeRequired] TemporalTimeString ::: AnnotatedTime - AnnotatedDateTimeTimeRequired + AnnotatedDateTime[~Zoned, +TimeRequired] TemporalYearMonthString ::: AnnotatedYearMonth - AnnotatedDateTime[~Zoned] + AnnotatedDateTime[~Zoned, ~TimeRequired] + +

Static Semantics: IsValidMonthDay ( ): a Boolean

+
+ + DateSpec[Extended] ::: + DateYear DateSeparator[?Extended] DateMonth DateSeparator[?Extended] DateDay + + DateSpecMonthDay ::: + `--`? DateMonth DateSeparator[+Extended] DateDay + `--`? DateMonth DateSeparator[~Extended] DateDay + + + 1. If |DateDay| is *"31"* and |DateMonth| is *"02"*, *"04"*, *"06"*, *"09"*, *"11"*, return *false*. + 1. If |DateMonth| is *"02"* and |DateDay| is *"30"*, return *false*. + 1. Return *true*. + +
+ + +

Static Semantics: IsValidDate ( ): a Boolean

+
+ + DateSpec[Extended] ::: + DateYear DateSeparator[?Extended] DateMonth DateSeparator[?Extended] DateDay + + + 1. If IsValidMonthDay of |DateSpec| is *false*, return *false*. + 1. Let _year_ be ℝ(StringToNumber(CodePointsToString(|DateYear|))). + 1. If |DateMonth| is *"02"* and |DateDay| is *"29"* and MathematicalInLeapYear(EpochTimeForYear(_year_)) = 0, return *false*. + 1. Return *true*. + +
+

Static Semantics: Early Errors

+ + AnnotatedTime ::: + Time DateTimeUTCOffset[~Z]? TimeZoneAnnotation? Annotations? + + + + DateSpec[Extended] ::: + DateYear DateSeparator[?Extended] DateMonth DateSeparator[?Extended] DateDay + + + + DateSpecMonthDay ::: + `--`? DateMonth DateSeparator[+Extended] DateDay + `--`? DateMonth DateSeparator[~Extended] DateDay + + DateYear ::: TemporalSign DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit @@ -1565,16 +1617,16 @@

1. Let _millisecondMV_ be 0. 1. Let _microsecondMV_ be 0. 1. Let _nanosecondMV_ be 0. - 1. If IsValidISODate(_yearMV_, _monthMV_, _dayMV_) is *false*, throw a *RangeError* exception. - 1. If IsValidTime(_hourMV_, _minuteMV_, _secondMV_, _millisecondMV_, _microsecondMV_, _nanosecondMV_) is *false*, throw a *RangeError* exception. + 1. Assert: IsValidISODate(_yearMV_, _monthMV_, _dayMV_) is *true*. + 1. Assert: IsValidTime(_hourMV_, _minuteMV_, _secondMV_, _millisecondMV_, _microsecondMV_, _nanosecondMV_) is *true*. 1. Let _timeZoneResult_ be the Record { [[Z]]: *false*, [[OffsetString]]: *undefined*, [[TimeZoneAnnotation]]: *undefined* }. 1. If _parseResult_ contains a |TimeZoneIdentifier| Parse Node, then 1. Let _identifier_ be the source text matched by the |TimeZoneIdentifier| Parse Node contained within _parseResult_. 1. Set _timeZoneResult_.[[TimeZoneAnnotation]] to CodePointsToString(_identifier_). 1. If _parseResult_ contains a |UTCDesignator| Parse Node, then 1. Set _timeZoneResult_.[[Z]] to *true*. - 1. Else if _parseResult_ contains a |UTCOffsetSubMinutePrecision| Parse Node, then - 1. Let _offset_ be the source text matched by the |UTCOffsetSubMinutePrecision| Parse Node contained within _parseResult_. + 1. Else if _parseResult_ contains a |UTCOffset[+SubMinutePrecision]| Parse Node, then + 1. Let _offset_ be the source text matched by the |UTCOffset[+SubMinutePrecision]| Parse Node contained within _parseResult_. 1. Set _timeZoneResult_.[[OffsetString]] to CodePointsToString(_offset_). 1. Return the Record { [[Year]]: _yearMV_, @@ -1693,9 +1745,8 @@

It parses the argument as an ISO 8601 string and returns a Record representing each date and time component needed to construct a Temporal.PlainDateTime instance as a distinct field.
- 1. Let _parseResult_ be ParseText(StringToCodePoints(_isoString_), |TemporalDateTimeString|). + 1. Let _parseResult_ be ParseText(StringToCodePoints(_isoString_), |TemporalDateTimeString[~Zoned]|). 1. If _parseResult_ is a List of errors, throw a *RangeError* exception. - 1. If _parseResult_ contains a |UTCDesignator| Parse Node, throw a *RangeError* exception. 1. Return ? ParseISODateTime(_isoString_). @@ -1777,7 +1828,6 @@

1. Let _parseResult_ be ParseText(StringToCodePoints(_isoString_), |TemporalMonthDayString|). 1. If _parseResult_ is a List of errors, throw a *RangeError* exception. - 1. If _parseResult_ contains a |UTCDesignator| Parse Node, throw a *RangeError* exception. 1. Let _result_ be ? ParseISODateTime(_isoString_). 1. Let _year_ be _result_.[[Year]]. 1. If _parseResult_ does not contain a |DateYear| Parse Node, then @@ -1804,7 +1854,6 @@

1. Let _parseResult_ be ParseText(StringToCodePoints(_isoString_), |TemporalDateTimeString|). 1. If _parseResult_ is a List of errors, throw a *RangeError* exception. - 1. If _parseResult_ contains a |UTCDesignator| ParseNode but no |TimeZoneAnnotation| Parse Node, throw a *RangeError* exception. 1. Return ? ParseISODateTime(_isoString_). @@ -1822,7 +1871,6 @@

1. Let _parseResult_ be ParseText(StringToCodePoints(_isoString_), |TemporalTimeString|). 1. If _parseResult_ is a List of errors, throw a *RangeError* exception. - 1. If _parseResult_ contains a |UTCDesignator| Parse Node, throw a *RangeError* exception. 1. Let _result_ be ? ParseISODateTime(_isoString_). 1. Return the Record { [[Hour]]: _result_.[[Hour]], @@ -1889,7 +1937,6 @@

1. Let _parseResult_ be ParseText(StringToCodePoints(_isoString_), |TemporalYearMonthString|). 1. If _parseResult_ is a List of errors, throw a *RangeError* exception. - 1. If _parseResult_ contains a |UTCDesignator| Parse Node, throw a *RangeError* exception. 1. Let _result_ be ? ParseISODateTime(_isoString_). 1. Return the Record { [[Year]]: _result_.[[Year]], diff --git a/spec/intl.html b/spec/intl.html index ba25a18dd..1650b79e0 100644 --- a/spec/intl.html +++ b/spec/intl.html @@ -650,7 +650,7 @@

1. If _toLocaleStringTimeZone_ is present, throw a *TypeError* exception. 1. Set _timeZone_ to ? ToString(_timeZone_). 1. If IsTimeZoneOffsetString(_timeZone_) is *true*, then - 1. Let _parseResult_ be ParseText(StringToCodePoints(_timeZone_), |UTCOffset||UTCOffsetMinutePrecision|). + 1. Let _parseResult_ be ParseText(StringToCodePoints(_timeZone_), |UTCOffset||UTCOffset[~SubMinutePrecision]|). 1. Assert: _parseResult_ is a Parse Node. 1. If _parseResult_ contains more than one |MinuteSecond| Parse Node, throw a *RangeError* exception. 1. Let _offsetNanoseconds_ be ParseTimeZoneOffsetString? ParseDateTimeUTCOffset(_timeZone_). diff --git a/spec/mainadditions.html b/spec/mainadditions.html index 977ce9b0a..5f9f1d28d 100644 --- a/spec/mainadditions.html +++ b/spec/mainadditions.html @@ -252,8 +252,8 @@

Time Zone Offset String FormatFormats

ECMAScript defines string interchange formats for UTC offsets, derived from ISO 8601. - UTC offsets that represent offset time zone identifiers, or that are intended for interoperability with ISO 8601, use only hours and minutes and are specified by |UTCOffsetMinutePrecision|. - UTC offsets that represent the offset of a named or custom time zone can be more precise, and are specified by |UTCOffsetSubMinutePrecision|. + UTC offsets that represent offset time zone identifiers, or that are intended for interoperability with ISO 8601, use only hours and minutes and are specified by |UTCOffset[~SubMinutePrecision]|. + UTC offsets that represent the offset of a named or custom time zone can be more precise, and are specified by |UTCOffset[+SubMinutePrecision]|.

These formats are described by the ISO String grammar in . @@ -461,7 +461,7 @@

- 1. Let _parseResult_ be ParseText(StringToCodePoints(_offsetString_), |UTCOffset||UTCOffsetSubMinutePrecision|). + 1. Let _parseResult_ be ParseText(StringToCodePoints(_offsetString_), |UTCOffset||UTCOffset[+SubMinutePrecision]|). 1. Assert: _parseResult_ is not a List of errors. 1. If _parseResult_ is a List of errors, throw a *RangeError* exception. 1. Assert: _parseResult_ contains a |TemporalSign| Parse Node.