diff --git a/polyfill/lib/ecmascript.mjs b/polyfill/lib/ecmascript.mjs index e7f04c0bc6..6dad2b478e 100644 --- a/polyfill/lib/ecmascript.mjs +++ b/polyfill/lib/ecmascript.mjs @@ -22,6 +22,7 @@ const StringFromCharCode = String.fromCharCode; const StringPrototypeCharCodeAt = String.prototype.charCodeAt; const StringPrototypeMatchAll = String.prototype.matchAll; const StringPrototypeReplace = String.prototype.replace; +const StringPrototypeSlice = String.prototype.slice; import bigInt from 'big-integer'; import callBound from 'call-bind/callBound'; @@ -2344,56 +2345,52 @@ export function ISOYearString(year) { if (year < 0 || year > 9999) { let sign = year < 0 ? '-' : '+'; let yearNumber = MathAbs(year); - yearString = sign + `000000${yearNumber}`.slice(-6); + yearString = sign + ToZeroPaddedDecimalString(yearNumber, 6); } else { - yearString = `0000${year}`.slice(-4); + yearString = ToZeroPaddedDecimalString(year, 4); } return yearString; } export function ISODateTimePartString(part) { - return `00${part}`.slice(-2); + return ToZeroPaddedDecimalString(part, 2); } -export function FormatSecondsStringPart(second, millisecond, microsecond, nanosecond, precision) { - if (precision === 'minute') return ''; - - const secs = `:${ISODateTimePartString(second)}`; - let fraction = millisecond * 1e6 + microsecond * 1e3 + nanosecond; - +export function FormatFractionalSeconds(subSecondNanoseconds, precision) { + let fraction; if (precision === 'auto') { - if (fraction === 0) return secs; - fraction = `${fraction}`.padStart(9, '0'); - while (fraction[fraction.length - 1] === '0') fraction = fraction.slice(0, -1); + if (subSecondNanoseconds === 0) return ''; + const fractionFullPrecision = ToZeroPaddedDecimalString(subSecondNanoseconds, 9); + // now remove any trailing zeroes + fraction = Call(StringPrototypeReplace, fractionFullPrecision, [/0+$/, '']); } else { - if (precision === 0) return secs; - fraction = `${fraction}`.padStart(9, '0').slice(0, precision); + if (precision === 0) return ''; + const fractionFullPrecision = ToZeroPaddedDecimalString(subSecondNanoseconds, 9); + fraction = Call(StringPrototypeSlice, fractionFullPrecision, [0, precision]); } - return `${secs}.${fraction}`; + return `.${fraction}`; +} + +export function FormatTimeString(hour, minute, second, subSecondNanoseconds, precision) { + let result = `${ISODateTimePartString(hour)}:${ISODateTimePartString(minute)}`; + if (precision === 'minute') return result; + + result += `:${ISODateTimePartString(second)}`; + result += FormatFractionalSeconds(subSecondNanoseconds, precision); + return result; } export function TemporalInstantToString(instant, timeZone, precision) { let outputTimeZone = timeZone; if (outputTimeZone === undefined) outputTimeZone = 'UTC'; const dateTime = GetPlainDateTimeFor(outputTimeZone, instant, 'iso8601'); - const year = ISOYearString(GetSlot(dateTime, ISO_YEAR)); - const month = ISODateTimePartString(GetSlot(dateTime, ISO_MONTH)); - const day = ISODateTimePartString(GetSlot(dateTime, ISO_DAY)); - const hour = ISODateTimePartString(GetSlot(dateTime, ISO_HOUR)); - const minute = ISODateTimePartString(GetSlot(dateTime, ISO_MINUTE)); - const seconds = FormatSecondsStringPart( - GetSlot(dateTime, ISO_SECOND), - GetSlot(dateTime, ISO_MILLISECOND), - GetSlot(dateTime, ISO_MICROSECOND), - GetSlot(dateTime, ISO_NANOSECOND), - precision - ); + const dateTimeString = TemporalDateTimeToString(dateTime, precision, 'never'); let timeZoneString = 'Z'; if (timeZone !== undefined) { const offsetNs = GetOffsetNanosecondsFor(outputTimeZone, instant); timeZoneString = FormatDateTimeUTCOffsetRounded(offsetNs); } - return `${year}-${month}-${day}T${hour}:${minute}${seconds}${timeZoneString}`; + return `${dateTimeString}${timeZoneString}`; } function formatAsDecimalNumber(num) { @@ -2439,20 +2436,11 @@ export function TemporalDurationToString( (years === 0 && months === 0 && weeks === 0 && days === 0 && hours === 0 && minutes === 0) || precision !== 'auto' ) { - const fraction = MathAbs(ms.toJSNumber()) * 1e6 + MathAbs(µs.toJSNumber()) * 1e3 + MathAbs(ns.toJSNumber()); - let decimalPart = ToZeroPaddedDecimalString(fraction, 9); - if (precision === 'auto') { - while (decimalPart[decimalPart.length - 1] === '0') { - decimalPart = decimalPart.slice(0, -1); - } - } else if (precision === 0) { - decimalPart = ''; - } else { - decimalPart = decimalPart.slice(0, precision); - } - let secondsPart = seconds.abs().toString(); - if (decimalPart) secondsPart += `.${decimalPart}`; - timePart += `${secondsPart}S`; + const secondsPart = formatAsDecimalNumber(seconds.abs()); + const subSecondNanoseconds = + MathAbs(ms.toJSNumber()) * 1e6 + MathAbs(µs.toJSNumber()) * 1e3 + MathAbs(ns.toJSNumber()); + const subSecondsPart = FormatFractionalSeconds(subSecondNanoseconds, precision); + timePart += `${secondsPart}${subSecondsPart}S`; } let result = `${sign < 0 ? '-' : ''}P${datePart}`; if (timePart) result = `${result}T${timePart}`; @@ -2496,14 +2484,13 @@ export function TemporalDateTimeToString(dateTime, precision, showCalendar = 'au )); } - year = ISOYearString(year); - month = ISODateTimePartString(month); - day = ISODateTimePartString(day); - hour = ISODateTimePartString(hour); - minute = ISODateTimePartString(minute); - const seconds = FormatSecondsStringPart(second, millisecond, microsecond, nanosecond, precision); + const yearString = ISOYearString(year); + const monthString = ISODateTimePartString(month); + const dayString = ISODateTimePartString(day); + const subSecondNanoseconds = millisecond * 1e6 + microsecond * 1e3 + nanosecond; + const timeString = FormatTimeString(hour, minute, second, subSecondNanoseconds, precision); const calendar = MaybeFormatCalendarAnnotation(GetSlot(dateTime, CALENDAR), showCalendar); - return `${year}-${month}-${day}T${hour}:${minute}${seconds}${calendar}`; + return `${yearString}-${monthString}-${dayString}T${timeString}${calendar}`; } export function TemporalMonthDayToString(monthDay, showCalendar = 'auto') { @@ -2555,31 +2542,18 @@ export function TemporalZonedDateTimeToString( const tz = GetSlot(zdt, TIME_ZONE); const dateTime = GetPlainDateTimeFor(tz, instant, 'iso8601'); - - const year = ISOYearString(GetSlot(dateTime, ISO_YEAR)); - const month = ISODateTimePartString(GetSlot(dateTime, ISO_MONTH)); - const day = ISODateTimePartString(GetSlot(dateTime, ISO_DAY)); - const hour = ISODateTimePartString(GetSlot(dateTime, ISO_HOUR)); - const minute = ISODateTimePartString(GetSlot(dateTime, ISO_MINUTE)); - const seconds = FormatSecondsStringPart( - GetSlot(dateTime, ISO_SECOND), - GetSlot(dateTime, ISO_MILLISECOND), - GetSlot(dateTime, ISO_MICROSECOND), - GetSlot(dateTime, ISO_NANOSECOND), - precision - ); - let result = `${year}-${month}-${day}T${hour}:${minute}${seconds}`; + let dateTimeString = TemporalDateTimeToString(dateTime, precision, 'never'); if (showOffset !== 'never') { const offsetNs = GetOffsetNanosecondsFor(tz, instant); - result += FormatDateTimeUTCOffsetRounded(offsetNs); + dateTimeString += FormatDateTimeUTCOffsetRounded(offsetNs); } if (showTimeZone !== 'never') { const identifier = ToTemporalTimeZoneIdentifier(tz); const flag = showTimeZone === 'critical' ? '!' : ''; - result += `[${flag}${identifier}]`; + dateTimeString += `[${flag}${identifier}]`; } - result += MaybeFormatCalendarAnnotation(GetSlot(zdt, CALENDAR), showCalendar); - return result; + dateTimeString += MaybeFormatCalendarAnnotation(GetSlot(zdt, CALENDAR), showCalendar); + return dateTimeString; } export function IsOffsetTimeZoneIdentifier(string) { diff --git a/polyfill/lib/plaintime.mjs b/polyfill/lib/plaintime.mjs index 341874c9f5..df83d0c8e7 100644 --- a/polyfill/lib/plaintime.mjs +++ b/polyfill/lib/plaintime.mjs @@ -47,10 +47,8 @@ function TemporalTimeToString(time, precision, options = undefined) { )); } - hour = ES.ISODateTimePartString(hour); - minute = ES.ISODateTimePartString(minute); - const seconds = ES.FormatSecondsStringPart(second, millisecond, microsecond, nanosecond, precision); - return `${hour}:${minute}${seconds}`; + const subSecondNanoseconds = millisecond * 1e6 + microsecond * 1e3 + nanosecond; + return ES.FormatTimeString(hour, minute, second, subSecondNanoseconds, precision); } export class PlainTime { diff --git a/spec/abstractops.html b/spec/abstractops.html index cf6a027922..421b70eb72 100644 --- a/spec/abstractops.html +++ b/spec/abstractops.html @@ -680,22 +680,60 @@

- -

FormatSecondsStringPart ( _second_, _millisecond_, _microsecond_, _nanosecond_, _precision_ )

+ +

+ FormatFractionalSeconds ( + _subSecondNanoseconds_: an integer, + _precision_: either an integer in the inclusive range 0 to 9 or *"auto"* + ): a String +

+
+
description
+
+ If _precision_ is zero, or if _precision_ is *"auto"* and _subSecondNanoseconds_ is zero, then an empty String will be returned. + Otherwise, the output will be a decimal point followed by a sequence of fractional seconds digits, truncated to _precision_ digits or (if _precision_ is *"auto"*) to the last non-zero digit. +
+
- 1. Assert: _second_, _millisecond_, _microsecond_, and _nanosecond_ are integers. - 1. If _precision_ is *"minute"*, return *""*. - 1. Let _secondsString_ be the string-concatenation of the code unit 0x003A (COLON) and ToZeroPaddedDecimalString(_second_, 2). - 1. Let _fraction_ be _millisecond_ × 106 + _microsecond_ × 103 + _nanosecond_. 1. If _precision_ is *"auto"*, then - 1. If _fraction_ is 0, return _secondsString_. - 1. Set _fraction_ to ToZeroPaddedDecimalString(_fraction_, 9). - 1. Set _fraction_ to the longest possible substring of _fraction_ starting at position 0 and not ending with the code unit 0x0030 (DIGIT ZERO). + 1. If _subSecondNanoseconds_ is 0, return the empty String. + 1. Let _fractionString_ be ToZeroPaddedDecimalString(_subSecondNanoseconds_, 9). + 1. Set _fractionString_ to the longest prefix of _fractionString_ ending with a code unit other than 0x0030 (DIGIT ZERO). 1. Else, - 1. If _precision_ is 0, return _secondsString_. - 1. Set _fraction_ to ToZeroPaddedDecimalString(_fraction_, 9). - 1. Set _fraction_ to the substring of _fraction_ from 0 to _precision_. - 1. Return the string-concatenation of _secondsString_, the code unit 0x002E (FULL STOP), and _fraction_. + 1. If _precision_ is 0, return the empty String. + 1. Let _fractionString_ be ToZeroPaddedDecimalString(_subSecondNanoseconds_, 9). + 1. Set _fractionString_ to the substring of _fractionString_ from 0 to _precision_. + 1. Return the string-concatenation of the code unit 0x002E (FULL STOP) and _fractionString_. + +
+ + +

+ FormatTimeString ( + _hour_: an integer, + _minute_: an integer, + _second_: an integer, + _subSecondNanoseconds_: an integer, + _precision_: an integer in the inclusive range 0 to 9, *"minute"*, or *"auto"* + ): a String +

+
+
description
+
+ The output will be formatted like ±HH:MM if _precision_ is *"minute"*. + Otherwise, the output will be formatted like ±HH:MM:SS if _precision_ is zero, or if _subSecondNanoseconds_ is zero and _precision is *"auto"*. + Otherwise, the output will be formatted like ±HH:MM:SS.fff where "fff" is a sequence of fractional seconds digits, truncated to _precision_ digits or (if _precision_ is *"auto"*) to the last non-zero digit. +
+
+ + 1. Let _hh_ be ToZeroPaddedDecimalString(_hour_, 2). + 1. Let _mm_ be ToZeroPaddedDecimalString(_minute_, 2). + 1. Let _result_ be the string-concatenation of _hh_, the code unit 0x003A (COLON), and _mm_. + 1. If _precision_ is *"minute"*, return _result_. + 1. Let _ss_ be ToZeroPaddedDecimalString(_second_, 2). + 1. Let _subSecondsPart_ be FormatFractionalSeconds(_subSecondNanoseconds_, _precision_). + 1. Set _result_ to the string-concatenation of _result_, the code unit 0x003A (COLON), _ss_, and _subSecondsPart_. + 1. Return _result_.
diff --git a/spec/duration.html b/spec/duration.html index d5983b62a1..b8c1c2e43b 100644 --- a/spec/duration.html +++ b/spec/duration.html @@ -1936,18 +1936,10 @@

1. Let _zeroMinutesAndHigher_ be *false*. 1. If _years_ = 0, and _months_ = 0, and _weeks_ = 0, and _days_ = 0, and _hours_ = 0, and _minutes_ = 0, set _zeroMinutesAndHigher_ to *true*. 1. If _nonzeroSecondsAndLower_ is *true*, or _zeroMinutesAndHigher_ is *true*, or _precision_ is not *"auto"*, then - 1. Let _fraction_ be abs(_milliseconds_) × 106 + abs(_microseconds_) × 103 + abs(_nanoseconds_). - 1. Let _decimalPart_ be ToZeroPaddedDecimalString(_fraction_, 9). - 1. If _precision_ is *"auto"*, then - 1. Set _decimalPart_ to the longest possible substring of _decimalPart_ starting at position 0 and not ending with the code unit 0x0030 (DIGIT ZERO). - 1. Else if _precision_ = 0, then - 1. Set _decimalPart_ to *""*. - 1. Else, - 1. Set _decimalPart_ to the substring of _decimalPart_ from 0 to _precision_. 1. Let _secondsPart_ be abs(_seconds_) formatted as a decimal number. - 1. If _decimalPart_ is not *""*, then - 1. Set _secondsPart_ to the string-concatenation of _secondsPart_, the code unit 0x002E (FULL STOP), and _decimalPart_. - 1. Set _timePart_ to the string concatenation of _timePart_, _secondsPart_, and the code unit 0x0053 (LATIN CAPITAL LETTER S). + 1. Let _subSecondNanoseconds_ be abs(_milliseconds_) × 106 + abs(_microseconds_) × 103 + abs(_nanoseconds_). + 1. Let _subSecondsPart_ be FormatFractionalSeconds(_subSecondNanoseconds_, _precision_). + 1. Set _timePart_ to the string concatenation of _timePart_, _secondsPart_, _subSecondsPart_, and the code unit 0x0053 (LATIN CAPITAL LETTER S). 1. Let _signPart_ be the code unit 0x002D (HYPHEN-MINUS) if _sign_ < 0, and otherwise the empty String. 1. Let _result_ be the string concatenation of _signPart_, the code unit 0x0050 (LATIN CAPITAL LETTER P) and _datePart_. 1. If _timePart_ is not *""*, then diff --git a/spec/mainadditions.html b/spec/mainadditions.html index 7c9b25a27c..a8f84b5b3c 100644 --- a/spec/mainadditions.html +++ b/spec/mainadditions.html @@ -415,6 +415,25 @@

[...]

+ +

+ TimeString ( + _tv_: a Number, but not *NaN*, + ): a String +

+
+
+ + 1. Let _hour_ be ToZeroPaddedDecimalString(ℝ(HourFromTime(_tv_)), 2). + 1. Let _minute_ be ToZeroPaddedDecimalString(ℝ(MinFromTime(_tv_)), 2). + 1. Let _second_ be ToZeroPaddedDecimalString(ℝ(SecFromTime(_tv_)), 2). + 1. Let _timeString_ be FormatTimeString(ℝ(HourFromTime(_tv_)), ℝ(MinFromTime(_tv_)), ℝ(SecFromTime(_tv_)), 0, 0). + 1. Return the string-concatenation of _timeString_, the code unit 0x0020 (SPACE), and *"GMT"*. + +
+ +

[...]

+

TimeZoneString ( diff --git a/spec/plaindate.html b/spec/plaindate.html index 00ff13f815..d797d7661e 100644 --- a/spec/plaindate.html +++ b/spec/plaindate.html @@ -928,7 +928,7 @@

TemporalDateToString ( _temporalDate_, _showCalendar_ )

1. Assert: Type(_temporalDate_) is Object. 1. Assert: _temporalDate_ has an [[InitializedTemporalDate]] internal slot. - 1. Let _year_ be ! PadISOYear(_temporalDate_.[[ISOYear]]). + 1. Let _year_ be PadISOYear(_temporalDate_.[[ISOYear]]). 1. Let _month_ be ToZeroPaddedDecimalString(_temporalDate_.[[ISOMonth]], 2). 1. Let _day_ be ToZeroPaddedDecimalString(_temporalDate_.[[ISODay]], 2). 1. Let _calendar_ be ? MaybeFormatCalendarAnnotation(_temporalDate_.[[Calendar]], _showCalendar_). diff --git a/spec/plaindatetime.html b/spec/plaindatetime.html index 18d465f28e..74cf650dda 100644 --- a/spec/plaindatetime.html +++ b/spec/plaindatetime.html @@ -993,14 +993,13 @@

TemporalDateTimeToString ( _isoYear_, _isoMonth_, _isoDay_, _hour_, _minute_, _second_, _millisecond_, _microsecond_, _nanosecond_, _calendar_, _precision_, _showCalendar_ )

1. Assert: _isoYear_, _isoMonth_, _isoDay_, _hour_, _minute_, _second_, _millisecond_, _microsecond_, and _nanosecond_ are integers. - 1. Let _year_ be ! PadISOYear(_isoYear_). - 1. Let _month_ be ToZeroPaddedDecimalString(_isoMonth_, 2). - 1. Let _day_ be ToZeroPaddedDecimalString(_isoDay_, 2). - 1. Let _hour_ be ToZeroPaddedDecimalString(_hour_, 2). - 1. Let _minute_ be ToZeroPaddedDecimalString(_minute_, 2). - 1. Let _seconds_ be ! FormatSecondsStringPart(_second_, _millisecond_, _microsecond_, _nanosecond_, _precision_). + 1. Let _yearString_ be PadISOYear(_isoYear_). + 1. Let _monthString_ be ToZeroPaddedDecimalString(_isoMonth_, 2). + 1. Let _dayString_ be ToZeroPaddedDecimalString(_isoDay_, 2). + 1. Let _subSecondNanoseconds_ be _millisecond_ × 106 + _microsecond_ × 103 + _nanosecond_. + 1. Let _timeString_ be FormatTimeString(_hour_, _minute_, _second_, _subSecondNanoseconds_, _precision_). 1. Let _calendarString_ be ? MaybeFormatCalendarAnnotation(_calendar_, _showCalendar_). - 1. Return the string-concatenation of _year_, the code unit 0x002D (HYPHEN-MINUS), _month_, the code unit 0x002D (HYPHEN-MINUS), _day_, 0x0054 (LATIN CAPITAL LETTER T), _hour_, the code unit 0x003A (COLON), _minute_, _seconds_, and _calendarString_. + 1. Return the string-concatenation of _yearString_, the code unit 0x002D (HYPHEN-MINUS), _monthString_, the code unit 0x002D (HYPHEN-MINUS), _dayString_, 0x0054 (LATIN CAPITAL LETTER T), _timeString_, and _calendarString_.
diff --git a/spec/plainmonthday.html b/spec/plainmonthday.html index 7aaad254f8..1b5fb1d935 100644 --- a/spec/plainmonthday.html +++ b/spec/plainmonthday.html @@ -437,7 +437,7 @@

TemporalMonthDayToString ( _monthDay_, _showCalendar_ )

1. Let _result_ be the string-concatenation of _month_, the code unit 0x002D (HYPHEN-MINUS), and _day_. 1. Let _calendarIdentifier_ be ? ToTemporalCalendarIdentifier(_monthDay_.[[Calendar]]). 1. If _showCalendar_ is one of *"always"* or *"critical"*, or if _calendarIdentifier_ is not *"iso8601"*, then - 1. Let _year_ be ! PadISOYear(_monthDay_.[[ISOYear]]). + 1. Let _year_ be PadISOYear(_monthDay_.[[ISOYear]]). 1. Set _result_ to the string-concatenation of _year_, the code unit 0x002D (HYPHEN-MINUS), and _result_. 1. Let _calendarString_ be FormatCalendarAnnotation(_calendarIdentifier_, _showCalendar_). 1. Set _result_ to the string-concatenation of _result_ and _calendarString_. diff --git a/spec/plaintime.html b/spec/plaintime.html index f1ce8c20f4..d01a0bf57b 100644 --- a/spec/plaintime.html +++ b/spec/plaintime.html @@ -813,10 +813,8 @@

TemporalTimeToString ( _hour_, _minute_, _second_, _millisecond_, _microsecond_, _nanosecond_, _precision_ )

1. Assert: _hour_, _minute_, _second_, _millisecond_, _microsecond_ and _nanosecond_ are integers. - 1. Let _hour_ be ToZeroPaddedDecimalString(_hour_, 2). - 1. Let _minute_ be ToZeroPaddedDecimalString(_minute_, 2). - 1. Let _seconds_ be ! FormatSecondsStringPart(_second_, _millisecond_, _microsecond_, _nanosecond_, _precision_). - 1. Return the string-concatenation of _hour_, the code unit 0x003A (COLON), _minute_, and _seconds_. + 1. Let _subSecondNanoseconds_ be _millisecond_ × 106 + _microsecond_ × 103 + _nanosecond_. + 1. Return FormatTimeString(_hour_, _minute_, _second_, _subSecondNanoseconds_, _precision_).
diff --git a/spec/plainyearmonth.html b/spec/plainyearmonth.html index 31d6160b8e..192bb056f0 100644 --- a/spec/plainyearmonth.html +++ b/spec/plainyearmonth.html @@ -598,7 +598,7 @@

TemporalYearMonthToString ( _yearMonth_, _showCalendar_ )

1. Assert: Type(_yearMonth_) is Object. 1. Assert: _yearMonth_ has an [[InitializedTemporalYearMonth]] internal slot. - 1. Let _year_ be ! PadISOYear(_yearMonth_.[[ISOYear]]). + 1. Let _year_ be PadISOYear(_yearMonth_.[[ISOYear]]). 1. Let _month_ be ToZeroPaddedDecimalString(_yearMonth_.[[ISOMonth]], 2). 1. Let _result_ be the string-concatenation of _year_, the code unit 0x002D (HYPHEN-MINUS), and _month_. 1. Let _calendarIdentifier_ be ? ToTemporalCalendarIdentifier(_yearMonth_.[[Calendar]]).