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