diff --git a/polyfill/lib/ecmascript.mjs b/polyfill/lib/ecmascript.mjs index 14f3355ba1..88624f2d30 100644 --- a/polyfill/lib/ecmascript.mjs +++ b/polyfill/lib/ecmascript.mjs @@ -1,6 +1,7 @@ /* global __debug__ */ const ArrayIncludes = Array.prototype.includes; +const ArrayPrototypeMap = Array.prototype.map; const ArrayPrototypePush = Array.prototype.push; const ArrayPrototypeSort = Array.prototype.sort; const ArrayPrototypeFind = Array.prototype.find; @@ -2422,6 +2423,10 @@ export function DisambiguatePossibleInstants(possibleInstants, timeZoneRec, date const offsetBefore = GetOffsetNanosecondsFor(timeZoneRec, dayBefore); const offsetAfter = GetOffsetNanosecondsFor(timeZoneRec, dayAfter); const nanoseconds = offsetAfter - offsetBefore; + if (MathAbs(nanoseconds) > DAY_NANOS) { + throw new RangeError('bad return from getOffsetNanosecondsFor: UTC offset shift longer than 24 hours'); + } + switch (disambiguation) { case 'earlier': { const norm = TimeDuration.normalize(0, 0, 0, 0, 0, -nanoseconds); @@ -2479,6 +2484,17 @@ export function GetPossibleInstantsFor(timeZoneRec, dateTime) { } Call(ArrayPrototypePush, result, [instant]); } + + const numResults = result.length; + if (numResults > 1) { + const mapped = Call(ArrayPrototypeMap, result, [(i) => GetSlot(i, EPOCHNANOSECONDS)]); + const min = bigInt.min(...mapped); + const max = bigInt.max(...mapped); + if (bigInt(max).subtract(min).abs().greater(DAY_NANOS)) { + throw new RangeError('bad return from getPossibleInstantsFor: UTC offset shift longer than 24 hours'); + } + } + return result; } @@ -3260,20 +3276,34 @@ export function NormalizedTimeDurationToDays(norm, zonedRelativeTo, timeZoneRec, // back inside the period where it belongs. Note that this case only can // happen for positive durations because the only direction that // `disambiguation: 'compatible'` can change clock time is forwards. - if (sign === 1) { - while (days > 0 && relativeResult.epochNs.greater(endNs)) { - days--; - relativeResult = AddDaysToZonedDateTime(start, dtStart, timeZoneRec, calendar, days); - // may do disambiguation + if (sign === 1 && days > 0 && relativeResult.epochNs.greater(endNs)) { + days--; + relativeResult = AddDaysToZonedDateTime(start, dtStart, timeZoneRec, calendar, days); + // may do disambiguation + if (days > 0 && relativeResult.epochNs.greater(endNs)) { + throw new RangeError('inconsistent result from custom time zone getInstantFor()'); } } norm = TimeDuration.fromEpochNsDiff(endNs, relativeResult.epochNs); - let isOverflow = false; - let dayLengthNs; - do { - // calculate length of the next day (day that contains the time remainder) - const oneDayFarther = AddDaysToZonedDateTime( + // calculate length of the next day (day that contains the time remainder) + let oneDayFarther = AddDaysToZonedDateTime( + relativeResult.instant, + relativeResult.dateTime, + timeZoneRec, + calendar, + sign + ); + let dayLengthNs = TimeDuration.fromEpochNsDiff(oneDayFarther.epochNs, relativeResult.epochNs); + const oneDayLess = norm.subtract(dayLengthNs); + let isOverflow = oneDayLess.sign() * sign >= 0; + if (isOverflow) { + norm = oneDayLess; + relativeResult = oneDayFarther; + days += sign; + + // ensure there was no more overflow + oneDayFarther = AddDaysToZonedDateTime( relativeResult.instant, relativeResult.dateTime, timeZoneRec, @@ -3282,14 +3312,9 @@ export function NormalizedTimeDurationToDays(norm, zonedRelativeTo, timeZoneRec, ); dayLengthNs = TimeDuration.fromEpochNsDiff(oneDayFarther.epochNs, relativeResult.epochNs); - const oneDayLess = norm.subtract(dayLengthNs); - isOverflow = oneDayLess.sign() * sign >= 0; - if (isOverflow) { - norm = oneDayLess; - relativeResult = oneDayFarther; - days += sign; - } - } while (isOverflow); + isOverflow = norm.subtract(dayLengthNs).sign() * sign >= 0; + if (isOverflow) throw new RangeError('inconsistent result from custom time zone getPossibleInstantsFor()'); + } if (days !== 0 && MathSign(days) != sign) { throw new RangeError('Time zone or calendar converted nanoseconds into a number of days with the opposite sign'); } diff --git a/spec/timezone.html b/spec/timezone.html index 0ca52593d1..432a252e8d 100644 --- a/spec/timezone.html +++ b/spec/timezone.html @@ -937,6 +937,7 @@

1. Let _offsetBefore_ be ? GetOffsetNanosecondsFor(_timeZoneRec_, _dayBefore_). 1. Let _offsetAfter_ be ? GetOffsetNanosecondsFor(_timeZoneRec_, _dayAfter_). 1. Let _nanoseconds_ be _offsetAfter_ - _offsetBefore_. + 1. If abs(_nanoseconds_) > nsPerDay, throw a *RangeError* exception. 1. If _disambiguation_ is *"earlier"*, then 1. Let _norm_ be NormalizeTimeDuration(0, 0, 0, 0, 0, -_nanoseconds_). 1. Let _earlierTime_ be AddTime(_dateTime_.[[ISOHour]], _dateTime_.[[ISOMinute]], _dateTime_.[[ISOSecond]], _dateTime_.[[ISOMillisecond]], _dateTime_.[[ISOMicrosecond]], _dateTime_.[[ISONanosecond]], _norm_). @@ -978,6 +979,14 @@

1. Repeat, 1. Let _value_ be ? IteratorStepValue(_iteratorRecord_). 1. If _value_ is ~done~, then + 1. Let _numResults_ be _list_'s length. + 1. If _numResults_ > 1, then + 1. Let _epochNs_ be a new empty List. + 1. For each value _instant_ in _list_, do + 1. Append _instant_.[[EpochNanoseconds]] to the end of the List _epochNs_. + 1. Let _min_ be the least element of the List _epochNs_. + 1. Let _max_ be the greatest element of the List _epochNs_. + 1. If abs(ℝ(_max_ - _min_)) > nsPerDay, throw a *RangeError* exception. 1. Return _list_. 1. If _value_ is not an Object or _value_ does not have an [[InitializedTemporalInstant]] internal slot, then 1. Let _completion_ be ThrowCompletion(a newly created *TypeError* object). diff --git a/spec/zoneddatetime.html b/spec/zoneddatetime.html index ba333b1703..edbad32b6b 100644 --- a/spec/zoneddatetime.html +++ b/spec/zoneddatetime.html @@ -1444,23 +1444,22 @@

1. Else if _days_ < 0 and _timeSign_ < 0, then 1. Set _days_ to _days_ + 1. 1. Let _relativeResult_ be ? AddDaysToZonedDateTime(_startInstant_, _startDateTime_, _timeZoneRec_, _zonedRelativeTo_.[[Calendar]], _days_). - 1. If _sign_ is 1, then - 1. Repeat, while _days_ > 0 and ℝ(_relativeResult_.[[EpochNanoseconds]]) > _endNs_, - 1. Set _days_ to _days_ - 1. - 1. Set _relativeResult_ to ? AddDaysToZonedDateTime(_startInstant_, _startDateTime_, _timeZoneRec_, _zonedRelativeTo_.[[Calendar]], _days_). + 1. If _sign_ = 1, and _days_ > 0, and ℝ(_relativeResult_.[[EpochNanoseconds]]) > _endNs_, then + 1. Set _days_ to _days_ - 1. + 1. Set _relativeResult_ to ? AddDaysToZonedDateTime(_startInstant_, _startDateTime_, _timeZoneRec_, _zonedRelativeTo_.[[Calendar]], _days_). + 1. If _days_ > 0 and ℝ(_relativeResult_.[[EpochNanoseconds]]) > _endNs_, throw a *RangeError* exception. 1. Set _norm_ to NormalizedTimeDurationFromEpochNanosecondsDifference(_endNs_, _relativeResult_.[[EpochNanoseconds]]). - 1. Let _done_ be *false*. - 1. Let _dayLengthNs_ be ~unset~. - 1. Repeat, while _done_ is *false*, - 1. Let _oneDayFarther_ be ? AddDaysToZonedDateTime(_relativeResult_.[[Instant]], _relativeResult_.[[DateTime]], _timeZoneRec_, _zonedRelativeTo_.[[Calendar]], _sign_). - 1. Set _dayLengthNs_ to NormalizedTimeDurationFromEpochNanosecondsDifference(_oneDayFarther_.[[EpochNanoseconds]], _relativeResult_.[[EpochNanoseconds]]). - 1. Let _oneDayLess_ be ! SubtractNormalizedTimeDuration(_norm_, _dayLengthNs_). - 1. If NormalizedTimeDurationSign(_oneDayLess_) × _sign_ ≥ 0, then - 1. Set _norm_ to _oneDayLess_. - 1. Set _relativeResult_ to _oneDayFarther_. - 1. Set _days_ to _days_ + _sign_. - 1. Else, - 1. Set _done_ to *true*. + 1. Let _oneDayFarther_ be ? AddDaysToZonedDateTime(_relativeResult_.[[Instant]], _relativeResult_.[[DateTime]], _timeZoneRec_, _zonedRelativeTo_.[[Calendar]], _sign_). + 1. Let _dayLengthNs_ be NormalizedTimeDurationFromEpochNanosecondsDifference(_oneDayFarther.[[EpochNanoseconds]], _relativeResult_.[[EpochNanoseconds]]). + 1. Let _oneDayLess_ be ! SubtractNormalizedTimeDuration(_norm_, _dayLengthNs_). + 1. If NormalizedTimeDurationSign(_oneDayLess_) × _sign_ ≥ 0, then + 1. Set _norm_ to _oneDayLess_. + 1. Set _relativeResult_ to _oneDayFarther_. + 1. Set _days_ to _days_ + _sign_. + 1. Set _oneDayFarther_ to ? AddDaysToZonedDateTime(_relativeResult_.[[Instant]], _relativeResult_.[[DateTime]], _timeZoneRec_, _zonedRelativeTo_.[[Calendar]], _sign_). + 1. Set _dayLengthNs_ to NormalizedTimeDurationFromEpochNanosecondsDifference(_oneDayFarther.[[EpochNanoseconds]], _relativeResult_.[[EpochNanoseconds]]). + 1. If NormalizedTimeDurationSign(? SubtractNormalizedTimeDuration(_norm_, _dayLengthNs_)) × _sign_ ≥ 0, then + 1. Throw a *RangeError* exception. 1. If _days_ < 0 and _sign_ = 1, throw a *RangeError* exception. 1. If _days_ > 0 and _sign_ = -1, throw a *RangeError* exception. 1. If NormalizedTimeDurationSign(_norm_) = -1, then