From 3f1571410a01d7507e038a346eb07b8ee6894e71 Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Thu, 10 Aug 2023 02:22:39 +0200 Subject: [PATCH] Editorial: Reintroduce an AO that formats UTC offset with ns precision Recently the code for formatting UTC offsets with nanosecond precision was inlined into GetOffsetStringFor because that was the only place it was used. However, we need to use it in more than one place after the user code audit of #2519 because we'll be pre-calculating the UTC offset in cases where it's calculated more than once. Specifically, in ZonedDateTime.p.getISOFields(), so change that to use the new FormatUTCOffsetNanoseconds operation. --- polyfill/lib/ecmascript.mjs | 8 +++---- polyfill/lib/zoneddatetime.mjs | 3 ++- spec/timezone.html | 38 ++++++++++++++++++++++++---------- spec/zoneddatetime.html | 3 ++- 4 files changed, 34 insertions(+), 18 deletions(-) diff --git a/polyfill/lib/ecmascript.mjs b/polyfill/lib/ecmascript.mjs index b05c3b701c..acbec40e32 100644 --- a/polyfill/lib/ecmascript.mjs +++ b/polyfill/lib/ecmascript.mjs @@ -1419,7 +1419,7 @@ export function InterpretISODateTimeOffset( // the user-provided offset doesn't match any instants for this time // zone and date/time. if (offsetOpt === 'reject') { - const offsetStr = formatOffsetStringNanoseconds(offsetNs); + const offsetStr = FormatUTCOffsetNanoseconds(offsetNs); const timeZoneString = IsTemporalTimeZone(timeZone) ? GetSlot(timeZone, TIMEZONE_ID) : 'time zone'; throw new RangeError(`Offset ${offsetStr} is invalid for ${dt} in ${timeZoneString}`); } @@ -2207,12 +2207,10 @@ export function GetOffsetNanosecondsFor(timeZone, instant, getOffsetNanosecondsF export function GetOffsetStringFor(timeZone, instant) { const offsetNs = GetOffsetNanosecondsFor(timeZone, instant); - return formatOffsetStringNanoseconds(offsetNs); + return FormatUTCOffsetNanoseconds(offsetNs); } -// In the spec, the code below only exists as part of GetOffsetStringFor. -// But in the polyfill, we re-use it to provide clearer error messages. -function formatOffsetStringNanoseconds(offsetNs) { +export function FormatUTCOffsetNanoseconds(offsetNs) { const sign = offsetNs < 0 ? '-' : '+'; const absoluteNs = MathAbs(offsetNs); const hour = MathFloor(absoluteNs / 3600e9); diff --git a/polyfill/lib/zoneddatetime.mjs b/polyfill/lib/zoneddatetime.mjs index ed1a41437d..ff805858a3 100644 --- a/polyfill/lib/zoneddatetime.mjs +++ b/polyfill/lib/zoneddatetime.mjs @@ -561,6 +561,7 @@ export class ZonedDateTime { if (!ES.IsTemporalZonedDateTime(this)) throw new TypeError('invalid receiver'); const dt = dateTime(this); const tz = GetSlot(this, TIME_ZONE); + const offsetNanoseconds = ES.GetOffsetNanosecondsFor(tz, GetSlot(this, INSTANT)); return { calendar: GetSlot(this, CALENDAR), isoDay: GetSlot(dt, ISO_DAY), @@ -572,7 +573,7 @@ export class ZonedDateTime { isoNanosecond: GetSlot(dt, ISO_NANOSECOND), isoSecond: GetSlot(dt, ISO_SECOND), isoYear: GetSlot(dt, ISO_YEAR), - offset: ES.GetOffsetStringFor(tz, GetSlot(this, INSTANT)), + offset: ES.FormatUTCOffsetNanoseconds(offsetNanoseconds), timeZone: tz }; } diff --git a/spec/timezone.html b/spec/timezone.html index 25cdd6d121..effb3c7344 100644 --- a/spec/timezone.html +++ b/spec/timezone.html @@ -507,6 +507,32 @@

+ +

+ FormatUTCOffsetNanoseconds ( + _offsetNanoseconds_: an integer, + ): a String +

+
+
description
+
+ If the offset represents an integer number of minutes, then the output will be formatted like ±HH:MM. + Otherwise, the output will be formatted like ±HH:MM:SS or (if the offset does not evenly divide into seconds) ±HH:MM:SS.fff… where the "fff" part is a sequence of at least 1 and at most 9 fractional seconds digits with no trailing zeroes. +
+
+ + 1. If _offsetNanoseconds_ ≥ 0, let _sign_ be the code unit 0x002B (PLUS SIGN); otherwise, let _sign_ be the code unit 0x002D (HYPHEN-MINUS). + 1. Let _absoluteNanoseconds_ be abs(_offsetNanoseconds_). + 1. Let _hour_ be floor(_absoluteNanoseconds_ / (3600 × 109)). + 1. Let _minute_ be floor(_absoluteNanoseconds_ / (60 × 109)) modulo 60. + 1. Let _second_ be floor(_absoluteNanoseconds_ / 109) modulo 60. + 1. Let _subSecondNanoseconds_ be _absoluteNanoseconds_ modulo 109. + 1. If _second_ = 0 and _subSecondNanoseconds_ = 0, let _precision_ be *"minute"*; otherwise, let _precision_ be *"auto"*. + 1. Let _timeString_ be FormatTimeString(_hour_, _minute_, _second_, _subSecondNanoseconds_, _precision_). + 1. Return the string-concatenation of _sign_ and _timeString_. + +
+

FormatDateTimeUTCOffsetRounded ( @@ -656,21 +682,11 @@

This operation is the internal implementation of the `Temporal.TimeZone.prototype.getOffsetStringFor` method. If the given _timeZone_ is an Object, it observably calls _timeZone_'s `getOffsetNanosecondsFor` method. - If the offset represents an integer number of minutes, then the output will be formatted like ±HH:MM. - Otherwise, the output will be formatted like ±HH:MM:SS or (if the offset does not evenly divide into seconds) ±HH:MM:SS.fff… where the "fff" part is a sequence of at least 1 and at most 9 fractional seconds digits with no trailing zeroes.
1. Let _offsetNanoseconds_ be ? GetOffsetNanosecondsFor(_timeZone_, _instant_). - 1. If _offsetNanoseconds_ ≥ 0, let _sign_ be the code unit 0x002B (PLUS SIGN); otherwise, let _sign_ be the code unit 0x002D (HYPHEN-MINUS). - 1. Let _absoluteNanoseconds_ be abs(_offsetNanoseconds_). - 1. Let _hour_ be floor(_absoluteNanoseconds_ / (3600 × 109)). - 1. Let _minute_ be floor(_absoluteNanoseconds_ / (60 × 109)) modulo 60. - 1. Let _second_ be floor(_absoluteNanoseconds_ / 109) modulo 60. - 1. Let _subSecondNanoseconds_ be _absoluteNanoseconds_ modulo 109. - 1. If _second_ = 0 and _subSecondNanoseconds_ = 0, let _precision_ be *"minute"*; otherwise, let _precision_ be *"auto"*. - 1. Let _timeString_ be FormatTimeString(_hour_, _minute_, _second_, _subSecondNanoseconds_, _precision_). - 1. Return the string-concatenation of _sign_ and _timeString_. + 1. Return FormatUTCOffsetNanoseconds(_offsetNanoseconds_). diff --git a/spec/zoneddatetime.html b/spec/zoneddatetime.html index 1afa97caa8..6c79a46a28 100644 --- a/spec/zoneddatetime.html +++ b/spec/zoneddatetime.html @@ -971,7 +971,8 @@

Temporal.ZonedDateTime.prototype.getISOFields ( )

1. Let _instant_ be ! CreateTemporalInstant(_zonedDateTime_.[[Nanoseconds]]). 1. Let _calendar_ be _zonedDateTime_.[[Calendar]]. 1. Let _dateTime_ be ? GetPlainDateTimeFor(_timeZone_, _instant_, _calendar_). - 1. Let _offset_ be ? GetOffsetStringFor(_timeZone_, _instant_). + 1. Let _offsetNanoseconds_ be ? GetOffsetNanosecondsFor(_timeZone_, _instant_). + 1. Let _offset_ be FormatUTCOffsetNanoseconds(_offsetNanoseconds_). 1. Perform ! CreateDataPropertyOrThrow(_fields_, *"calendar"*, _calendar_). 1. Perform ! CreateDataPropertyOrThrow(_fields_, *"isoDay"*, 𝔽(_dateTime_.[[ISODay]])). 1. Perform ! CreateDataPropertyOrThrow(_fields_, *"isoHour"*, 𝔽(_dateTime_.[[ISOHour]])).