Skip to content

Commit

Permalink
Editorial: Reintroduce an AO that formats UTC offset with ns precision
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ptomato committed Aug 10, 2023
1 parent 14154e9 commit 3f15714
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 18 deletions.
8 changes: 3 additions & 5 deletions polyfill/lib/ecmascript.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
}
Expand Down Expand Up @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion polyfill/lib/zoneddatetime.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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
};
}
Expand Down
38 changes: 27 additions & 11 deletions spec/timezone.html
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,32 @@ <h1>
</emu-alg>
</emu-clause>

<emu-clause id="sec-temporal-formatutcoffsetnanoseconds" type="abstract operation">
<h1>
FormatUTCOffsetNanoseconds (
_offsetNanoseconds_: an integer,
): a String
</h1>
<dl class="header">
<dt>description</dt>
<dd>
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.
</dd>
</dl>
<emu-alg>
1. If _offsetNanoseconds_ &ge; 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 × 10<sup>9</sup>)).
1. Let _minute_ be floor(_absoluteNanoseconds_ / (60 × 10<sup>9</sup>)) modulo 60.
1. Let _second_ be floor(_absoluteNanoseconds_ / 10<sup>9</sup>) modulo 60.
1. Let _subSecondNanoseconds_ be _absoluteNanoseconds_ modulo 10<sup>9</sup>.
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_.
</emu-alg>
</emu-clause>

<emu-clause id="sec-temporal-formatdatetimeutcoffsetrounded" type="abstract operation">
<h1>
FormatDateTimeUTCOffsetRounded (
Expand Down Expand Up @@ -656,21 +682,11 @@ <h1>
<dd>
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.
</dd>
</dl>
<emu-alg>
1. Let _offsetNanoseconds_ be ? GetOffsetNanosecondsFor(_timeZone_, _instant_).
1. If _offsetNanoseconds_ &ge; 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 × 10<sup>9</sup>)).
1. Let _minute_ be floor(_absoluteNanoseconds_ / (60 × 10<sup>9</sup>)) modulo 60.
1. Let _second_ be floor(_absoluteNanoseconds_ / 10<sup>9</sup>) modulo 60.
1. Let _subSecondNanoseconds_ be _absoluteNanoseconds_ modulo 10<sup>9</sup>.
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_).
</emu-alg>
</emu-clause>

Expand Down
3 changes: 2 additions & 1 deletion spec/zoneddatetime.html
Original file line number Diff line number Diff line change
Expand Up @@ -971,7 +971,8 @@ <h1>Temporal.ZonedDateTime.prototype.getISOFields ( )</h1>
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]])).
Expand Down

0 comments on commit 3f15714

Please sign in to comment.