diff --git a/polyfill/test/validStrings.mjs b/polyfill/test/validStrings.mjs index f50c62c8c..e19d48f7b 100644 --- a/polyfill/test/validStrings.mjs +++ b/polyfill/test/validStrings.mjs @@ -222,26 +222,11 @@ const dateYear = withCode( const dateMonth = withCode(zeroPaddedInclusive(1, 12, 2), (data, result) => (data.month = +result)); const dateDay = withCode(zeroPaddedInclusive(1, 31, 2), (data, result) => (data.day = +result)); -function saveHour(data, result) { - data.hour = +result; -} -function saveMinute(data, result) { - data.minute = +result; -} function saveSecond(data, result) { data.second = +result; if (data.second === 60) data.second = 59; } -const timeHour = withCode(hour, saveHour); -const timeMinute = withCode(minuteSecond, saveMinute); const timeSecond = withCode(choice(minuteSecond, '60'), saveSecond); -const timeFraction = withCode(temporalDecimalFraction, (data, result) => { - result = result.slice(1); - const fraction = result.padEnd(9, '0'); - data.millisecond = +fraction.slice(0, 3); - data.microsecond = +fraction.slice(3, 6); - data.nanosecond = +fraction.slice(6, 9); -}); function saveOffset(data, result) { data.offset = ES.ParseDateTimeUTCOffset(result); } @@ -257,10 +242,10 @@ const utcOffsetSubMinutePrecision = withCode( saveOffset ); const dateTimeUTCOffset = choice(utcDesignator, utcOffsetSubMinutePrecision); -const timeZoneUTCOffsetName = seq(temporalSign, hour, choice([minuteSecond], seq(':', minuteSecond))); +const utcOffsetMinutePrecision = seq(temporalSign, hour, choice([minuteSecond], seq(':', minuteSecond))); const timeZoneIANAName = choice(...timezoneNames); const timeZoneIdentifier = withCode( - choice(timeZoneUTCOffsetName, timeZoneIANAName), + choice(utcOffsetMinutePrecision, timeZoneIANAName), (data, result) => (data.tzAnnotation = result) ); const timeZoneAnnotation = seq('[', [annotationCriticalFlag], timeZoneIdentifier, ']'); @@ -282,8 +267,41 @@ const annotations = withSyntaxConstraints(oneOrMore(choice(calendarAnnotation, a } }); const timeSpec = seq( - timeHour, - choice([':', timeMinute, [':', timeSecond, [timeFraction]]], seq(timeMinute, [timeSecond, [timeFraction]])) + withCode(hour, (data, result) => (data.hour = +result)), + choice( + [ + ':', + withCode(minuteSecond, (data, result) => (data.minute = +result)), + [ + ':', + timeSecond, + [ + withCode(temporalDecimalFraction, (data, result) => { + result = result.slice(1); + const fraction = result.padEnd(9, '0'); + data.millisecond = +fraction.slice(0, 3); + data.microsecond = +fraction.slice(3, 6); + data.nanosecond = +fraction.slice(6, 9); + }) + ] + ] + ], + seq( + withCode(minuteSecond, (data, result) => (data.minute = +result)), + [ + timeSecond, + [ + withCode(temporalDecimalFraction, (data, result) => { + result = result.slice(1); + const fraction = result.padEnd(9, '0'); + data.millisecond = +fraction.slice(0, 3); + data.microsecond = +fraction.slice(3, 6); + data.nanosecond = +fraction.slice(6, 9); + }) + ] + ] + ) + ) ); 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)) { @@ -334,31 +352,6 @@ const annotatedMonthDay = withSyntaxConstraints( } ); -const durationSecondsFraction = withCode(temporalDecimalFraction, (data, result) => { - result = result.slice(1); - const fraction = result.padEnd(9, '0'); - data.milliseconds = +fraction.slice(0, 3) * data.factor; - data.microseconds = +fraction.slice(3, 6) * data.factor; - data.nanoseconds = +fraction.slice(6, 9) * data.factor; -}); -const durationMinutesFraction = withCode(temporalDecimalFraction, (data, result) => { - result = result.slice(1); - const ns = +result.padEnd(9, '0') * 60; - data.seconds = Math.trunc(ns / 1e9) * data.factor; - data.milliseconds = Math.trunc((ns % 1e9) / 1e6) * data.factor; - data.microseconds = Math.trunc((ns % 1e6) / 1e3) * data.factor; - data.nanoseconds = Math.trunc(ns % 1e3) * data.factor; -}); -const durationHoursFraction = withCode(temporalDecimalFraction, (data, result) => { - result = result.slice(1); - const ns = +result.padEnd(9, '0') * 3600; - data.minutes = Math.trunc(ns / 6e10) * data.factor; - data.seconds = Math.trunc((ns % 6e10) / 1e9) * data.factor; - data.milliseconds = Math.trunc((ns % 1e9) / 1e6) * data.factor; - data.microseconds = Math.trunc((ns % 1e6) / 1e3) * data.factor; - data.nanoseconds = Math.trunc(ns % 1e3) * data.factor; -}); - const uint32Digits = withSyntaxConstraints(between(1, 10, digit()), (result) => { if (+result >= 2 ** 32) throw new SyntaxError('try again for an uint32'); }); @@ -366,40 +359,77 @@ const timeDurationDigits = (factor) => withSyntaxConstraints(between(1, 16, digit()), (result) => { if (!Number.isSafeInteger(+result * factor)) throw new SyntaxError('try again on unsafe integer'); }); -const durationSeconds = seq( +const durationSecondsPart = seq( withCode(timeDurationDigits(1), (data, result) => (data.seconds = +result * data.factor)), - [durationSecondsFraction], + [ + withCode(temporalDecimalFraction, (data, result) => { + result = result.slice(1); + const fraction = result.padEnd(9, '0'); + data.milliseconds = +fraction.slice(0, 3) * data.factor; + data.microseconds = +fraction.slice(3, 6) * data.factor; + data.nanoseconds = +fraction.slice(6, 9) * data.factor; + }) + ], secondsDesignator ); -const durationMinutes = seq( +const durationMinutesPart = seq( withCode(timeDurationDigits(60), (data, result) => (data.minutes = +result * data.factor)), - choice(seq(minutesDesignator, [durationSeconds]), seq(durationMinutesFraction, minutesDesignator)) + choice( + seq(minutesDesignator, [durationSecondsPart]), + seq( + withCode(temporalDecimalFraction, (data, result) => { + result = result.slice(1); + const ns = +result.padEnd(9, '0') * 60; + data.seconds = Math.trunc(ns / 1e9) * data.factor; + data.milliseconds = Math.trunc((ns % 1e9) / 1e6) * data.factor; + data.microseconds = Math.trunc((ns % 1e6) / 1e3) * data.factor; + data.nanoseconds = Math.trunc(ns % 1e3) * data.factor; + }), + minutesDesignator + ) + ) ); -const durationHours = seq( +const durationHoursPart = seq( withCode(timeDurationDigits(3600), (data, result) => (data.hours = +result * data.factor)), - choice(seq(hoursDesignator, [choice(durationMinutes, durationSeconds)]), seq(durationHoursFraction, hoursDesignator)) + choice( + seq(hoursDesignator, [choice(durationMinutesPart, durationSecondsPart)]), + seq( + withCode(temporalDecimalFraction, (data, result) => { + result = result.slice(1); + const ns = +result.padEnd(9, '0') * 3600; + data.minutes = Math.trunc(ns / 6e10) * data.factor; + data.seconds = Math.trunc((ns % 6e10) / 1e9) * data.factor; + data.milliseconds = Math.trunc((ns % 1e9) / 1e6) * data.factor; + data.microseconds = Math.trunc((ns % 1e6) / 1e3) * data.factor; + data.nanoseconds = Math.trunc(ns % 1e3) * data.factor; + }), + hoursDesignator + ) + ) ); -const durationTime = seq(timeDesignator, choice(durationHours, durationMinutes, durationSeconds)); -const durationDays = seq( +const durationTime = seq(timeDesignator, choice(durationHoursPart, durationMinutesPart, durationSecondsPart)); +const durationDaysPart = seq( withCode(timeDurationDigits(86400), (data, result) => (data.days = +result * data.factor)), daysDesignator ); -const durationWeeks = seq( +const durationWeeksPart = seq( withCode(uint32Digits, (data, result) => (data.weeks = +result * data.factor)), weeksDesignator, - [durationDays] + [durationDaysPart] ); -const durationMonths = seq( +const durationMonthsPart = seq( withCode(uint32Digits, (data, result) => (data.months = +result * data.factor)), monthsDesignator, - [choice(durationWeeks, durationDays)] + [choice(durationWeeksPart, durationDaysPart)] ); -const durationYears = seq( +const durationYearsPart = seq( withCode(uint32Digits, (data, result) => (data.years = +result * data.factor)), yearsDesignator, - [choice(durationMonths, durationWeeks, durationDays)] + [choice(durationMonthsPart, durationWeeksPart, durationDaysPart)] ); -const durationDate = seq(choice(durationYears, durationMonths, durationWeeks, durationDays), [durationTime]); +const durationDate = seq(choice(durationYearsPart, durationMonthsPart, durationWeeksPart, durationDaysPart), [ + durationTime +]); const duration = seq( withCode([temporalSign], (data, result) => (data.factor = result === '-' || result === '\u2212' ? -1 : 1)), durationDesignator, diff --git a/spec/abstractops.html b/spec/abstractops.html index 93463fe98..77d288cd1 100644 --- a/spec/abstractops.html +++ b/spec/abstractops.html @@ -1248,19 +1248,10 @@

ISO 8601 grammar

DateYear `-` DateMonth `-` DateDay DateYear DateMonth DateDay - TimeHour ::: - Hour - - TimeMinute ::: - MinuteSecond - TimeSecond ::: MinuteSecond `60` - TimeFraction ::: - TemporalDecimalFraction - UTCOffsetWithSubMinuteComponents[Extended] ::: TemporalSign Hour TimeSeparator[?Extended] MinuteSecond TimeSeparator[?Extended] MinuteSecond TemporalDecimalFraction? @@ -1281,9 +1272,6 @@

ISO 8601 grammar

UTCDesignator UTCOffsetSubMinutePrecision - TimeZoneUTCOffsetName ::: - UTCOffsetMinutePrecision - TZLeadingChar ::: Alpha `.` @@ -1304,7 +1292,7 @@

ISO 8601 grammar

TimeZoneIANAName `/` TimeZoneIANANameComponent TimeZoneIdentifier ::: - TimeZoneUTCOffsetName + UTCOffsetMinutePrecision TimeZoneIANAName TimeZoneAnnotation ::: @@ -1338,11 +1326,11 @@

ISO 8601 grammar

Annotation Annotations? TimeSpec ::: - TimeHour - TimeHour `:` TimeMinute - TimeHour TimeMinute - TimeHour `:` TimeMinute `:` TimeSecond TimeFraction? - TimeHour TimeMinute TimeSecond TimeFraction? + Hour + Hour `:` MinuteSecond + Hour MinuteSecond + Hour `:` MinuteSecond `:` TimeSecond TemporalDecimalFraction? + Hour MinuteSecond TimeSecond TemporalDecimalFraction? TimeSpecWithOptionalOffsetNotAmbiguous ::: TimeSpec DateTimeUTCOffset? but not one of ValidMonthDay or DateSpecYearMonth @@ -1368,67 +1356,37 @@

ISO 8601 grammar

AnnotatedMonthDay ::: DateSpecMonthDay TimeZoneAnnotation? Annotations? - DurationWholeSeconds ::: - DecimalDigits[~Sep] - - DurationSecondsFraction ::: - TimeFraction - DurationSecondsPart ::: - DurationWholeSeconds DurationSecondsFraction? SecondsDesignator - - DurationWholeMinutes ::: - DecimalDigits[~Sep] - - DurationMinutesFraction ::: - TimeFraction + DecimalDigits[~Sep] TemporalDecimalFraction? SecondsDesignator DurationMinutesPart ::: - DurationWholeMinutes DurationMinutesFraction MinutesDesignator - DurationWholeMinutes MinutesDesignator DurationSecondsPart? - - DurationWholeHours ::: - DecimalDigits[~Sep] - - DurationHoursFraction ::: - TimeFraction + DecimalDigits[~Sep] TemporalDecimalFraction MinutesDesignator + DecimalDigits[~Sep] MinutesDesignator DurationSecondsPart? DurationHoursPart ::: - DurationWholeHours DurationHoursFraction HoursDesignator - DurationWholeHours HoursDesignator DurationMinutesPart - DurationWholeHours HoursDesignator DurationSecondsPart? + DecimalDigits[~Sep] TemporalDecimalFraction HoursDesignator + DecimalDigits[~Sep] HoursDesignator DurationMinutesPart + DecimalDigits[~Sep] HoursDesignator DurationSecondsPart? DurationTime ::: TimeDesignator DurationHoursPart TimeDesignator DurationMinutesPart TimeDesignator DurationSecondsPart - DurationDays ::: - DecimalDigits[~Sep] - DurationDaysPart ::: - DurationDays DaysDesignator - - DurationWeeks ::: - DecimalDigits[~Sep] + DecimalDigits[~Sep] DaysDesignator DurationWeeksPart ::: - DurationWeeks WeeksDesignator DurationDaysPart? - - DurationMonths ::: - DecimalDigits[~Sep] + DecimalDigits[~Sep] WeeksDesignator DurationDaysPart? DurationMonthsPart ::: - DurationMonths MonthsDesignator DurationWeeksPart - DurationMonths MonthsDesignator DurationDaysPart? - - DurationYears ::: - DecimalDigits[~Sep] + DecimalDigits[~Sep] MonthsDesignator DurationWeeksPart + DecimalDigits[~Sep] MonthsDesignator DurationDaysPart? DurationYearsPart ::: - DurationYears YearsDesignator DurationMonthsPart - DurationYears YearsDesignator DurationWeeksPart - DurationYears YearsDesignator DurationDaysPart? + DecimalDigits[~Sep] YearsDesignator DurationMonthsPart + DecimalDigits[~Sep] YearsDesignator DurationWeeksPart + DecimalDigits[~Sep] YearsDesignator DurationDaysPart? DurationDate ::: DurationYearsPart DurationTime? @@ -1503,7 +1461,7 @@

1. Set _foundCalendar_ to CodePointsToString(_value_). 1. If _foundCalendar_ is not *undefined* and the ASCII-lowercase of _foundCalendar_ is not *"iso8601"*, throw a *RangeError* exception. 1. If _parseResult_ is not a Parse Node, throw a *RangeError* exception. - 1. Let each of _year_, _month_, _day_, _hour_, _minute_, _second_, and _fSeconds_ be the source text matched by the respective |DateYear|, |DateMonth|, |DateDay|, |TimeHour|, |TimeMinute|, |TimeSecond|, and |TimeFraction| Parse Node contained within _parseResult_, or an empty sequence of code points if not present. + 1. Let each of _year_, _month_, _day_, _hour_, _minute_, _second_, and _fSeconds_ be the source text matched by the respective |DateYear|, |DateMonth|, |DateDay|, the first |Hour|, the first |MinuteSecond|, |TimeSecond|, and the first |TemporalDecimalFraction| Parse Node contained within _parseResult_, or an empty sequence of code points if not present. 1. If the first code point of _year_ is U+2212 (MINUS SIGN), replace the first code point with U+002D (HYPHEN-MINUS). 1. Let _yearMV_ be ! ToIntegerOrInfinity(CodePointsToString(_year_)). 1. If _month_ is empty, then @@ -1695,7 +1653,48 @@

1. Let _duration_ be ParseText(StringToCodePoints(_isoString_), |TemporalDurationString|). 1. If _duration_ is a List of errors, throw a *RangeError* exception. - 1. Let each of _sign_, _years_, _months_, _weeks_, _days_, _hours_, _fHours_, _minutes_, _fMinutes_, _seconds_, and _fSeconds_ be the source text matched by the respective |TemporalSign|, |DurationYears|, |DurationMonths|, |DurationWeeks|, |DurationDays|, |DurationWholeHours|, |DurationHoursFraction|, |DurationWholeMinutes|, |DurationMinutesFraction|, |DurationWholeSeconds|, and |DurationSecondsFraction| Parse Node contained within _duration_, or an empty sequence of code points if not present. + 1. Let _sign_ be the source text matched by the |TemporalSign| Parse Node contained within _duration_, or an empty sequence of code points if not present. + 1. If _duration_ contains a |DurationYearsPart| Parse Node, then + 1. Let _yearsNode_ be that |DurationYearsPart| Parse Node contained within _duration_. + 1. Let _years_ be the source text matched by the |DecimalDigits| Parse Node contained within _yearsNode_. + 1. Else, + 1. Let _years_ be an empty sequence of code points. + 1. If _duration_ contains a |DurationMonthsPart| Parse Node, then + 1. Let _monthsNode_ be the |DurationMonthsPart| Parse Node contained within _duration_. + 1. Let _months_ be the source text matched by the |DecimalDigits| Parse Node contained within _monthsNode_. + 1. Else, + 1. Let _months_ be an empty sequence of code points. + 1. If _duration_ contains a |DurationWeeksPart| Parse Node, then + 1. Let _weeksNode_ be the |DurationWeeksPart| Parse Node contained within _duration_. + 1. Let _weeks_ be the source text matched by the |DecimalDigits| Parse Node contained within _weeksNode_. + 1. Else, + 1. Let _weeks_ be an empty sequence of code points. + 1. If _duration_ contains a |DurationDaysPart| Parse Node, then + 1. Let _daysNode_ be the |DurationDaysPart| Parse Node contained within _duration_. + 1. Let _days_ be the source text matched by the |DecimalDigits| Parse Node contained within _daysNode_. + 1. Else, + 1. Let _days_ be an empty sequence of code points. + 1. If _duration_ contains a |DurationHoursPart| Parse Node, then + 1. Let _hoursNode_ be the |DurationHoursPart| Parse Node contained within _duration_. + 1. Let _hours_ be the source text matched by the |DecimalDigits| Parse Node contained within _hoursNode_. + 1. Let _fHours_ be the source text matched by the |TemporalDecimalFraction| Parse Node contained within _hoursNode_, or an empty sequence of code points if not present. + 1. Else, + 1. Let _hours_ be an empty sequence of code points. + 1. Let _fHours_ be an empty sequence of code points. + 1. If _duration_ contains a |DurationMinutesPart| Parse Node, then + 1. Let _minutesNode_ be the |DurationMinutesPart| Parse Node contained within _duration_. + 1. Let _minutes_ be the source text matched by the |DecimalDigits| Parse Node contained within _minutesNode_. + 1. Let _fMinutes_ be the source text matched by the |TemporalDecimalFraction| Parse Node contained within _minutesNode_, or an empty sequence of code points if not present. + 1. Else, + 1. Let _minutes_ be an empty sequence of code points. + 1. Let _fMinutes_ be an empty sequence of code points. + 1. If _duration_ contains a |DurationSecondsPart| Parse Node, then + 1. Let _secondsNode_ be the |DurationSecondsPart| Parse Node contained within _duration_. + 1. Let _seconds_ be the source text matched by the |DecimalDigits| Parse Node contained within _secondsNode_. + 1. Let _fSeconds_ be the source text matched by the |TemporalDecimalFraction| Parse Node contained within _secondsNode_, or an empty sequence of code points if not present. + 1. Else, + 1. Let _seconds_ be an empty sequence of code points. + 1. Let _fSeconds_ be an empty sequence of code points. 1. Let _yearsMV_ be ? ToIntegerWithTruncation(CodePointsToString(_years_)). 1. Let _monthsMV_ be ? ToIntegerWithTruncation(CodePointsToString(_months_)). 1. Let _weeksMV_ be ? ToIntegerWithTruncation(CodePointsToString(_weeks_)). diff --git a/spec/mainadditions.html b/spec/mainadditions.html index f9b853f9e..11adbbe49 100644 --- a/spec/mainadditions.html +++ b/spec/mainadditions.html @@ -214,7 +214,7 @@

Time Zone Identifiers

Time zone identifiers are compared using ASCII-case-insensitive comparisons, and are accepted as input in any variation of letter case. - Offset time zone identifiers are compared using the number of minutes represented (not as a String), and are accepted as input in any the formats specified by |TimeZoneUTCOffsetName|. + Offset time zone identifiers are compared using the number of minutes represented (not as a String), and are accepted as input in any the formats specified by |UTCOffsetMinutePrecision|. However, ECMAScript built-in objects will only output the normalized format of a time zone identifier. The normalized format of an available named time zone identifier is the preferred letter case for that identifier. The normalized format of an offset time zone identifier is specified by |NormalizedUTCOffset|. @@ -479,10 +479,10 @@

description
-
The return value indicates whether _offsetString_ conforms to the grammar given by |UTCOffset||TimeZoneUTCOffsetName|.
+
The return value indicates whether _offsetString_ conforms to the grammar given by |UTCOffset||UTCOffsetMinutePrecision|.
- 1. Let _parseResult_ be ParseText(StringToCodePoints(_offsetString_), |UTCOffset||TimeZoneUTCOffsetName|). + 1. Let _parseResult_ be ParseText(StringToCodePoints(_offsetString_), |UTCOffset||UTCOffsetMinutePrecision|). 1. If _parseResult_ is a List of errors, return *false*. 1. Return *true*. diff --git a/spec/timezone.html b/spec/timezone.html index 432a252e8..d7f8417c1 100644 --- a/spec/timezone.html +++ b/spec/timezone.html @@ -1045,8 +1045,8 @@

1. NOTE: _name_ is syntactically valid, but does not necessarily conform to IANA Time Zone Database naming guidelines or correspond with an available named time zone identifier. 1. Return the Record { [[Name]]: _name_, [[OffsetMinutes]]: ~empty~ }. 1. Else, - 1. Assert: _parseResult_ contains a |TimeZoneUTCOffsetName| Parse Node. - 1. Let _offsetString_ be the source text matched by the |TimeZoneUTCOffsetName| Parse Node contained within _parseResult_. + 1. Assert: _parseResult_ contains a |UTCOffsetMinutePrecision| Parse Node. + 1. Let _offsetString_ be the source text matched by the |UTCOffsetMinutePrecision| Parse Node contained within _parseResult_. 1. Let _offsetNanoseconds_ be ! ParseDateTimeUTCOffset(_offsetString_). 1. Let _offsetMinutes_ be _offsetNanoseconds_ / (60 × 109). 1. Assert: _offsetMinutes_ is an integer.