Skip to content

Commit

Permalink
Editorial: rename TimeZone nanoseconds slot/AOs
Browse files Browse the repository at this point in the history
Now that we've limited TimeZone's [[OffsetNanoseconds]] internal slot
to minute precision, this commit refactors TimeZone to clarify that only
minutes are allowed in that slot and related abstract operations.

Changes:
* Renames TimeZone's [[OffsetNanoseconds]] internal slot to
  [[OffsetMinutes]]
* Changes ParseTimeZoneIdentifier to return an [[OffsetMinutes]] field
  instead of an [[OffsetNanoseconds]] field.
* Changes FormatOffsetTimeZoneIdentifier to expect a minutes argument.

The goal of this change is to avoid the complexity and potential
confusion from a slot and AOs that deal with "nanoseconds" values
that nonetheless are restricted to minutes.
  • Loading branch information
justingrant committed Jul 19, 2023
1 parent 861aba9 commit 4ed1f37
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 69 deletions.
24 changes: 12 additions & 12 deletions polyfill/lib/ecmascript.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,7 @@ export function ParseTimeZoneIdentifier(identifier) {
if (OFFSET_IDENTIFIER.test(identifier)) {
// The regex limits the input to minutes precision
const { offsetNanoseconds } = ParseDateTimeUTCOffset(identifier);
return { offsetNanoseconds };
return { offsetMinutes: offsetNanoseconds / 60e9 };
}
return { tzName: identifier };
}
Expand Down Expand Up @@ -645,7 +645,7 @@ export function ParseTemporalDurationString(isoString) {
const microseconds = MathTrunc(excessNanoseconds / 1000) % 1000;
const milliseconds = MathTrunc(excessNanoseconds / 1e6) % 1000;
seconds += MathTrunc(excessNanoseconds / 1e9) % 60;
minutes += MathTrunc(excessNanoseconds / 6e10);
minutes += MathTrunc(excessNanoseconds / 60e9);

return { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds };
}
Expand Down Expand Up @@ -2127,8 +2127,8 @@ export function ToTemporalTimeZoneSlotValue(temporalTimeZoneLike) {
const { tzName, offset, z } = ParseTemporalTimeZoneString(identifier);
if (tzName) {
// tzName is any valid identifier string in brackets, and could be an offset identifier
const { offsetNanoseconds } = ParseTimeZoneIdentifier(tzName);
if (offsetNanoseconds !== undefined) return FormatOffsetTimeZoneIdentifier(offsetNanoseconds);
const { offsetMinutes } = ParseTimeZoneIdentifier(tzName);
if (offsetMinutes !== undefined) return FormatOffsetTimeZoneIdentifier(offsetMinutes);

const record = GetAvailableNamedTimeZoneIdentifier(tzName);
if (!record) throw new RangeError(`Unrecognized time zone ${tzName}`);
Expand All @@ -2140,7 +2140,7 @@ export function ToTemporalTimeZoneSlotValue(temporalTimeZoneLike) {
if (hasSubMinutePrecision) {
throw new RangeError(`Seconds not allowed in offset time zone: ${offset}`);
}
return FormatOffsetTimeZoneIdentifier(offsetNanoseconds);
return FormatOffsetTimeZoneIdentifier(offsetNanoseconds / 60e9);
}

export function ToTemporalTimeZoneIdentifier(slotValue) {
Expand Down Expand Up @@ -2209,9 +2209,9 @@ export function GetOffsetStringFor(timeZone, instant) {
// 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) {
const offsetMinutes = MathTrunc(offsetNs / 6e10);
let offsetStringMinutes = FormatOffsetTimeZoneIdentifier(offsetMinutes * 6e10);
const subMinuteNanoseconds = MathAbs(offsetNs) % 6e10;
const offsetMinutes = MathTrunc(offsetNs / 60e9);
let offsetStringMinutes = FormatOffsetTimeZoneIdentifier(offsetMinutes);
const subMinuteNanoseconds = MathAbs(offsetNs) % 60e9;
if (!subMinuteNanoseconds) return offsetStringMinutes;

// For offsets between -1s and 0, exclusive, FormatOffsetTimeZoneIdentifier's
Expand Down Expand Up @@ -2739,9 +2739,9 @@ export function GetNamedTimeZoneOffsetNanoseconds(id, epochNanoseconds) {
return +utc.minus(epochNanoseconds);
}

export function FormatOffsetTimeZoneIdentifier(offsetNanoseconds) {
const sign = offsetNanoseconds < 0 ? '-' : '+';
const absoluteMinutes = MathAbs(offsetNanoseconds / 6e10);
export function FormatOffsetTimeZoneIdentifier(offsetMinutes) {
const sign = offsetMinutes < 0 ? '-' : '+';
const absoluteMinutes = MathAbs(offsetMinutes);
const intHours = MathFloor(absoluteMinutes / 60);
const hh = ISODateTimePartString(intHours);
const intMinutes = absoluteMinutes % 60;
Expand All @@ -2751,7 +2751,7 @@ export function FormatOffsetTimeZoneIdentifier(offsetNanoseconds) {

export function FormatDateTimeUTCOffsetRounded(offsetNanoseconds) {
offsetNanoseconds = RoundNumberToIncrement(bigInt(offsetNanoseconds), 60e9, 'halfExpand').toJSNumber();
return FormatOffsetTimeZoneIdentifier(offsetNanoseconds);
return FormatOffsetTimeZoneIdentifier(offsetNanoseconds / 60e9);
}

export function GetUTCEpochNanoseconds(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond) {
Expand Down
14 changes: 7 additions & 7 deletions polyfill/lib/timezone.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ export class TimeZone {
constructor(identifier) {
let stringIdentifier = ES.RequireString(identifier);
const parseResult = ES.ParseTimeZoneIdentifier(identifier);
if (parseResult.offsetNanoseconds !== undefined) {
stringIdentifier = ES.FormatOffsetTimeZoneIdentifier(parseResult.offsetNanoseconds);
if (parseResult.offsetMinutes !== undefined) {
stringIdentifier = ES.FormatOffsetTimeZoneIdentifier(parseResult.offsetMinutes);
} else {
const record = ES.GetAvailableNamedTimeZoneIdentifier(stringIdentifier);
if (!record) throw new RangeError(`Invalid time zone identifier: ${stringIdentifier}`);
Expand All @@ -51,8 +51,8 @@ export class TimeZone {
instant = ES.ToTemporalInstant(instant);
const id = GetSlot(this, TIMEZONE_ID);

const offsetNanoseconds = ES.ParseTimeZoneIdentifier(id).offsetNanoseconds;
if (offsetNanoseconds !== undefined) return offsetNanoseconds;
const offsetMinutes = ES.ParseTimeZoneIdentifier(id).offsetMinutes;
if (offsetMinutes !== undefined) return offsetMinutes * 60e9;

return ES.GetNamedTimeZoneOffsetNanoseconds(id, GetSlot(instant, EPOCHNANOSECONDS));
}
Expand Down Expand Up @@ -80,8 +80,8 @@ export class TimeZone {
const Instant = GetIntrinsic('%Temporal.Instant%');
const id = GetSlot(this, TIMEZONE_ID);

const offsetNanoseconds = ES.ParseTimeZoneIdentifier(id).offsetNanoseconds;
if (offsetNanoseconds !== undefined) {
const offsetMinutes = ES.ParseTimeZoneIdentifier(id).offsetMinutes;
if (offsetMinutes !== undefined) {
const epochNs = ES.GetUTCEpochNanoseconds(
GetSlot(dateTime, ISO_YEAR),
GetSlot(dateTime, ISO_MONTH),
Expand All @@ -94,7 +94,7 @@ export class TimeZone {
GetSlot(dateTime, ISO_NANOSECOND)
);
if (epochNs === null) throw new RangeError('DateTime outside of supported range');
return [new Instant(epochNs.minus(offsetNanoseconds))];
return [new Instant(epochNs.minus(offsetMinutes * 60e9))];
}

const possibleEpochNs = ES.GetNamedTimeZoneEpochNanoseconds(
Expand Down
6 changes: 3 additions & 3 deletions spec/abstractops.html
Original file line number Diff line number Diff line change
Expand Up @@ -725,9 +725,9 @@ <h1>
<dl class="header">
<dt>description</dt>
<dd>
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.
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.
</dd>
</dl>
<emu-alg>
Expand Down
2 changes: 1 addition & 1 deletion spec/intl.html
Original file line number Diff line number Diff line change
Expand Up @@ -2543,7 +2543,7 @@ <h1>Temporal.ZonedDateTime.prototype.toLocaleString ( [ _locales_ [ , _options_
1. Let _dateTimeFormat_ be ! OrdinaryCreateFromConstructor(%DateTimeFormat%, %DateTimeFormat.prototype%, « [[InitializedDateTimeFormat]], [[Locale]], [[Calendar]], [[NumberingSystem]], [[TimeZone]], [[Weekday]], [[Era]], [[Year]], [[Month]], [[Day]], [[DayPeriod]], [[Hour]], [[Minute]], [[Second]], [[FractionalSecondDigits]], [[TimeZoneName]], [[HourCycle]], [[Pattern]], [[BoundFormat]] »).
1. Let _timeZone_ be ? ToTemporalTimeZoneIdentifier(_zonedDateTime_.[[TimeZone]]).
1. Let _timeZoneParseResult_ be ? ParseTimeZoneIdentifier(_timeZone_).
1. If _timeZoneParseResult_.[[OffsetNanoseconds]] is not ~empty~, throw a *RangeError* exception.
1. If _timeZoneParseResult_.[[OffsetMinutes]] is not ~empty~, throw a *RangeError* exception.
1. Let _timeZoneIdentifierRecord_ be GetAvailableNamedTimeZoneIdentifier(_timeZone_).
1. If _timeZoneIdentifierRecord_ is ~empty~, throw a *RangeError* exception.
1. Set _timeZone_ to _timeZoneIdentifierRecord_.[[PrimaryIdentifier]].
Expand Down
20 changes: 11 additions & 9 deletions spec/mainadditions.html
Original file line number Diff line number Diff line change
Expand Up @@ -325,8 +325,8 @@ <h1>
<emu-alg>
1. Let _systemTimeZoneIdentifier_ be SystemTimeZoneIdentifier().
1. <ins>Let _parseResult_ be ! ParseTimeZoneIdentifier(_systemTimeZoneIdentifier_).</ins>
1. If <del>IsTimeZoneOffsetString(_systemTimeZoneIdentifier_) is *true*</del><ins>_parseResult_.[[OffsetNanoseconds]] is not ~empty~</ins>, then
1. Let _offsetNs_ be <del>ParseTimeZoneOffsetString(_systemTimeZoneIdentifier_)</del><ins>_parseResult_.[[OffsetNanoseconds]]</ins>.
1. If <del>IsTimeZoneOffsetString(_systemTimeZoneIdentifier_) is *true*</del><ins>_parseResult_.[[OffsetMinutes]] is not ~empty~</ins>, then
1. Let _offsetNs_ be <del>ParseTimeZoneOffsetString(_systemTimeZoneIdentifier_)</del><ins>_parseResult_.[[OffsetMinutes]] × (60 × 10<sup>9</sup>)</ins>.
1. Else,
1. Let _offsetNs_ be GetNamedTimeZoneOffsetNanoseconds(_systemTimeZoneIdentifier_, ℤ(ℝ(_t_) × 10<sup>6</sup>)).
1. Let _offsetMs_ be truncate(_offsetNs_ / 10<sup>6</sup>).
Expand Down Expand Up @@ -360,8 +360,8 @@ <h1>
<emu-alg>
1. Let _systemTimeZoneIdentifier_ be SystemTimeZoneIdentifier().
1. <ins>Let _parseResult_ be ! ParseTimeZoneIdentifier(_systemTimeZoneIdentifier_).</ins>
1. If <del>IsTimeZoneOffsetString(_systemTimeZoneIdentifier_) is *true*</del><ins>_parseResult_.[[OffsetNanoseconds]] is not ~empty~</ins>, then
1. Let _offsetNs_ be <del>ParseTimeZoneOffsetString(_systemTimeZoneIdentifier_)</del><ins>_parseResult_.[[OffsetNanoseconds]]</ins>.
1. If <del>IsTimeZoneOffsetString(_systemTimeZoneIdentifier_) is *true*</del><ins>_parseResult_.[[OffsetMinutes]] is not ~empty~</ins>, then
1. Let _offsetNs_ be <del>ParseTimeZoneOffsetString(_systemTimeZoneIdentifier_)</del><ins>_parseResult_.[[OffsetMinutes]] × (60 × 10<sup>9</sup>)</ins>.
1. Else,
1. Let _possibleInstants_ be GetNamedTimeZoneEpochNanoseconds(_systemTimeZoneIdentifier_, ℝ(YearFromTime(_t_)), ℝ(MonthFromTime(_t_)) + 1, ℝ(DateFromTime(_t_)), ℝ(HourFromTime(_t_)), ℝ(MinFromTime(_t_)), ℝ(SecFromTime(_t_)), ℝ(msFromTime(_t_)), 0, 0).
1. NOTE: The following steps ensure that when _t_ represents local time repeating multiple times at a negative time zone transition (e.g. when the daylight saving time ends or the time zone offset is decreased due to a time zone rule change) or skipped local time at a positive time zone transition (e.g. when the daylight saving time starts or the time zone offset is increased due to a time zone rule change), _t_ is interpreted using the time zone offset before the transition.
Expand Down Expand Up @@ -429,11 +429,13 @@ <h1>
</dl>
<emu-alg>
1. Let _systemTimeZoneIdentifier_ be SystemTimeZoneIdentifier().
1. <ins>Let _parseResult_ be ! ParseTimeZoneIdentifier(_systemTimeZoneIdentifier_).</ins>
1. If <del>IsTimeZoneOffsetString(_systemTimeZoneIdentifier_) is *true*</del><ins>_parseResult_.[[OffsetNanoseconds]] is not ~empty~</ins>, then
1. Let _offsetNs_ be <del>ParseTimeZoneOffsetString(_systemTimeZoneIdentifier_)</del><ins>_parseResult_.[[OffsetNanoseconds]]</ins>.
1. Else,
1. <del>If IsTimeZoneOffsetString(_systemTimeZoneIdentifier_) is *true*, then</del>
1. <del>Let _offsetNs_ be ParseTimeZoneOffsetString(_systemTimeZoneIdentifier_).</del>
1. <del>Else,</del>
1. <ins>Let _offsetMinutes_ be ! ParseTimeZoneIdentifier(_systemTimeZoneIdentifier_).[[OffsetMinutes]].</ins>
1. <ins>If _offsetMinutes_ is ~empty~, then</ins>
1. Let _offsetNs_ be GetNamedTimeZoneOffsetNanoseconds(_systemTimeZoneIdentifier_, ℤ(ℝ(_tv_) × 10<sup>6</sup>)).
1. <ins>Set _offsetMinutes_ to truncate(_offsetNs_ / (60 × 10<sup>9</sup>)).</ins>
1. <del>Let _offset_ be 𝔽(truncate(_offsetNs_ / 10<sup>6</sup>)).</del>
1. <del>If _offset_ is *+0*<sub>𝔽</sub> or _offset_ > *+0*<sub>𝔽</sub>, then</del>
1. <del>Let _offsetSign_ be *"+"*.</del>
Expand All @@ -443,7 +445,7 @@ <h1>
1. <del>Let _absOffset_ be -_offset_.</del>
1. <del>Let _offsetMin_ be ToZeroPaddedDecimalString(ℝ(MinFromTime(_absOffset_)), 2).</del>
1. <del>Let _offsetHour_ be ToZeroPaddedDecimalString(ℝ(HourFromTime(_absOffset_)), 2).</del>
1. <ins>Let _offsetString_ be FormatOffsetTimeZoneIdentifier(_offsetNs_, ~legacy~).</ins>
1. <ins>Let _offsetString_ be FormatOffsetTimeZoneIdentifier(_offsetMinutes_, ~legacy~).</ins>
1. Let _tzName_ be an implementation-defined string that is either the empty String or the string-concatenation of the code unit 0x0020 (SPACE), the code unit 0x0028 (LEFT PARENTHESIS), an implementation-defined timezone name, and the code unit 0x0029 (RIGHT PARENTHESIS).
1. <del>Return the string-concatenation of _offsetSign_, _offsetHour_, _offsetMin_, and _tzName_.</del>
1. <ins>Return the string-concatenation of _offsetString_ and _tzName_.</ins>
Expand Down
Loading

0 comments on commit 4ed1f37

Please sign in to comment.