Skip to content

Commit

Permalink
Editorial: Use ISO Date-Time Record in GetNamedTimeZoneEpochNanoseconds
Browse files Browse the repository at this point in the history
Instead of passing each component individually.
GetNamedTimeZoneEpochNanoseconds is part of ECMA-262 already, so this
requires some <ins> and <del> tags.

See: #2949
  • Loading branch information
ptomato authored and Ms2ger committed Oct 8, 2024
1 parent e948cc8 commit 8e57e86
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 59 deletions.
71 changes: 16 additions & 55 deletions polyfill/lib/ecmascript.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2049,22 +2049,18 @@ export function DisambiguatePossibleEpochNanoseconds(possibleEpochNs, timeZone,
}

export function GetPossibleEpochNanoseconds(timeZone, isoDateTime) {
const {
isoDate: { year, month, day },
time: { hour, minute, second, millisecond, microsecond, nanosecond }
} = isoDateTime;
const offsetMinutes = ParseTimeZoneIdentifier(timeZone).offsetMinutes;
if (offsetMinutes !== undefined) {
const balanced = BalanceISODateTime(
year,
month,
day,
hour,
minute - offsetMinutes,
second,
millisecond,
microsecond,
nanosecond
isoDateTime.isoDate.year,
isoDateTime.isoDate.month,
isoDateTime.isoDate.day,
isoDateTime.time.hour,
isoDateTime.time.minute - offsetMinutes,
isoDateTime.time.second,
isoDateTime.time.millisecond,
isoDateTime.time.microsecond,
isoDateTime.time.nanosecond
);
if (MathAbs(ISODateToEpochDays(balanced.isoDate.year, balanced.isoDate.month - 1, balanced.isoDate.day)) > 1e8) {
throw new RangeErrorCtor('date/time value is outside the supported range');
Expand All @@ -2074,21 +2070,12 @@ export function GetPossibleEpochNanoseconds(timeZone, isoDateTime) {
return [epochNs];
}

if (MathAbs(ISODateToEpochDays(year, month - 1, day)) > 1e8) {
if (
MathAbs(ISODateToEpochDays(isoDateTime.isoDate.year, isoDateTime.isoDate.month - 1, isoDateTime.isoDate.day)) > 1e8
) {
throw new RangeErrorCtor('date/time value is outside the supported range');
}
return GetNamedTimeZoneEpochNanoseconds(
timeZone,
year,
month,
day,
hour,
minute,
second,
millisecond,
microsecond,
nanosecond
);
return GetNamedTimeZoneEpochNanoseconds(timeZone, isoDateTime);
}

export function GetStartOfDay(timeZone, isoDate) {
Expand Down Expand Up @@ -2639,24 +2626,10 @@ export function GetFormatterParts(timeZone, epochMilliseconds) {
// be only one match. But for repeated clock times after backwards transitions
// (like when DST ends) there may be two matches. And for skipped clock times
// after forward transitions, there will be no matches.
export function GetNamedTimeZoneEpochNanoseconds(
id,
year,
month,
day,
hour,
minute,
second,
millisecond,
microsecond,
nanosecond
) {
function GetNamedTimeZoneEpochNanoseconds(id, isoDateTime) {
// Get the offset of one day before and after the requested calendar date and
// clock time, avoiding overflows if near the edge of the Instant range.
let ns = GetUTCEpochNanoseconds({
isoDate: { year, month, day },
time: { hour, minute, second, millisecond, microsecond, nanosecond }
});
let ns = GetUTCEpochNanoseconds(isoDateTime);
let nsEarlier = ns.minus(DAY_NANOS);
if (nsEarlier.lesser(NS_MIN)) nsEarlier = ns;
let nsLater = ns.plus(DAY_NANOS);
Expand All @@ -2674,19 +2647,7 @@ export function GetNamedTimeZoneEpochNanoseconds(
(offsetNanoseconds) => {
const epochNanoseconds = bigInt(ns).minus(offsetNanoseconds);
const parts = GetNamedTimeZoneDateTimeParts(id, epochNanoseconds);
if (
year !== parts.isoDate.year ||
month !== parts.isoDate.month ||
day !== parts.isoDate.day ||
hour !== parts.time.hour ||
minute !== parts.time.minute ||
second !== parts.time.second ||
millisecond !== parts.time.millisecond ||
microsecond !== parts.time.microsecond ||
nanosecond !== parts.time.nanosecond
) {
return undefined;
}
if (CompareISODateTime(isoDateTime, parts) !== 0) return undefined;
ValidateEpochNanoseconds(epochNanoseconds);
return epochNanoseconds;
}
Expand Down
45 changes: 43 additions & 2 deletions spec/mainadditions.html
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,46 @@ <h1>Time Zone Identifiers</h1>
</p>
</emu-clause>

<emu-clause id="sec-getnamedtimezoneepochnanoseconds" type="implementation-defined abstract operation">
<h1>
GetNamedTimeZoneEpochNanoseconds (
_timeZoneIdentifier_: a String,
<del>_year_: an integer,</del>
<del>_month_: an integer in the inclusive interval from 1 to 12,</del>
<del>_day_: an integer in the inclusive interval from 1 to 31,</del>
<del>_hour_: an integer in the inclusive interval from 0 to 23,</del>
<del>_minute_: an integer in the inclusive interval from 0 to 59,</del>
<del>_second_: an integer in the inclusive interval from 0 to 59,</del>
<del>_millisecond_: an integer in the inclusive interval from 0 to 999,</del>
<del>_microsecond_: an integer in the inclusive interval from 0 to 999,</del>
<del>_nanosecond_: an integer in the inclusive interval from 0 to 999,</del>
<ins>_isoDateTime_: an ISO Date-Time Record,</ins>
): a List of BigInts
</h1>
<dl class="header">
<dt>description</dt>
<dd>
Each value in the returned List represents a number of nanoseconds since the epoch that corresponds to the given ISO 8601 calendar date and wall-clock time in the named time zone identified by _timeZoneIdentifier_.
</dd>
</dl>
<p>
When the input represents a local time occurring more than once because of a negative time zone transition (e.g. when daylight saving time ends or the time zone offset is decreased due to a time zone rule change), the returned List will have more than one element and will be sorted by ascending numerical value.
When the input represents a local time skipped because of a positive time zone transition (e.g. when daylight saving time begins or the time zone offset is increased due to a time zone rule change), the returned List will be empty.
Otherwise, the returned List will have one element.
</p>
<p>The default implementation of GetNamedTimeZoneEpochNanoseconds, to be used for ECMAScript implementations that do not include local political rules for any time zones, performs the following steps when called:</p>
<emu-alg>
1. Assert: _timeZoneIdentifier_ is *"UTC"*.
1. Let _epochNanoseconds_ be GetUTCEpochNanoseconds(<del>_year_, _month_, _day_, _hour_, _minute_, _second_, _millisecond_, _microsecond_, _nanosecond_</del><ins>_isoDateTime_</ins>).
1. Return « _epochNanoseconds_ ».
</emu-alg>
<emu-note>
<p>It is required for time zone aware implementations (and recommended for all others) to use the time zone information of the IANA Time Zone Database <a href="https://www.iana.org/time-zones/">https://www.iana.org/time-zones/</a>.</p>
<p>1:30 AM on 5 November 2017 in America/New_York is repeated twice, so GetNamedTimeZoneEpochNanoseconds<del>(*"America/New_York"*, 2017, 11, 5, 1, 30, 0, 0, 0, 0)</del> <ins>for that time zone and ISO date-time</ins> would return a List of length 2 in which the first element represents 05:30 UTC (corresponding with 01:30 US Eastern Daylight Time at UTC offset -04:00) and the second element represents 06:30 UTC (corresponding with 01:30 US Eastern Standard Time at UTC offset -05:00).</p>
<p>2:30 AM on 12 March 2017 in America/New_York does not exist, so GetNamedTimeZoneEpochNanoseconds<del>(*"America/New_York"*, 2017, 3, 12, 2, 30, 0, 0, 0, 0)</del> <ins>for that time zone and ISO date-time</ins> would return an empty List.</p>
</emu-note>
</emu-clause>

<emu-clause id="sec-systemtimezoneidentifier" oldids="sec-defaulttimezone" type="implementation-defined abstract operation">
<h1>SystemTimeZoneIdentifier ( ): an available time zone identifier</h1>
<dl class="header">
Expand Down Expand Up @@ -475,13 +515,14 @@ <h1>
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. <ins>Let _isoDateTime_ be TimeValueToISODateTimeRecord(_t_).</ins>
1. Let _possibleInstants_ be GetNamedTimeZoneEpochNanoseconds(_systemTimeZoneIdentifier_, <del>ℝ(YearFromTime(_t_)), ℝ(MonthFromTime(_t_)) + 1, ℝ(DateFromTime(_t_)), ℝ(HourFromTime(_t_)), ℝ(MinFromTime(_t_)), ℝ(SecFromTime(_t_)), ℝ(msFromTime(_t_)), 0, 0</del><ins>_isoDateTime_</ins>).
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.
1. If _possibleInstants_ is not empty, then
1. Let _disambiguatedInstant_ be _possibleInstants_[0].
1. Else,
1. NOTE: _t_ represents a local time skipped at a positive time zone transition (e.g. due to daylight saving time starting or a time zone rule change increasing the UTC offset).
1. [declared="tBefore"] Let _possibleInstantsBefore_ be GetNamedTimeZoneEpochNanoseconds(_systemTimeZoneIdentifier_, ℝ(YearFromTime(_tBefore_)), ℝ(MonthFromTime(_tBefore_)) + 1, ℝ(DateFromTime(_tBefore_)), ℝ(HourFromTime(_tBefore_)), ℝ(MinFromTime(_tBefore_)), ℝ(SecFromTime(_tBefore_)), ℝ(msFromTime(_tBefore_)), 0, 0), where _tBefore_ is the largest integral Number &lt; _t_ for which _possibleInstantsBefore_ is not empty (i.e., _tBefore_ represents the last local time before the transition).
1. [declared="tBefore"] Let _possibleInstantsBefore_ be GetNamedTimeZoneEpochNanoseconds(_systemTimeZoneIdentifier_, <del>ℝ(YearFromTime(_tBefore_)), ℝ(MonthFromTime(_tBefore_)) + 1, ℝ(DateFromTime(_tBefore_)), ℝ(HourFromTime(_tBefore_)), ℝ(MinFromTime(_tBefore_)), ℝ(SecFromTime(_tBefore_)), ℝ(msFromTime(_tBefore_)), 0, 0</del><ins>TimeValueToISODateTimeRecord(_tBefore_)</ins>), where _tBefore_ is the largest integral Number &lt; _t_ for which _possibleInstantsBefore_ is not empty (i.e., _tBefore_ represents the last local time before the transition).
1. Let _disambiguatedInstant_ be the last element of _possibleInstantsBefore_.
1. Let _offsetNs_ be GetNamedTimeZoneOffsetNanoseconds(_systemTimeZoneIdentifier_, _disambiguatedInstant_).
1. Let _offsetMs_ be truncate(_offsetNs_ / 10<sup>6</sup>).
Expand Down
19 changes: 19 additions & 0 deletions spec/plaindatetime.html
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,25 @@ <h1>
</emu-alg>
</emu-clause>

<emu-clause id="sec-temporal-timevaluetoisodatetimerecord" type="abstract operation">
<h1>
TimeValueToISODateTimeRecord (
_t_: a finite time value,
): an ISO Date-Time Record
</h1>
<dl class="header">
<dt>description</dt>
<dd>
It converts a time value into an ISO Date-Time Record.
</dd>
</dl>
<emu-alg>
1. Let _isoDate_ be CreateISODateRecord(ℝ(YearFromTime(_t_)), ℝ(MonthFromTime(_t_)) + 1, ℝ(DateFromTime(_t_))).
1. Let _time_ be Time Record { [[Days]]: 0, [[Hour]]: ℝ(HourFromTime(_t_)), [[Minute]]: ℝ(MinFromTime(_t_)), [[Second]]: ℝ(SecFromTime(_t_)), [[Millisecond]]: ℝ(msFromTime(_t_)), [[Microsecond]]: 0, [[Nanosecond]]: 0 }.
1. Return ISO Date-Time Record { [[ISODate]]: _isoDate_, [[Time]]: _time_ }.
</emu-alg>
</emu-clause>

<emu-clause id="sec-temporal-combineisodateandtimerecord" type="abstract operation">
<h1>
CombineISODateAndTimeRecord (
Expand Down
4 changes: 2 additions & 2 deletions spec/timezone.html
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ <h1>
1. Let _possibleEpochNanoseconds_ be « _epochNanoseconds_ ».
1. Else,
1. If abs(ISODateToEpochDays(_isoDateTime_.[[ISODate]].[[Year]], _isoDateTime_.[[ISODate]].[[Month]] - 1, _isoDateTime_.[[ISODate]].[[Day]])) > 10<sup>8</sup>, throw a *RangeError* exception.
1. Let _possibleEpochNanoseconds_ be GetNamedTimeZoneEpochNanoseconds(_parseResult_.[[Name]], _isoDateTime_.[[ISODate]].[[Year]], _isoDateTime_.[[ISODate]].[[Month]], _isoDateTime_.[[ISODate]].[[Day]], _isoDateTime_.[[Time]].[[Hour]], _isoDateTime_.[[Time]].[[Minute]], _isoDateTime_.[[Time]].[[Second]], _isoDateTime_.[[Time]].[[Millisecond]], _isoDateTime_.[[Time]].[[Microsecond]], _isoDateTime_.[[Time]].[[Nanosecond]]).
1. Let _possibleEpochNanoseconds_ be GetNamedTimeZoneEpochNanoseconds(_parseResult_.[[Name]], _isoDateTime_).
1. For each value _epochNanoseconds_ in _possibleEpochNanoseconds_, do
1. If IsValidEpochNanoseconds(_epochNanoseconds_) is *false*, throw a *RangeError* exception.
1. Return _possibleEpochNanoseconds_.
Expand All @@ -386,7 +386,7 @@ <h1>
1. Let _possibleEpochNs_ be ? GetPossibleEpochNanoseconds(_timeZone_, _isoDateTime_).
1. If _possibleEpochNs_ is not empty, return _possibleEpochNs_[0].
1. Assert: IsOffsetTimeZoneIdentifier(_timeZone_) is *false*.
1. [declared="isoDateTimeAfter"] Let _possibleEpochNsAfter_ be GetNamedTimeZoneEpochNanoseconds(_timeZone_, _isoDateTimeAfter_.[[ISODate]].[[Year]], _isoDateTimeAfter_.[[ISODate]].[[Month]], _isoDateTimeAfter_.[[ISODate]].[[Day]], _isoDateTimeAfter_.[[Time]].[[Hour]], _isoDateTimeAfter_.[[Time]].[[Minute]], _isoDateTimeAfter_.[[Time]].[[Second]], _isoDateTimeAfter_.[[Time]].[[Millisecond]], _isoDateTimeAfter_.[[Time]].[[Microsecond]], _isoDateTimeAfter_.[[Time]].[[Nanosecond]]), where _isoDateTimeAfter_ is the ISO Date-Time Record for which ! DifferenceISODateTime(_isoDateTime_, _isoDateTimeAfter_, *"iso8601"*, ~hour~).[[Norm]] is the smallest possible value > 0 for which _possibleEpochNsAfter_ is not empty (i.e., _isoDateTimeAfter_ represents the first local time after the transition).
1. [declared="isoDateTimeAfter"] Let _possibleEpochNsAfter_ be GetNamedTimeZoneEpochNanoseconds(_timeZone_, _isoDateTimeAfter_), where _isoDateTimeAfter_ is the ISO Date-Time Record for which ! DifferenceISODateTime(_isoDateTime_, _isoDateTimeAfter_, *"iso8601"*, ~hour~).[[Norm]] is the smallest possible value > 0 for which _possibleEpochNsAfter_ is not empty (i.e., _isoDateTimeAfter_ represents the first local time after the transition).
1. Assert: _possibleEpochNsAfter_'s length = 1.
1. Return _possibleEpochNsAfter_[0].
</emu-alg>
Expand Down

0 comments on commit 8e57e86

Please sign in to comment.