Skip to content

Commit

Permalink
Editorial: Adjust RangeError condition in InterpretISODateTimeOffset
Browse files Browse the repository at this point in the history
Similarly to the previous commit, we must avoid calling
GetUTCEpochNanoseconds with dates that are too large because it is
ill-defined in ECMA-262 what is supposed to happen. (See
tc39/ecma262#1087). Previously to PR #2925, that
could not happen because we used a PlainDateTime object in
InterpretISODateTimeOffset, but now we use an ISO Date-Time Record.

Note that this is editorial, because IsValidEpochNanoseconds would throw
anyway in this case even if GetUTCEpochNanoseconds was fully defined for
large inputs.
  • Loading branch information
ptomato committed Oct 1, 2024
1 parent fb6c830 commit a3cff9d
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 12 deletions.
47 changes: 37 additions & 10 deletions polyfill/lib/ecmascript.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1541,6 +1541,43 @@ export function InterpretISODateTimeOffset(
return GetEpochNanosecondsFor(timeZone, dt, disambiguation);
}

// The caller wants the offset to always win ('use') OR the caller is OK
// with the offset winning ('prefer' or 'reject') as long as it's valid
// for this timezone and date/time.
if (offsetBehaviour === 'exact' || offsetOpt === 'use') {
// Calculate the instant for the input's date/time and offset
const balanced = BalanceISODateTime(
year,
month,
day,
time.hour,
time.minute,
time.second,
time.millisecond,
time.microsecond,
time.nanosecond - offsetNs
);
if (MathAbs(ISODateToEpochDays(balanced.year, balanced.month - 1, balanced.day)) > 1e8) {
throw new RangeErrorCtor('date/time outside of supported range');
}
const epochNs = GetUTCEpochNanoseconds(
balanced.year,
balanced.month,
balanced.day,
balanced.hour,
balanced.minute,
balanced.second,
balanced.millisecond,
balanced.microsecond,
balanced.nanosecond
);
ValidateEpochNanoseconds(epochNs);
return epochNs;
}

if (MathAbs(ISODateToEpochDays(year, month - 1, day)) > 1e8) {
throw new RangeErrorCtor('date/time outside of supported range');
}
const utcEpochNs = GetUTCEpochNanoseconds(
year,
month,
Expand All @@ -1553,16 +1590,6 @@ export function InterpretISODateTimeOffset(
time.nanosecond
);

// The caller wants the offset to always win ('use') OR the caller is OK
// with the offset winning ('prefer' or 'reject') as long as it's valid
// for this timezone and date/time.
if (offsetBehaviour === 'exact' || offsetOpt === 'use') {
// Calculate the instant for the input's date/time and offset
const epochNs = utcEpochNs.subtract(offsetNs);
ValidateEpochNanoseconds(epochNs);
return epochNs;
}

// "prefer" or "reject"
const possibleEpochNs = GetPossibleEpochNanoseconds(timeZone, dt);
for (let index = 0; index < possibleEpochNs.length; index++) {
Expand Down
7 changes: 5 additions & 2 deletions spec/zoneddatetime.html
Original file line number Diff line number Diff line change
Expand Up @@ -921,13 +921,16 @@ <h1>
1. Let _isoDateTime_ be CombineISODateAndTimeRecord(_isoDate_, _time_).
1. If _offsetBehaviour_ is ~wall~, or _offsetBehaviour_ is ~option~ and _offsetOption_ is ~ignore~, then
1. Return ? GetEpochNanosecondsFor(_timeZone_, _isoDateTime_, _disambiguation_).
1. Let _utcEpochNanoseconds_ be GetUTCEpochNanoseconds(_year_, _month_, _day_, _time_.[[Hour]], _time_.[[Minute]], _time_.[[Second]], _time_.[[Millisecond]], _time_.[[Microsecond]], _time_.[[Nanosecond]]).
1. If _offsetBehaviour_ is ~exact~, or _offsetBehaviour_ is ~option~ and _offsetOption_ is ~use~, then
1. Let _epochNanoseconds_ be _utcEpochNanoseconds_ - ℤ(_offsetNanoseconds_).
1. Let _balanced_ be BalanceISODateTime(_year_, _month_, _day_, _time_.[[Hour]], _time_.[[Minute]], _time_.[[Second]], _time_.[[Millisecond]], _time_.[[Microsecond]], _time_.[[Nanosecond]] - _offsetNanoseconds_).
1. If abs(ISODateToEpochDays(_balanced_.[[Year]], _balanced_.[[Month]] - 1, _balanced_.[[Day]])) > 10<sup>8</sup>, throw a *RangeError* exception.
1. Let _epochNanoseconds_ be GetUTCEpochNanoseconds(_balanced_.[[Year]], _balanced_.[[Month]], _balanced_.[[Day]], _balanced_.[[Hour]], _balanced_.[[Minute]], _balanced_.[[Second]], _balanced_.[[Millisecond]], _balanced_.[[Microsecond]], _balanced_.[[Nanosecond]]).
1. If IsValidEpochNanoseconds(_epochNanoseconds_) is *false*, throw a *RangeError* exception.
1. Return _epochNanoseconds_.
1. Assert: _offsetBehaviour_ is ~option~.
1. Assert: _offsetOption_ is ~prefer~ or ~reject~.
1. If abs(ISODateToEpochDays(_year_, _month_ - 1, _day_)) > 10<sup>8</sup>, throw a *RangeError* exception.
1. Let _utcEpochNanoseconds_ be GetUTCEpochNanoseconds(_year_, _month_, _day_, _time_.[[Hour]], _time_.[[Minute]], _time_.[[Second]], _time_.[[Millisecond]], _time_.[[Microsecond]], _time_.[[Nanosecond]]).
1. Let _possibleEpochNs_ be ? GetPossibleEpochNanoseconds(_timeZone_, _isoDateTime_).
1. For each element _candidate_ of _possibleEpochNs_, do
1. Let _candidateOffset_ be _utcEpochNanoseconds_ - _candidate_.
Expand Down

0 comments on commit a3cff9d

Please sign in to comment.