From 411418150dd2c1820d5660cba1ff8fca7eafe96a Mon Sep 17 00:00:00 2001
From: Philip Chimento
Date: Wed, 8 Mar 2023 16:11:18 -0800
Subject: [PATCH 01/10] Normative: Avoid calendar operations when adding
days-only duration to ZDT
We call AddZonedDateTime in several places with all of the duration
components being zero except for days. In this case, we can skip the
calendar operations altogether, because adding/subtracting days to/from
the ISO fields is not calendar-dependent.
---
polyfill/lib/ecmascript.mjs | 96 ++++++++++++++++++++--------------
polyfill/lib/zoneddatetime.mjs | 4 +-
spec/duration.html | 4 +-
spec/zoneddatetime.html | 54 ++++++++++++++++---
4 files changed, 108 insertions(+), 50 deletions(-)
diff --git a/polyfill/lib/ecmascript.mjs b/polyfill/lib/ecmascript.mjs
index 0c0feaf285..f5dd9ed389 100644
--- a/polyfill/lib/ecmascript.mjs
+++ b/polyfill/lib/ecmascript.mjs
@@ -3327,7 +3327,7 @@ export function NanosecondsToDays(nanoseconds, zonedRelativeTo) {
'day',
ObjectCreate(null)
);
- let intermediateNs = AddZonedDateTime(start, timeZone, calendar, 0, 0, 0, days, 0, 0, 0, 0, 0, 0, dtStart);
+ let relativeResult = AddDaysToZonedDateTime(start, dtStart, timeZone, calendar, days);
// may disambiguate
// If clock time after addition was in the middle of a skipped period, the
@@ -3340,41 +3340,31 @@ export function NanosecondsToDays(nanoseconds, zonedRelativeTo) {
// `disambiguation: 'compatible'` can change clock time is forwards.
days = bigInt(days);
if (sign === 1) {
- while (days.greater(0) && intermediateNs.greater(endNs)) {
+ while (days.greater(0) && relativeResult.epochNs.greater(endNs)) {
days = days.prev();
- intermediateNs = AddZonedDateTime(
- start,
- timeZone,
- calendar,
- 0,
- 0,
- 0,
- days.toJSNumber(),
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- dtStart
- );
+ relativeResult = AddDaysToZonedDateTime(start, dtStart, timeZone, calendar, days.toJSNumber());
// may do disambiguation
}
}
- nanoseconds = endNs.subtract(intermediateNs);
+ nanoseconds = endNs.subtract(relativeResult.epochNs);
let isOverflow = false;
- let relativeInstant = new TemporalInstant(intermediateNs);
let dayLengthNs;
do {
// calculate length of the next day (day that contains the time remainder)
- const oneDayFartherNs = AddZonedDateTime(relativeInstant, timeZone, calendar, 0, 0, 0, sign, 0, 0, 0, 0, 0, 0);
- const relativeNs = GetSlot(relativeInstant, EPOCHNANOSECONDS);
- dayLengthNs = oneDayFartherNs.subtract(relativeNs).toJSNumber();
+ const oneDayFarther = AddDaysToZonedDateTime(
+ relativeResult.instant,
+ relativeResult.dateTime,
+ timeZone,
+ calendar,
+ sign
+ );
+
+ dayLengthNs = oneDayFarther.epochNs.subtract(relativeResult.epochNs).toJSNumber();
isOverflow = nanoseconds.subtract(dayLengthNs).multiply(sign).geq(0);
if (isOverflow) {
nanoseconds = nanoseconds.subtract(dayLengthNs);
- relativeInstant = new TemporalInstant(oneDayFartherNs);
+ relativeResult = oneDayFarther;
days = days.add(sign);
}
} while (isOverflow);
@@ -4978,6 +4968,46 @@ export function AddZonedDateTime(
return AddInstant(GetSlot(instantIntermediate, EPOCHNANOSECONDS), h, min, s, ms, µs, ns);
}
+export function AddDaysToZonedDateTime(instant, dateTime, timeZone, calendar, days) {
+ // Same as AddZonedDateTime above, but an optimized version with fewer
+ // observable calls that only adds a number of days. Returns an object with
+ // all three versions of the ZonedDateTime: epoch nanoseconds, Instant, and
+ // PlainDateTime
+ if (days === 0) {
+ return { instant, dateTime, epochNs: GetSlot(instant, EPOCHNANOSECONDS) };
+ }
+
+ const addedDate = AddISODate(
+ GetSlot(dateTime, ISO_YEAR),
+ GetSlot(dateTime, ISO_MONTH),
+ GetSlot(dateTime, ISO_DAY),
+ 0,
+ 0,
+ 0,
+ days,
+ 'constrain'
+ );
+ const dateTimeResult = CreateTemporalDateTime(
+ addedDate.year,
+ addedDate.month,
+ addedDate.day,
+ GetSlot(dateTime, ISO_HOUR),
+ GetSlot(dateTime, ISO_MINUTE),
+ GetSlot(dateTime, ISO_SECOND),
+ GetSlot(dateTime, ISO_MILLISECOND),
+ GetSlot(dateTime, ISO_MICROSECOND),
+ GetSlot(dateTime, ISO_NANOSECOND),
+ calendar
+ );
+
+ const instantResult = GetInstantFor(timeZone, dateTimeResult, 'compatible');
+ return {
+ instant: instantResult,
+ dateTime: dateTimeResult,
+ epochNs: GetSlot(instantResult, EPOCHNANOSECONDS)
+ };
+}
+
export function AddDurationToOrSubtractDurationFromDuration(operation, duration, other, options) {
const sign = operation === 'subtract' ? -1 : 1;
let { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } =
@@ -5397,21 +5427,9 @@ export function AdjustRoundedDurationDays(
0
);
const TemporalInstant = GetIntrinsic('%Temporal.Instant%');
- const dayEnd = AddZonedDateTime(
- new TemporalInstant(dayStart),
- timeZone,
- calendar,
- 0,
- 0,
- 0,
- direction,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0
- );
+ const dayStartInstant = new TemporalInstant(dayStart);
+ const dayStartDateTime = GetPlainDateTimeFor(timeZone, dayStartInstant, calendar);
+ const dayEnd = AddDaysToZonedDateTime(dayStartInstant, dayStartDateTime, timeZone, calendar, direction).epochNs;
const dayLengthNs = dayEnd.subtract(dayStart);
const oneDayLess = timeRemainderNs.subtract(dayLengthNs);
diff --git a/polyfill/lib/zoneddatetime.mjs b/polyfill/lib/zoneddatetime.mjs
index 2938e68616..80746241aa 100644
--- a/polyfill/lib/zoneddatetime.mjs
+++ b/polyfill/lib/zoneddatetime.mjs
@@ -389,10 +389,10 @@ export class ZonedDateTime {
const calendar = GetSlot(this, CALENDAR);
const dtStart = ES.CreateTemporalDateTime(year, month, day, 0, 0, 0, 0, 0, 0, 'iso8601');
const instantStart = ES.GetInstantFor(timeZone, dtStart, 'compatible');
- const endNs = ES.AddZonedDateTime(instantStart, timeZone, calendar, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, dtStart);
+ const endNs = ES.AddDaysToZonedDateTime(instantStart, dtStart, timeZone, calendar, 1).epochNs;
const dayLengthNs = endNs.subtract(GetSlot(instantStart, EPOCHNANOSECONDS));
if (dayLengthNs.leq(0)) {
- throw new RangeError('cannot round a ZonedDateTime in a calendar with zero- or negative-length days');
+ throw new RangeError('cannot round a ZonedDateTime in a time zone with zero- or negative-length days');
}
({ year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = ES.RoundISODateTime(
year,
diff --git a/spec/duration.html b/spec/duration.html
index 59c06fde90..120889e255 100644
--- a/spec/duration.html
+++ b/spec/duration.html
@@ -1884,7 +1884,9 @@
1. Else if _timeRemainderNs_ < 0, let _direction_ be -1.
1. Else, let _direction_ be 1.
1. Let _dayStart_ be ? AddZonedDateTime(_zonedRelativeTo_.[[Nanoseconds]], _zonedRelativeTo_.[[TimeZone]], _zonedRelativeTo_.[[Calendar]], _years_, _months_, _weeks_, _days_, 0, 0, 0, 0, 0, 0).
- 1. Let _dayEnd_ be ? AddZonedDateTime(_dayStart_, _zonedRelativeTo_.[[TimeZone]], _zonedRelativeTo_.[[Calendar]], 0, 0, 0, _direction_, 0, 0, 0, 0, 0, 0).
+ 1. Let _dayStartInstant_ be ! CreateTemporalInstant(_dayStart_).
+ 1. Let _dayStartDateTime_ be ? GetPlainDateTimeFor(_zonedRelativeTo_.[[TimeZone]], _dayStartInstant_, _zonedRelativeTo_.[[Calendar]]).
+ 1. Let _dayEnd_ be ? AddDaysToZonedDateTime(_dayStartInstant_, _dayStartDateTime_, _zonedRelativeTo_.[[TimeZone]], _zonedRelativeTo_.[[Calendar]], _direction_).[[EpochNanoseconds]].
1. Let _dayLengthNs_ be ℝ(_dayEnd_ - _dayStart_).
1. Let _oneDayLess_ be _timeRemainderNs_ - _dayLengthNs_.
1. If _oneDayLess_ × _direction_ < 0, then
diff --git a/spec/zoneddatetime.html b/spec/zoneddatetime.html
index 8473006137..0b619d623d 100644
--- a/spec/zoneddatetime.html
+++ b/spec/zoneddatetime.html
@@ -768,7 +768,7 @@ Temporal.ZonedDateTime.prototype.round ( _roundTo_ )
1. Let _dtStart_ be ? CreateTemporalDateTime(_temporalDateTime_.[[ISOYear]], _temporalDateTime_.[[ISOMonth]], _temporalDateTime_.[[ISODay]], 0, 0, 0, 0, 0, 0, *"iso8601"*).
1. Let _instantStart_ be ? GetInstantFor(_timeZone_, _dtStart_, *"compatible"*).
1. Let _startNs_ be _instantStart_.[[Nanoseconds]].
- 1. Let _endNs_ be ? AddZonedDateTime(_startNs_, _timeZone_, _calendar_, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, _dtStart_).
+ 1. Let _endNs_ be ? AddDaysToZonedDateTime(_instantStart_, _dtStart_, _timeZone_, _calendar_, 1).[[EpochNanoseconds]].
1. Let _dayLengthNs_ be ℝ(_endNs_ - _startNs_).
1. If _dayLengthNs_ ≤ 0, then
1. Throw a *RangeError* exception.
@@ -1320,6 +1320,44 @@
+
+
+ AddDaysToZonedDateTime (
+ _instant_: a Temporal.Instant,
+ _dateTime_: a Temporal.PlainDateTime,
+ _timeZone_: a String or Object,
+ _calendar_: a String or Object,
+ _days_: an integer,
+ ): either a normal completion containing a Record, or an abrupt completion
+
+
+
+ This operation is similar to AddZonedDateTime, but adds only a number of days instead of a full duration, which allows performing fewer observable operations.
+
+
+ 1. Assert: If _timeZone_ is a String, _dateTime_'s internal slots are the same as those of the Object returned from ! GetPlainDateTimeFor(_timeZone_, _instant_, _calendar_).
+ 1. If _days_ = 0, then
+ 1. Return the Record {
+ [[EpochNanoseconds]]: _instant_.[[Nanoseconds]],
+ [[Instant]]: _instant_,
+ [[DateTime]]: _dateTime_
+ }.
+ 1. Let _addedDate_ be ? AddISODate(_dateTime_.[[ISOYear]], _dateTime_.[[ISOMonth]], _dateTime_.[[ISODay]], 0, 0, 0, _days_, *"constrain"*).
+ 1. Let _dateTimeResult_ be ? CreateTemporalDateTime(_addedDate_.[[Year]], _addedDate_.[[Month]], _addedDate_.[[Day]], _dateTime_.[[ISOHour]], _dateTime_.[[ISOMinute]], _dateTime_.[[ISOSecond]], _dateTime_.[[ISOMillisecond]], _dateTime_.[[ISOMicrosecond]], _dateTime_.[[ISONanosecond]], _calendar_).
+ 1. Let _instantResult_ be ? GetInstantFor(_timeZone_, _dateTimeResult_, *"compatible"*).
+ 1. Return the Record {
+ [[EpochNanoseconds]]: _instantResult_.[[Nanoseconds]],
+ [[Instant]]: _instantResult_,
+ [[DateTime]]: _dateTimeResult_
+ }.
+
+
+
DifferenceZonedDateTime (
@@ -1381,20 +1419,20 @@
1. Let _endDateTime_ be ? GetPlainDateTimeFor(_zonedRelativeTo_.[[TimeZone]], _endInstant_, _zonedRelativeTo_.[[Calendar]]).
1. Let _dateDifference_ be ? DifferenceISODateTime(_startDateTime_.[[ISOYear]], _startDateTime_.[[ISOMonth]], _startDateTime_.[[ISODay]], _startDateTime_.[[ISOHour]], _startDateTime_.[[ISOMinute]], _startDateTime_.[[ISOSecond]], _startDateTime_.[[ISOMillisecond]], _startDateTime_.[[ISOMicrosecond]], _startDateTime_.[[ISONanosecond]], _endDateTime_.[[ISOYear]], _endDateTime_.[[ISOMonth]], _endDateTime_.[[ISODay]], _endDateTime_.[[ISOHour]], _endDateTime_.[[ISOMinute]], _endDateTime_.[[ISOSecond]], _endDateTime_.[[ISOMillisecond]], _endDateTime_.[[ISOMicrosecond]], _endDateTime_.[[ISONanosecond]], _zonedRelativeTo_.[[Calendar]], *"day"*, OrdinaryObjectCreate(*null*)).
1. Let _days_ be _dateDifference_.[[Days]].
- 1. Let _intermediateNs_ be ℝ(? AddZonedDateTime(ℤ(_startNs_), _zonedRelativeTo_.[[TimeZone]], _zonedRelativeTo_.[[Calendar]], 0, 0, 0, _days_, 0, 0, 0, 0, 0, 0, _startDateTime_)).
+ 1. Let _relativeResult_ be ? AddDaysToZonedDateTime(_startInstant_, _startDateTime_, _zonedRelativeTo_.[[TimeZone]], _zonedRelativeTo_.[[Calendar]], _days_).
1. If _sign_ is 1, then
- 1. Repeat, while _days_ > 0 and _intermediateNs_ > _endNs_,
+ 1. Repeat, while _days_ > 0 and ℝ(_relativeResult_.[[EpochNanoseconds]]) > _endNs_,
1. Set _days_ to _days_ - 1.
- 1. Set _intermediateNs_ to ℝ(? AddZonedDateTime(ℤ(_startNs_), _zonedRelativeTo_.[[TimeZone]], _zonedRelativeTo_.[[Calendar]], 0, 0, 0, _days_, 0, 0, 0, 0, 0, 0, _startDateTime_)).
- 1. Set _nanoseconds_ to _endNs_ - _intermediateNs_.
+ 1. Set _relativeResult_ to ? AddDaysToZonedDateTime(_startInstant_, _startDateTime_, _zonedRelativeTo_.[[TimeZone]], _zonedRelativeTo_.[[Calendar]], _days_).
+ 1. Set _nanoseconds_ to _endNs_ - ℝ(_relativeResult_.[[EpochNanoseconds]]).
1. Let _done_ be *false*.
1. Let _dayLengthNs_ be ~unset~.
1. Repeat, while _done_ is *false*,
- 1. Let _oneDayFartherNs_ be ℝ(? AddZonedDateTime(ℤ(_intermediateNs_), _zonedRelativeTo_.[[TimeZone]], _zonedRelativeTo_.[[Calendar]], 0, 0, 0, _sign_, 0, 0, 0, 0, 0, 0)).
- 1. Set _dayLengthNs_ to _oneDayFartherNs_ - _intermediateNs_.
+ 1. Let _oneDayFarther_ be ? AddDaysToZonedDateTime(_relativeResult_.[[Instant]], _relativeResult_.[[DateTime]], _zonedRelativeTo_.[[TimeZone]], _zonedRelativeTo_.[[Calendar]], _sign_).
+ 1. Set _dayLengthNs_ to ℝ(_oneDayFarther_.[[EpochNanoseconds]] - _relativeResult_.[[EpochNanoseconds]]).
1. If (_nanoseconds_ - _dayLengthNs_) × _sign_ ≥ 0, then
1. Set _nanoseconds_ to _nanoseconds_ - _dayLengthNs_.
- 1. Set _intermediateNs_ to _oneDayFartherNs_.
+ 1. Set _relativeResult_ to _oneDayFarther_.
1. Set _days_ to _days_ + _sign_.
1. Else,
1. Set _done_ to *true*.
From d2beb2109dfa554891d61d3161db8aa250e3bbcb Mon Sep 17 00:00:00 2001
From: Philip Chimento
Date: Wed, 8 Mar 2023 17:33:51 -0800
Subject: [PATCH 02/10] Normative: Fast-path AddDaysToZonedDateTime in
AddZonedDateTime
In the case where AddZonedDateTime is called with years, months, and weeks
all zero, we can fall back to AddDaysToZonedDateTime to avoid the calendar
call, and then AddInstant on the result.
In BalancePossiblyInfiniteTimeDurationRelative, inline the fast path from
AddZonedDateTime since we are not adding years, months, or weeks, and can
now no longer call any calendar methods. Give all intermediate objects the
ISO 8601 calendar for simplicity.
---
polyfill/lib/ecmascript.mjs | 37 +++++++++++++++++++------------------
spec/duration.html | 8 +++++++-
spec/zoneddatetime.html | 8 +++++++-
3 files changed, 33 insertions(+), 20 deletions(-)
diff --git a/polyfill/lib/ecmascript.mjs b/polyfill/lib/ecmascript.mjs
index f5dd9ed389..f3d3a2e570 100644
--- a/polyfill/lib/ecmascript.mjs
+++ b/polyfill/lib/ecmascript.mjs
@@ -3535,22 +3535,17 @@ export function BalancePossiblyInfiniteTimeDurationRelative(
largestUnit,
zonedRelativeTo
) {
- const endNs = AddZonedDateTime(
- GetSlot(zonedRelativeTo, INSTANT),
- GetSlot(zonedRelativeTo, TIME_ZONE),
- GetSlot(zonedRelativeTo, CALENDAR),
- 0,
- 0,
- 0,
- days,
- hours,
- minutes,
- seconds,
- milliseconds,
- microseconds,
- nanoseconds
- );
const startNs = GetSlot(zonedRelativeTo, EPOCHNANOSECONDS);
+
+ let intermediateNs = startNs;
+ if (days !== 0) {
+ const startInstant = GetSlot(zonedRelativeTo, INSTANT);
+ const timeZone = GetSlot(zonedRelativeTo, TIME_ZONE);
+ const startDt = GetPlainDateTimeFor(timeZone, startInstant, 'iso8601');
+ intermediateNs = AddDaysToZonedDateTime(startInstant, startDt, timeZone, 'iso8601', days, 'constrain').epochNs;
+ }
+
+ const endNs = AddInstant(intermediateNs, hours, minutes, seconds, milliseconds, microseconds, nanoseconds);
nanoseconds = endNs.subtract(startNs);
if (largestUnit === 'year' || largestUnit === 'month' || largestUnit === 'week' || largestUnit === 'day') {
@@ -4943,9 +4938,15 @@ export function AddZonedDateTime(
return AddInstant(GetSlot(instant, EPOCHNANOSECONDS), h, min, s, ms, µs, ns);
}
+ const dt = precalculatedPlainDateTime ?? GetPlainDateTimeFor(timeZone, instant, calendar);
+ if (DurationSign(years, months, weeks, 0, 0, 0, 0, 0, 0, 0) === 0) {
+ const overflow = ToTemporalOverflow(options);
+ const intermediate = AddDaysToZonedDateTime(instant, dt, timeZone, calendar, days, overflow).epochNs;
+ return AddInstant(intermediate, h, min, s, ms, µs, ns);
+ }
+
// RFC 5545 requires the date portion to be added in calendar days and the
// time portion to be added in exact time.
- const dt = precalculatedPlainDateTime ?? GetPlainDateTimeFor(timeZone, instant, calendar);
const datePart = CreateTemporalDate(GetSlot(dt, ISO_YEAR), GetSlot(dt, ISO_MONTH), GetSlot(dt, ISO_DAY), calendar);
const dateDuration = new TemporalDuration(years, months, weeks, days, 0, 0, 0, 0, 0, 0);
const addedDate = CalendarDateAdd(calendar, datePart, dateDuration, options);
@@ -4968,7 +4969,7 @@ export function AddZonedDateTime(
return AddInstant(GetSlot(instantIntermediate, EPOCHNANOSECONDS), h, min, s, ms, µs, ns);
}
-export function AddDaysToZonedDateTime(instant, dateTime, timeZone, calendar, days) {
+export function AddDaysToZonedDateTime(instant, dateTime, timeZone, calendar, days, overflow = 'constrain') {
// Same as AddZonedDateTime above, but an optimized version with fewer
// observable calls that only adds a number of days. Returns an object with
// all three versions of the ZonedDateTime: epoch nanoseconds, Instant, and
@@ -4985,7 +4986,7 @@ export function AddDaysToZonedDateTime(instant, dateTime, timeZone, calendar, da
0,
0,
days,
- 'constrain'
+ overflow
);
const dateTimeResult = CreateTemporalDateTime(
addedDate.year,
diff --git a/spec/duration.html b/spec/duration.html
index 120889e255..6eb318ae9f 100644
--- a/spec/duration.html
+++ b/spec/duration.html
@@ -1330,7 +1330,13 @@
This operation observably calls time zone and calendar methods.
- 1. Let _endNs_ be ? AddZonedDateTime(_zonedRelativeTo_.[[Nanoseconds]], _zonedRelativeTo_.[[TimeZone]], _zonedRelativeTo_.[[Calendar]], 0, 0, 0, _days_, _hours_, _minutes_, _seconds_, _milliseconds_, _microseconds_, _nanoseconds_).
+ 1. Let _intermediateNs_ be _zonedRelativeTo_.[[Nanoseconds]].
+ 1. If _days_ ≠ 0, then
+ 1. Let _startInstant_ be ! CreateTemporalInstant(_zonedRelativeTo_.[[Nanoseconds]]).
+ 1. Let _startDateTime_ be ? GetPlainDateTimeFor(_zonedRelativeTo_.[[TimeZone]], _startInstant_, *"iso8601"*).
+ 1. Let _intermediateResult_ be ? AddDaysToZonedDateTime(_startInstant_, _startDateTime_, _zonedRelativeTo_.[[TimeZone]], *"iso8601"*, _days_, *"constrain"*).
+ 1. Set _intermediateNs_ to _intermediateResult_.[[EpochNanoseconds]].
+ 1. Let _endNs_ be AddInstant(_intermediateNs_, _hours_, _minutes_, _seconds_, _milliseconds_, _microseconds_, _nanoseconds_).
1. Set _nanoseconds_ to ℝ(_endNs_ - _zonedRelativeTo_.[[Nanoseconds]]).
1. If _largestUnit_ is one of *"year"*, *"month"*, *"week"*, or *"day"*, then
1. Let _result_ be ? NanosecondsToDays(_nanoseconds_, _zonedRelativeTo_).
diff --git a/spec/zoneddatetime.html b/spec/zoneddatetime.html
index 0b619d623d..5371e37452 100644
--- a/spec/zoneddatetime.html
+++ b/spec/zoneddatetime.html
@@ -1311,6 +1311,10 @@
1. Else,
1. Let _instant_ be ! CreateTemporalInstant(_epochNanoseconds_).
1. Let _temporalDateTime_ be ? GetPlainDateTimeFor(_timeZone_, _instant_, _calendar_).
+ 1. If _years_ = 0, and _months_ = 0, and _weeks_ = 0, then
+ 1. Let _overflow_ be ? ToTemporalOverflow(_options_).
+ 1. Let _intermediate_ be ? AddDaysToZonedDateTime(_instant_, _temporalDateTime_, _timeZone_, _calendar_, _days_, _overflow_).[[EpochNanoseconds]].
+ 1. Return ? AddInstant(_intermediate_, _hours_, _minutes_, _seconds_, _milliseconds_, _microseconds_, _nanoseconds_).
1. Let _datePart_ be ! CreateTemporalDate(_temporalDateTime_.[[ISOYear]], _temporalDateTime_.[[ISOMonth]], _temporalDateTime_.[[ISODay]], _calendar_).
1. Let _dateDuration_ be ! CreateTemporalDuration(_years_, _months_, _weeks_, _days_, 0, 0, 0, 0, 0, 0).
1. Let _addedDate_ be ? CalendarDateAdd(_calendar_, _datePart_, _dateDuration_, _options_).
@@ -1328,6 +1332,7 @@
_timeZone_: a String or Object,
_calendar_: a String or Object,
_days_: an integer,
+ optional _overflow_: *"constrain"* or *"reject"*,
): either a normal completion containing a Record, or an abrupt completion
- 1. Let _newDate_ be ? CalendarDateAdd(_calendar_, _relativeTo_, _duration_, *undefined*, _dateAdd_).
+ 1. Let _newDate_ be ? AddDate(_calendar_, _relativeTo_, _duration_, *undefined*, _dateAdd_).
1. Let _days_ be DaysUntil(_relativeTo_, _newDate_).
1. Return the Record {
[[RelativeTo]]: _newDate_,
@@ -1742,9 +1744,9 @@
1. Let _dateAdd_ be ? GetMethod(_calendar_, *"dateAdd"*).
1. Else,
1. Let _dateAdd_ be ~unused~.
- 1. Let _yearsLater_ be ? CalendarDateAdd(_calendar_, _plainRelativeTo_, _yearsDuration_, *undefined*, _dateAdd_).
+ 1. Let _yearsLater_ be ? AddDate(_calendar_, _plainRelativeTo_, _yearsDuration_, *undefined*, _dateAdd_).
1. Let _yearsMonthsWeeks_ be ! CreateTemporalDuration(_years_, _months_, _weeks_, 0, 0, 0, 0, 0, 0, 0).
- 1. Let _yearsMonthsWeeksLater_ be ? CalendarDateAdd(_calendar_, _plainRelativeTo_, _yearsMonthsWeeks_, *undefined*, _dateAdd_).
+ 1. Let _yearsMonthsWeeksLater_ be ? AddDate(_calendar_, _plainRelativeTo_, _yearsMonthsWeeks_, *undefined*, _dateAdd_).
1. Let _monthsWeeksInDays_ be DaysUntil(_yearsLater_, _yearsMonthsWeeksLater_).
1. Set _plainRelativeTo_ to _yearsLater_.
1. Set _fractionalDays_ to _fractionalDays_ + _monthsWeeksInDays_.
@@ -1775,9 +1777,9 @@
1. Let _dateAdd_ be ? GetMethod(_calendar_, *"dateAdd"*).
1. Else,
1. Let _dateAdd_ be ~unused~.
- 1. Let _yearsMonthsLater_ be ? CalendarDateAdd(_calendar_, _plainRelativeTo_, _yearsMonths_, *undefined*, _dateAdd_).
+ 1. Let _yearsMonthsLater_ be ? AddDate(_calendar_, _plainRelativeTo_, _yearsMonths_, *undefined*, _dateAdd_).
1. Let _yearsMonthsWeeks_ be ! CreateTemporalDuration(_years_, _months_, _weeks_, 0, 0, 0, 0, 0, 0, 0).
- 1. Let _yearsMonthsWeeksLater_ be ? CalendarDateAdd(_calendar_, _plainRelativeTo_, _yearsMonthsWeeks_, *undefined*, _dateAdd_).
+ 1. Let _yearsMonthsWeeksLater_ be ? AddDate(_calendar_, _plainRelativeTo_, _yearsMonthsWeeks_, *undefined*, _dateAdd_).
1. Let _weeksInDays_ be DaysUntil(_yearsMonthsLater_, _yearsMonthsWeeksLater_).
1. Set _plainRelativeTo_ to _yearsMonthsLater_.
1. Set _fractionalDays_ to _fractionalDays_ + _weeksInDays_.
diff --git a/spec/plaindate.html b/spec/plaindate.html
index 300850009c..ee0dbb53c6 100644
--- a/spec/plaindate.html
+++ b/spec/plaindate.html
@@ -377,7 +377,7 @@ Temporal.PlainDate.prototype.add ( _temporalDurationLike_ [ , _options_ ] )<
1. Perform ? RequireInternalSlot(_temporalDate_, [[InitializedTemporalDate]]).
1. Let _duration_ be ? ToTemporalDuration(_temporalDurationLike_).
1. Set _options_ to ? GetOptionsObject(_options_).
- 1. Return ? CalendarDateAdd(_temporalDate_.[[Calendar]], _temporalDate_, _duration_, _options_).
+ 1. Return ? AddDate(_temporalDate_.[[Calendar]], _temporalDate_, _duration_, _options_).
@@ -392,7 +392,7 @@ Temporal.PlainDate.prototype.subtract ( _temporalDurationLike_ [ , _options_
1. Let _duration_ be ? ToTemporalDuration(_temporalDurationLike_).
1. Set _options_ to ? GetOptionsObject(_options_).
1. Let _negatedDuration_ be ! CreateNegatedTemporalDuration(_duration_).
- 1. Return ? CalendarDateAdd(_temporalDate_.[[Calendar]], _temporalDate_, _negatedDuration_, _options_).
+ 1. Return ? AddDate(_temporalDate_.[[Calendar]], _temporalDate_, _negatedDuration_, _options_).
@@ -954,6 +954,38 @@ AddISODate ( _year_, _month_, _day_, _years_, _months_, _weeks_, _days_, _ov
+
+
+ AddDate (
+ _calendar_: a String or Object,
+ _plainDate_: a Temporal.PlainDate,
+ _duration_: a Temporal.Duration,
+ optional _options_: an Object or *undefined*,
+ optional _dateAdd_: a function Object or ~unused~,
+ ): either a normal completion containing a Temporal.PlainDate, or an abrupt completion
+
+
+
+ 1. If _options_ is not present, set _options_ to *undefined*.
+ 1. If _duration_.[[Years]] ≠ 0, or _duration_.[[Months]] ≠ 0, or _duration_.[[Weeks]] ≠ 0, then
+ 1. If _dateAdd_ is not present, then
+ 1. Set _dateAdd_ to ~unused~.
+ 1. If _calendar_ is an Object, set _dateAdd_ to ? GetMethod(_calendar_, *"dateAdd"*).
+ 1. Return ? CalendarDateAdd(_calendar_, _plainDate_, _duration_, _options_, _dateAdd_).
+ 1. Let _overflow_ be ? ToTemporalOverflow(_options_).
+ 1. Let _days_ be ? BalanceTimeDuration(_duration_.[[Days]], _duration_.[[Hours]], _duration_.[[Minutes]], _duration_.[[Seconds]], _duration_.[[Milliseconds]], _duration_.[[Microseconds]], _duration_.[[Nanoseconds]], *"day"*).[[Days]].
+ 1. Let _result_ be ? AddISODate(_plainDate_.[[ISOYear]], _plainDate_.[[ISOMonth]], _plainDate_.[[ISODay]], 0, 0, 0, _days_, _overflow_).
+ 1. Return ! CreateTemporalDate(_result_.[[Year]], _result_.[[Month]], _result_.[[Day]], _calendar_).
+
+
+
CompareISODate ( _y1_, _m1_, _d1_, _y2_, _m2_, _d2_ )
diff --git a/spec/plaindatetime.html b/spec/plaindatetime.html
index 0f7f76e1a9..c08622bc8d 100644
--- a/spec/plaindatetime.html
+++ b/spec/plaindatetime.html
@@ -1164,7 +1164,7 @@
1. Let _timeResult_ be AddTime(_hour_, _minute_, _second_, _millisecond_, _microsecond_, _nanosecond_, _hours_, _minutes_, _seconds_, _milliseconds_, _microseconds_, _nanoseconds_).
1. Let _datePart_ be ! CreateTemporalDate(_year_, _month_, _day_, _calendar_).
1. Let _dateDuration_ be ? CreateTemporalDuration(_years_, _months_, _weeks_, _days_ + _timeResult_.[[Days]], 0, 0, 0, 0, 0, 0).
- 1. Let _addedDate_ be ? CalendarDateAdd(_calendar_, _datePart_, _dateDuration_, _options_).
+ 1. Let _addedDate_ be ? AddDate(_calendar_, _datePart_, _dateDuration_, _options_).
1. Return the Record {
[[Year]]: _addedDate_.[[ISOYear]],
[[Month]]: _addedDate_.[[ISOMonth]],
diff --git a/spec/plainyearmonth.html b/spec/plainyearmonth.html
index 6466f32af6..7724463678 100644
--- a/spec/plainyearmonth.html
+++ b/spec/plainyearmonth.html
@@ -690,7 +690,7 @@
1. Let _durationToAdd_ be ! CreateTemporalDuration(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _balanceResult_.[[Days]], 0, 0, 0, 0, 0, 0).
1. Set _options_ to ? GetOptionsObject(_options_).
1. Let _optionsCopy_ be ? SnapshotOwnProperties(_options_, *null*).
- 1. Let _addedDate_ be ? CalendarDateAdd(_calendar_, _date_, _durationToAdd_, _options_, _dateAdd_).
+ 1. Let _addedDate_ be ? AddDate(_calendar_, _date_, _durationToAdd_, _options_, _dateAdd_).
1. Let _addedDateFields_ be ? PrepareTemporalFields(_addedDate_, _fieldNames_, «»).
1. Return ? CalendarYearMonthFromFields(_calendar_, _addedDateFields_, _optionsCopy_).
From 08e48d4369f80e1e8af58fa3360975acc1d75ed2 Mon Sep 17 00:00:00 2001
From: Philip Chimento
Date: Thu, 9 Mar 2023 16:28:37 -0800
Subject: [PATCH 05/10] Normative: Fast-path differences between identical
objects
If two Temporal objects have identical internal slots, then we can skip
calculating the difference with a calendar method; the difference is
always 0.
This optimization isn't necessary for PlainTime or Instant differences,
since no calendar methods are called for those. Implementations can return
early for PlainTime or Instant if they like, but it won't be reflected in
the spec text in order to keep things as simple as possible.
Note that the early return still comes after the processing of the options
object. Passing an illegal options object to until() or since() should
still throw, even if the difference is zero.
(We'll also fast-path CalendarDateUntil, but this fast path also cuts out
calls to calendar.dateFromFields().)
---
polyfill/lib/ecmascript.mjs | 44 +++++++++++++++++++++++++++++++++----
spec/plaindate.html | 2 ++
spec/plaindatetime.html | 2 ++
spec/plainyearmonth.html | 2 ++
spec/zoneddatetime.html | 2 ++
5 files changed, 48 insertions(+), 4 deletions(-)
diff --git a/polyfill/lib/ecmascript.mjs b/polyfill/lib/ecmascript.mjs
index 77fc15093f..b0c44a11e3 100644
--- a/polyfill/lib/ecmascript.mjs
+++ b/polyfill/lib/ecmascript.mjs
@@ -4303,6 +4303,15 @@ export function DifferenceTemporalPlainDate(operation, plainDate, other, options
const settings = GetDifferenceSettings(operation, resolvedOptions, 'date', [], 'day', 'day');
resolvedOptions.largestUnit = settings.largestUnit;
+ const Duration = GetIntrinsic('%Temporal.Duration%');
+ if (
+ GetSlot(plainDate, ISO_YEAR) === GetSlot(other, ISO_YEAR) &&
+ GetSlot(plainDate, ISO_MONTH) === GetSlot(other, ISO_MONTH) &&
+ GetSlot(plainDate, ISO_DAY) === GetSlot(other, ISO_DAY)
+ ) {
+ return new Duration();
+ }
+
const untilResult = CalendarDateUntil(calendar, plainDate, other, resolvedOptions);
let years = GetSlot(untilResult, YEARS);
let months = GetSlot(untilResult, MONTHS);
@@ -4328,7 +4337,6 @@ export function DifferenceTemporalPlainDate(operation, plainDate, other, options
));
}
- const Duration = GetIntrinsic('%Temporal.Duration%');
return new Duration(sign * years, sign * months, sign * weeks, sign * days, 0, 0, 0, 0, 0, 0);
}
@@ -4342,6 +4350,21 @@ export function DifferenceTemporalPlainDateTime(operation, plainDateTime, other,
const resolvedOptions = SnapshotOwnProperties(GetOptionsObject(options), null);
const settings = GetDifferenceSettings(operation, resolvedOptions, 'datetime', [], 'nanosecond', 'day');
+ const Duration = GetIntrinsic('%Temporal.Duration%');
+ if (
+ GetSlot(plainDateTime, ISO_YEAR) === GetSlot(other, ISO_YEAR) &&
+ GetSlot(plainDateTime, ISO_MONTH) === GetSlot(other, ISO_MONTH) &&
+ GetSlot(plainDateTime, ISO_DAY) === GetSlot(other, ISO_DAY) &&
+ GetSlot(plainDateTime, ISO_HOUR) == GetSlot(other, ISO_HOUR) &&
+ GetSlot(plainDateTime, ISO_MINUTE) == GetSlot(other, ISO_MINUTE) &&
+ GetSlot(plainDateTime, ISO_SECOND) == GetSlot(other, ISO_SECOND) &&
+ GetSlot(plainDateTime, ISO_MILLISECOND) == GetSlot(other, ISO_MILLISECOND) &&
+ GetSlot(plainDateTime, ISO_MICROSECOND) == GetSlot(other, ISO_MICROSECOND) &&
+ GetSlot(plainDateTime, ISO_NANOSECOND) == GetSlot(other, ISO_NANOSECOND)
+ ) {
+ return new Duration();
+ }
+
let { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } =
DifferenceISODateTime(
GetSlot(plainDateTime, ISO_YEAR),
@@ -4397,7 +4420,6 @@ export function DifferenceTemporalPlainDateTime(operation, plainDateTime, other,
));
}
- const Duration = GetIntrinsic('%Temporal.Duration%');
return new Duration(
sign * years,
sign * months,
@@ -4484,6 +4506,16 @@ export function DifferenceTemporalPlainYearMonth(operation, yearMonth, other, op
const resolvedOptions = SnapshotOwnProperties(GetOptionsObject(options), null);
const settings = GetDifferenceSettings(operation, resolvedOptions, 'date', ['week', 'day'], 'month', 'year');
+
+ const Duration = GetIntrinsic('%Temporal.Duration%');
+ if (
+ GetSlot(yearMonth, ISO_YEAR) === GetSlot(other, ISO_YEAR) &&
+ GetSlot(yearMonth, ISO_MONTH) === GetSlot(other, ISO_MONTH) &&
+ GetSlot(yearMonth, ISO_DAY) === GetSlot(other, ISO_DAY)
+ ) {
+ return new Duration();
+ }
+
resolvedOptions.largestUnit = settings.largestUnit;
const fieldNames = CalendarFields(calendar, ['monthCode', 'year']);
@@ -4515,7 +4547,6 @@ export function DifferenceTemporalPlainYearMonth(operation, yearMonth, other, op
));
}
- const Duration = GetIntrinsic('%Temporal.Duration%');
return new Duration(sign * years, sign * months, 0, 0, 0, 0, 0, 0, 0, 0);
}
@@ -4532,6 +4563,9 @@ export function DifferenceTemporalZonedDateTime(operation, zonedDateTime, other,
const ns1 = GetSlot(zonedDateTime, EPOCHNANOSECONDS);
const ns2 = GetSlot(other, EPOCHNANOSECONDS);
+
+ const Duration = GetIntrinsic('%Temporal.Duration%');
+
let years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds;
if (
settings.largestUnit !== 'year' &&
@@ -4560,6 +4594,9 @@ export function DifferenceTemporalZonedDateTime(operation, zonedDateTime, other,
'or smaller because day lengths can vary between time zones due to DST or time zone offset changes.'
);
}
+
+ if (ns1.equals(ns2)) return new Duration();
+
({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } =
DifferenceZonedDateTime(ns1, ns2, timeZone, calendar, settings.largestUnit, resolvedOptions));
@@ -4604,7 +4641,6 @@ export function DifferenceTemporalZonedDateTime(operation, zonedDateTime, other,
}
}
- const Duration = GetIntrinsic('%Temporal.Duration%');
return new Duration(
sign * years,
sign * months,
diff --git a/spec/plaindate.html b/spec/plaindate.html
index ee0dbb53c6..ceb9409e97 100644
--- a/spec/plaindate.html
+++ b/spec/plaindate.html
@@ -1019,6 +1019,8 @@
1. Let _resolvedOptions_ be ? SnapshotOwnProperties(? GetOptionsObject(_options_), *null*).
1. Let _settings_ be ? GetDifferenceSettings(_operation_, _resolvedOptions_, ~date~, « », *"day"*, *"day"*).
1. Perform ! CreateDataPropertyOrThrow(_resolvedOptions_, *"largestUnit"*, _settings_.[[LargestUnit]]).
+ 1. If _temporalDate_.[[ISOYear]] = _other_.[[ISOYear]], and _temporalDate_.[[ISOMonth]] = _other_.[[ISOMonth]], and _temporalDate_.[[ISODay]] = _other_.[[ISODay]], then
+ 1. Return ! CreateTemporalDuration(0, 0, 0, 0, 0, 0, 0, 0, 0, 0).
1. Let _result_ be ? CalendarDateUntil(_temporalDate_.[[Calendar]], _temporalDate_, _other_, _resolvedOptions_).
1. If _settings_.[[SmallestUnit]] is not *"day"* or _settings_.[[RoundingIncrement]] ≠ 1, then
1. Let _roundRecord_ be ? RoundDuration(_result_.[[Years]], _result_.[[Months]], _result_.[[Weeks]], _result_.[[Days]], 0, 0, 0, 0, 0, 0, _settings_.[[RoundingIncrement]], _settings_.[[SmallestUnit]], _settings_.[[RoundingMode]], _temporalDate_).
diff --git a/spec/plaindatetime.html b/spec/plaindatetime.html
index c08622bc8d..e5b5d2ea6a 100644
--- a/spec/plaindatetime.html
+++ b/spec/plaindatetime.html
@@ -1293,6 +1293,8 @@
1. If ? CalendarEquals(_dateTime_.[[Calendar]], _other_.[[Calendar]]) is *false*, throw a *RangeError* exception.
1. Let _resolvedOptions_ be ? SnapshotOwnProperties(? GetOptionsObject(_options_), *null*).
1. Let _settings_ be ? GetDifferenceSettings(_operation_, _resolvedOptions_, ~datetime~, « », *"nanosecond"*, *"day"*).
+ 1. If _dateTime_.[[ISOYear]] = _other_.[[ISOYear]], and _dateTime_.[[ISOMonth]] = _other_.[[ISOMonth]], and _dateTime_.[[ISODay]] = _other_.[[ISODay]], and _dateTime_.[[ISOHour]] = _other_.[[ISOHour]], and _dateTime_.[[ISOMinute]] = _other_.[[ISOMinute]], and _dateTime_.[[ISOSecond]] = _other_.[[ISOSecond]], and _dateTime_.[[ISOMillisecond]] = _other_.[[ISOMillisecond]], and _dateTime_.[[ISOMicrosecond]] = _other_.[[ISOMicrosecond]], and _dateTime_.[[ISONanosecond]] = _other_.[[ISONanosecond]], then
+ 1. Return ! CreateTemporalDuration(0, 0, 0, 0, 0, 0, 0, 0, 0, 0).
1. Let _diff_ be ? DifferenceISODateTime(_dateTime_.[[ISOYear]], _dateTime_.[[ISOMonth]], _dateTime_.[[ISODay]], _dateTime_.[[ISOHour]], _dateTime_.[[ISOMinute]], _dateTime_.[[ISOSecond]], _dateTime_.[[ISOMillisecond]], _dateTime_.[[ISOMicrosecond]], _dateTime_.[[ISONanosecond]], _other_.[[ISOYear]], _other_.[[ISOMonth]], _other_.[[ISODay]], _other_.[[ISOHour]], _other_.[[ISOMinute]], _other_.[[ISOSecond]], _other_.[[ISOMillisecond]], _other_.[[ISOMicrosecond]], _other_.[[ISONanosecond]], _dateTime_.[[Calendar]], _settings_.[[LargestUnit]], _resolvedOptions_).
1. If _settings_.[[SmallestUnit]] is *"nanosecond"* and _settings_.[[RoundingIncrement]] is 1, then
1. Return ! CreateTemporalDuration(_sign_ × _diff_.[[Years]], _sign_ × _diff_.[[Months]], _sign_ × _diff_.[[Weeks]], _sign_ × _diff_.[[Days]], _sign_ × _diff_.[[Hours]], _sign_ × _diff_.[[Minutes]], _sign_ × _diff_.[[Seconds]], _sign_ × _diff_.[[Milliseconds]], _sign_ × _diff_.[[Microseconds]], _sign_ × _diff_.[[Nanoseconds]]).
diff --git a/spec/plainyearmonth.html b/spec/plainyearmonth.html
index 7724463678..58c3e6f382 100644
--- a/spec/plainyearmonth.html
+++ b/spec/plainyearmonth.html
@@ -636,6 +636,8 @@
1. If ? CalendarEquals(_calendar_, _other_.[[Calendar]]) is *false*, throw a *RangeError* exception.
1. Let _resolvedOptions_ be ? SnapshotOwnProperties(? GetOptionsObject(_options_), *null*).
1. Let _settings_ be ? GetDifferenceSettings(_operation_, _resolvedOptions_, ~date~, « *"week"*, *"day"* », *"month"*, *"year"*).
+ 1. If _yearMonth_.[[ISOYear]] = _other_.[[ISOYear]] and _yearMonth_.[[ISOMonth]] = _other_.[[ISOMonth]] and _yearMonth_.[[ISODay]] = _other_.[[ISODay]], then
+ 1. Return ! CreateTemporalDuration(0, 0, 0, 0, 0, 0, 0, 0, 0, 0).
1. Perform ! CreateDataPropertyOrThrow(_resolvedOptions_, *"largestUnit"*, _settings_.[[LargestUnit]]).
1. Let _fieldNames_ be ? CalendarFields(_calendar_, « *"monthCode"*, *"year"* »).
1. Let _thisFields_ be ? PrepareTemporalFields(_yearMonth_, _fieldNames_, «»).
diff --git a/spec/zoneddatetime.html b/spec/zoneddatetime.html
index 5371e37452..c74921a4be 100644
--- a/spec/zoneddatetime.html
+++ b/spec/zoneddatetime.html
@@ -1481,6 +1481,8 @@
1. Return ! CreateTemporalDuration(0, 0, 0, 0, _sign_ × _result_.[[Hours]], _sign_ × _result_.[[Minutes]], _sign_ × _result_.[[Seconds]], _sign_ × _result_.[[Milliseconds]], _sign_ × _result_.[[Microseconds]], _sign_ × _result_.[[Nanoseconds]]).
1. If ? TimeZoneEquals(_zonedDateTime_.[[TimeZone]], _other_.[[TimeZone]]) is *false*, then
1. Throw a *RangeError* exception.
+ 1. If _zonedDateTime_.[[Nanoseconds]] = _other_.[[Nanoseconds]], then
+ 1. Return ! CreateTemporalDuration(0, 0, 0, 0, 0, 0, 0, 0, 0, 0).
1. Let _difference_ be ? DifferenceZonedDateTime(_zonedDateTime_.[[Nanoseconds]], _other_.[[Nanoseconds]], _zonedDateTime_.[[TimeZone]], _zonedDateTime_.[[Calendar]], _settings_.[[LargestUnit]], _resolvedOptions_).
1. If _settings_.[[SmallestUnit]] is *"nanosecond"* and _settings_.[[RoundingIncrement]] is 1, then
1. Return ! CreateTemporalDuration(_sign_ × _difference_.[[Years]], _sign_ × _difference_.[[Months]], _sign_ × _difference_.[[Weeks]], _sign_ × _difference_.[[Days]], _sign_ × _difference_.[[Hours]], _sign_ × _difference_.[[Minutes]], _sign_ × _difference_.[[Seconds]], _sign_ × _difference_.[[Milliseconds]], _sign_ × _difference_.[[Microseconds]], _sign_ × _difference_.[[Nanoseconds]]).
From 25c020dbda44891717127887436f2b37568d9213 Mon Sep 17 00:00:00 2001
From: Philip Chimento
Date: Thu, 9 Mar 2023 21:03:30 -0800
Subject: [PATCH 06/10] Normative: Fast-path dateUntil() when difference
largest unit is days
Introduce an operation DifferenceDate, which has a fast-path through the
ISO calendar (via DaysUntil) in the case where we would otherwise call
calendar.dateUntil() with largestUnit === "day". Also return a blank
duration immediately if the two dates are the same day.
Note that this makes it impossible for the following BalanceDuration call
in DifferenceISODateTime to throw. The dateUntil() method will no longer
be called for largestUnits of "day" or less. So dateDifference.[[Days]]
can be at most Number.MAX_VALUE, but timeDifference cannot balance into
more than one day and make it overflow in the BalanceDuration call.
In NanosecondsToDays, inline the fast path from DifferenceISODateTime
since we only have largestUnit === "day" there and can now no longer call
any calendar methods. Give all intermediate objects the ISO 8601 calendar
for simplicity.
Note, as evident from the corresponding test262 tests, this removes
several loopholes where it was possible to return particular values from
user calls that would cause infinite loops, or calculate zero-length days.
---
polyfill/lib/ecmascript.mjs | 76 +++++++++++++++++++++++++++----------
spec/duration.html | 4 +-
spec/plaindate.html | 32 +++++++++++++++-
spec/plaindatetime.html | 4 +-
spec/zoneddatetime.html | 24 ++++++++++--
5 files changed, 112 insertions(+), 28 deletions(-)
diff --git a/polyfill/lib/ecmascript.mjs b/polyfill/lib/ecmascript.mjs
index b0c44a11e3..6310162675 100644
--- a/polyfill/lib/ecmascript.mjs
+++ b/polyfill/lib/ecmascript.mjs
@@ -3268,32 +3268,55 @@ export function NanosecondsToDays(nanoseconds, zonedRelativeTo) {
const timeZone = GetSlot(zonedRelativeTo, TIME_ZONE);
const calendar = GetSlot(zonedRelativeTo, CALENDAR);
- // Find the difference in days only.
- const dtStart = GetPlainDateTimeFor(timeZone, start, calendar);
- const dtEnd = GetPlainDateTimeFor(timeZone, end, calendar);
- let { days } = DifferenceISODateTime(
- GetSlot(dtStart, ISO_YEAR),
- GetSlot(dtStart, ISO_MONTH),
- GetSlot(dtStart, ISO_DAY),
+ // Find the difference in days only. Inline DifferenceISODateTime because we
+ // don't need the path that potentially calls calendar methods.
+ const dtStart = GetPlainDateTimeFor(timeZone, start, 'iso8601');
+ const dtEnd = GetPlainDateTimeFor(timeZone, end, 'iso8601');
+ let hours, minutes, seconds, milliseconds, microseconds;
+ ({ hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = DifferenceTime(
GetSlot(dtStart, ISO_HOUR),
GetSlot(dtStart, ISO_MINUTE),
GetSlot(dtStart, ISO_SECOND),
GetSlot(dtStart, ISO_MILLISECOND),
GetSlot(dtStart, ISO_MICROSECOND),
GetSlot(dtStart, ISO_NANOSECOND),
- GetSlot(dtEnd, ISO_YEAR),
- GetSlot(dtEnd, ISO_MONTH),
- GetSlot(dtEnd, ISO_DAY),
GetSlot(dtEnd, ISO_HOUR),
GetSlot(dtEnd, ISO_MINUTE),
GetSlot(dtEnd, ISO_SECOND),
GetSlot(dtEnd, ISO_MILLISECOND),
GetSlot(dtEnd, ISO_MICROSECOND),
- GetSlot(dtEnd, ISO_NANOSECOND),
- calendar,
- 'day',
- ObjectCreate(null)
- );
+ GetSlot(dtEnd, ISO_NANOSECOND)
+ ));
+
+ const timeSign = DurationSign(0, 0, 0, 0, hours, minutes, seconds, milliseconds, microseconds, nanoseconds);
+ let y1 = GetSlot(dtStart, ISO_YEAR);
+ let mon1 = GetSlot(dtStart, ISO_MONTH);
+ let d1 = GetSlot(dtStart, ISO_DAY);
+ const y2 = GetSlot(dtEnd, ISO_YEAR);
+ const mon2 = GetSlot(dtEnd, ISO_MONTH);
+ const d2 = GetSlot(dtEnd, ISO_DAY);
+ const dateSign = CompareISODate(y2, mon2, d2, y1, mon1, d1);
+ if (dateSign === -timeSign) {
+ ({ year: y1, month: mon1, day: d1 } = BalanceISODate(y1, mon1, d1 - timeSign));
+ ({ hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = BalanceTimeDuration(
+ -timeSign,
+ hours,
+ minutes,
+ seconds,
+ milliseconds,
+ microseconds,
+ nanoseconds,
+ 'day'
+ ));
+ }
+
+ const date1 = CreateTemporalDate(y1, mon1, d1, 'iso8601');
+ const date2 = CreateTemporalDate(y2, mon2, d2, 'iso8601');
+
+ let days = DaysUntil(date1, date2);
+ // Signs of date part and time part may not agree; balance them together
+ ({ days } = BalanceTimeDuration(days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, 'day'));
+
let relativeResult = AddDaysToZonedDateTime(start, dtStart, timeZone, calendar, days);
// may disambiguate
@@ -4071,6 +4094,21 @@ export function DifferenceInstant(ns1, ns2, increment, smallestUnit, largestUnit
return BalanceTimeDuration(0, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, largestUnit);
}
+export function DifferenceDate(calendar, plainDate1, plainDate2, options) {
+ const TemporalDuration = GetIntrinsic('%Temporal.Duration%');
+ if (
+ GetSlot(plainDate1, ISO_YEAR) === GetSlot(plainDate2, ISO_YEAR) &&
+ GetSlot(plainDate1, ISO_MONTH) === GetSlot(plainDate2, ISO_MONTH) &&
+ GetSlot(plainDate1, ISO_DAY) === GetSlot(plainDate2, ISO_DAY)
+ ) {
+ return new TemporalDuration();
+ }
+ if (options.largestUnit === 'day') {
+ return new TemporalDuration(0, 0, 0, DaysUntil(plainDate1, plainDate2));
+ }
+ return CalendarDateUntil(calendar, plainDate1, plainDate2, options);
+}
+
export function DifferenceISODateTime(
y1,
mon1,
@@ -4130,7 +4168,7 @@ export function DifferenceISODateTime(
const dateLargestUnit = LargerOfTwoTemporalUnits('day', largestUnit);
const untilOptions = SnapshotOwnProperties(GetOptionsObject(options), null);
untilOptions.largestUnit = dateLargestUnit;
- const untilResult = CalendarDateUntil(calendar, date1, date2, untilOptions);
+ const untilResult = DifferenceDate(calendar, date1, date2, untilOptions);
const years = GetSlot(untilResult, YEARS);
const months = GetSlot(untilResult, MONTHS);
const weeks = GetSlot(untilResult, WEEKS);
@@ -4312,7 +4350,7 @@ export function DifferenceTemporalPlainDate(operation, plainDate, other, options
return new Duration();
}
- const untilResult = CalendarDateUntil(calendar, plainDate, other, resolvedOptions);
+ const untilResult = DifferenceDate(calendar, plainDate, other, resolvedOptions);
let years = GetSlot(untilResult, YEARS);
let months = GetSlot(untilResult, MONTHS);
let weeks = GetSlot(untilResult, WEEKS);
@@ -4785,7 +4823,7 @@ export function AddDuration(
const dateLargestUnit = LargerOfTwoTemporalUnits('day', largestUnit);
const differenceOptions = ObjectCreate(null);
differenceOptions.largestUnit = dateLargestUnit;
- const untilResult = CalendarDateUntil(calendar, plainRelativeTo, end, differenceOptions);
+ const untilResult = DifferenceDate(calendar, plainRelativeTo, end, differenceOptions);
years = GetSlot(untilResult, YEARS);
months = GetSlot(untilResult, MONTHS);
weeks = GetSlot(untilResult, WEEKS);
@@ -5600,7 +5638,7 @@ export function RoundDuration(
const wholeDaysLater = CreateTemporalDate(isoResult.year, isoResult.month, isoResult.day, calendar);
const untilOptions = ObjectCreate(null);
untilOptions.largestUnit = 'year';
- const yearsPassed = GetSlot(CalendarDateUntil(calendar, plainRelativeTo, wholeDaysLater, untilOptions), YEARS);
+ const yearsPassed = GetSlot(DifferenceDate(calendar, plainRelativeTo, wholeDaysLater, untilOptions), YEARS);
years += yearsPassed;
const yearsPassedDuration = new TemporalDuration(yearsPassed);
let daysPassed;
diff --git a/spec/duration.html b/spec/duration.html
index 45966810a7..36e0aab64a 100644
--- a/spec/duration.html
+++ b/spec/duration.html
@@ -1610,7 +1610,7 @@
1. Let _dateLargestUnit_ be ! LargerOfTwoTemporalUnits(*"day"*, _largestUnit_).
1. Let _differenceOptions_ be OrdinaryObjectCreate(*null*).
1. Perform ! CreateDataPropertyOrThrow(_differenceOptions_, *"largestUnit"*, _dateLargestUnit_).
- 1. Let _dateDifference_ be ? CalendarDateUntil(_calendar_, _plainRelativeTo_, _end_, _differenceOptions_).
+ 1. Let _dateDifference_ be ? DifferenceDate(_calendar_, _plainRelativeTo_, _end_, _differenceOptions_).
1. Let _result_ be ? BalanceTimeDuration(_dateDifference_.[[Days]], _h1_ + _h2_, _min1_ + _min2_, _s1_ + _s2_, _ms1_ + _ms2_, _mus1_ + _mus2_, _ns1_ + _ns2_, _largestUnit_).
1. Return ! CreateDurationRecord(_dateDifference_.[[Years]], _dateDifference_.[[Months]], _dateDifference_.[[Weeks]], _result_.[[Days]], _result_.[[Hours]], _result_.[[Minutes]], _result_.[[Seconds]], _result_.[[Milliseconds]], _result_.[[Microseconds]], _result_.[[Nanoseconds]]).
1. Assert: _zonedRelativeTo_ is not *undefined*.
@@ -1754,7 +1754,7 @@
1. Let _wholeDaysLater_ be ! CreateTemporalDate(_isoResult_.[[Year]], _isoResult_.[[Month]], _isoResult_.[[Day]], _calendar_).
1. Let _untilOptions_ be OrdinaryObjectCreate(*null*).
1. Perform ! CreateDataPropertyOrThrow(_untilOptions_, *"largestUnit"*, *"year"*).
- 1. Let _timePassed_ be ? CalendarDateUntil(_calendar_, _plainRelativeTo_, _wholeDaysLater_, _untilOptions_).
+ 1. Let _timePassed_ be ? DifferenceDate(_calendar_, _plainRelativeTo_, _wholeDaysLater_, _untilOptions_).
1. Let _yearsPassed_ be _timePassed_.[[Years]].
1. Set _years_ to _years_ + _yearsPassed_.
1. Let _yearsDuration_ be ! CreateTemporalDuration(_yearsPassed_, 0, 0, 0, 0, 0, 0, 0, 0, 0).
diff --git a/spec/plaindate.html b/spec/plaindate.html
index ceb9409e97..e13b550b83 100644
--- a/spec/plaindate.html
+++ b/spec/plaindate.html
@@ -841,6 +841,36 @@
+
+
+ DifferenceDate (
+ _calendar_: a String or Object,
+ _one_: a Temporal.PlainDate,
+ _two_: a Temporal.PlainDate,
+ _options_: an Object,
+ ): either a normal completion containing a Temporal.Duration, or an abrupt completion
+
+
+
+ 1. Assert: _one_.[[Calendar]] and _two_.[[Calendar]] have been determined to be equivalent as with CalendarEquals.
+ 1. Assert: _options_ is an ordinary Object.
+ 1. Assert: _options_.[[Prototype]] is *null*.
+ 1. Assert: _options_ has a *"largestUnit"* data property.
+ 1. If _one_.[[ISOYear]] = _two_.[[ISOYear]] and _one_.[[ISOMonth]] = _two_.[[ISOMonth]] and _one_.[[ISODay]] = _two_.[[ISODay]], then
+ 1. Return ! CreateTemporalDuration(0, 0, 0, 0, 0, 0, 0, 0, 0, 0).
+ 1. If ! Get(_options_, *"largestUnit"*) is *"day"*, then
+ 1. Let _days_ be DaysUntil(_one_, _two_).
+ 1. Return ! CreateTemporalDuration(0, 0, 0, _days_, 0, 0, 0, 0, 0, 0).
+ 1. Return ? CalendarDateUntil(_calendar_, _one_, _two_, _options_).
+
+
+
RegulateISODate (
@@ -1021,7 +1051,7 @@
1. Perform ! CreateDataPropertyOrThrow(_resolvedOptions_, *"largestUnit"*, _settings_.[[LargestUnit]]).
1. If _temporalDate_.[[ISOYear]] = _other_.[[ISOYear]], and _temporalDate_.[[ISOMonth]] = _other_.[[ISOMonth]], and _temporalDate_.[[ISODay]] = _other_.[[ISODay]], then
1. Return ! CreateTemporalDuration(0, 0, 0, 0, 0, 0, 0, 0, 0, 0).
- 1. Let _result_ be ? CalendarDateUntil(_temporalDate_.[[Calendar]], _temporalDate_, _other_, _resolvedOptions_).
+ 1. Let _result_ be ? DifferenceDate(_temporalDate_.[[Calendar]], _temporalDate_, _other_, _resolvedOptions_).
1. If _settings_.[[SmallestUnit]] is not *"day"* or _settings_.[[RoundingIncrement]] ≠ 1, then
1. Let _roundRecord_ be ? RoundDuration(_result_.[[Years]], _result_.[[Months]], _result_.[[Weeks]], _result_.[[Days]], 0, 0, 0, 0, 0, 0, _settings_.[[RoundingIncrement]], _settings_.[[SmallestUnit]], _settings_.[[RoundingMode]], _temporalDate_).
1. Set _result_ to _roundRecord_.[[DurationRecord]].
diff --git a/spec/plaindatetime.html b/spec/plaindatetime.html
index e5b5d2ea6a..b6823a09d2 100644
--- a/spec/plaindatetime.html
+++ b/spec/plaindatetime.html
@@ -1269,8 +1269,8 @@
1. Let _dateLargestUnit_ be ! LargerOfTwoTemporalUnits(*"day"*, _largestUnit_).
1. Let _untilOptions_ be ? SnapshotOwnProperties(? GetOptionsObject(_options_), *null*).
1. Perform ! CreateDataPropertyOrThrow(_untilOptions_, *"largestUnit"*, _dateLargestUnit_).
- 1. Let _dateDifference_ be ? CalendarDateUntil(_calendar_, _date1_, _date2_, _untilOptions_).
- 1. Let _balanceResult_ be ? BalanceTimeDuration(_dateDifference_.[[Days]], _timeDifference_.[[Hours]], _timeDifference_.[[Minutes]], _timeDifference_.[[Seconds]], _timeDifference_.[[Milliseconds]], _timeDifference_.[[Microseconds]], _timeDifference_.[[Nanoseconds]], _largestUnit_).
+ 1. Let _dateDifference_ be ? DifferenceDate(_calendar_, _date1_, _date2_, _untilOptions_).
+ 1. Let _balanceResult_ be ! BalanceTimeDuration(_dateDifference_.[[Days]], _timeDifference_.[[Hours]], _timeDifference_.[[Minutes]], _timeDifference_.[[Seconds]], _timeDifference_.[[Milliseconds]], _timeDifference_.[[Microseconds]], _timeDifference_.[[Nanoseconds]], _largestUnit_).
1. Return ! CreateDurationRecord(_dateDifference_.[[Years]], _dateDifference_.[[Months]], _dateDifference_.[[Weeks]], _balanceResult_.[[Days]], _balanceResult_.[[Hours]], _balanceResult_.[[Minutes]], _balanceResult_.[[Seconds]], _balanceResult_.[[Milliseconds]], _balanceResult_.[[Microseconds]], _balanceResult_.[[Nanoseconds]]).
diff --git a/spec/zoneddatetime.html b/spec/zoneddatetime.html
index c74921a4be..1b6a0077c9 100644
--- a/spec/zoneddatetime.html
+++ b/spec/zoneddatetime.html
@@ -1421,10 +1421,26 @@
1. Let _endNs_ be _startNs_ + _nanoseconds_.
1. If IsValidEpochNanoseconds(ℤ(_endNs_)) is *false*, throw a *RangeError* exception.
1. Let _endInstant_ be ! CreateTemporalInstant(ℤ(_endNs_)).
- 1. Let _startDateTime_ be ? GetPlainDateTimeFor(_zonedRelativeTo_.[[TimeZone]], _startInstant_, _zonedRelativeTo_.[[Calendar]]).
- 1. Let _endDateTime_ be ? GetPlainDateTimeFor(_zonedRelativeTo_.[[TimeZone]], _endInstant_, _zonedRelativeTo_.[[Calendar]]).
- 1. Let _dateDifference_ be ? DifferenceISODateTime(_startDateTime_.[[ISOYear]], _startDateTime_.[[ISOMonth]], _startDateTime_.[[ISODay]], _startDateTime_.[[ISOHour]], _startDateTime_.[[ISOMinute]], _startDateTime_.[[ISOSecond]], _startDateTime_.[[ISOMillisecond]], _startDateTime_.[[ISOMicrosecond]], _startDateTime_.[[ISONanosecond]], _endDateTime_.[[ISOYear]], _endDateTime_.[[ISOMonth]], _endDateTime_.[[ISODay]], _endDateTime_.[[ISOHour]], _endDateTime_.[[ISOMinute]], _endDateTime_.[[ISOSecond]], _endDateTime_.[[ISOMillisecond]], _endDateTime_.[[ISOMicrosecond]], _endDateTime_.[[ISONanosecond]], _zonedRelativeTo_.[[Calendar]], *"day"*, OrdinaryObjectCreate(*null*)).
- 1. Let _days_ be _dateDifference_.[[Days]].
+ 1. Let _startDateTime_ be ? GetPlainDateTimeFor(_zonedRelativeTo_.[[TimeZone]], _startInstant_, *"iso8601"*).
+ 1. Let _endDateTime_ be ? GetPlainDateTimeFor(_zonedRelativeTo_.[[TimeZone]], _endInstant_, *"iso8601"*).
+ 1. Let _timeDifference_ be DifferenceTime(_startDateTime_.[[ISOHour]], _startDateTime_.[[ISOMinute]], _startDateTime_.[[ISOSecond]], _startDateTime_.[[ISOMillisecond]], _startDateTime_.[[ISOMicrosecond]], _startDateTime_.[[ISONanosecond]], _endDateTime_.[[ISOHour]], _endDateTime_.[[ISOMinute]], _endDateTime_.[[ISOSecond]], _endDateTime_.[[ISOMillisecond]], _endDateTime_.[[ISOMicrosecond]], _endDateTime_.[[ISONanosecond]]).
+ 1. Let _timeSign_ be DurationSign(0, 0, 0, 0, _timeDifference_.[[Hours]], _timeDifference_.[[Minutes]], _timeDifference_.[[Seconds]], _timeDifference_.[[Milliseconds]], _timeDifference_.[[Microseconds]], _timeDifference_.[[Nanoseconds]]).
+ 1. Let _y1_ be _startDateTime_.[[ISOYear]].
+ 1. Let _mon1_ be _startDateTime_.[[ISOMonth]].
+ 1. Let _d1_ be _startDateTime_.[[ISODay]].
+ 1. Let _y2_ be _endDateTime_.[[ISOYear]].
+ 1. Let _mon2_ be _endDateTime_.[[ISOMonth]].
+ 1. Let _d2_ be _endDateTime_.[[ISODay]].
+ 1. Let _dateSign_ be CompareISODate(_y2_, _mon2_, _d2_, _y1_, _mon1_, _d1_).
+ 1. Let _adjustedDate_ be CreateISODateRecord(_y1_, _mon1_, _d1_).
+ 1. If _timeSign_ = -_dateSign_, then
+ 1. Set _adjustedDate_ to BalanceISODate(_y1_, _mon1_, _d1_ - _timeSign_).
+ 1. Set _timeDifference_ to ! BalanceTimeDuration(-_timeSign_, _timeDifference_.[[Hours]], _timeDifference_.[[Minutes]], _timeDifference_.[[Seconds]], _timeDifference_.[[Milliseconds]], _timeDifference_.[[Microseconds]], _timeDifference_.[[Nanoseconds]], *"day"*).
+ 1. Let _date1_ be ! CreateTemporalDate(_adjustedDate_.[[Year]], _adjustedDate_.[[Month]], _adjustedDate_.[[Day]], *"iso8601"*).
+ 1. Let _date2_ be ! CreateTemporalDate(_y2_, _mon2_, _d2_, *"iso8601"*).
+ 1. Let _days_ be DaysUntil(_date1_, _date2_).
+ 1. Let _balanceResult_ be ! BalanceTimeDuration(_days_, _timeDifference_.[[Hours]], _timeDifference_.[[Minutes]], _timeDifference_.[[Seconds]], _timeDifference_.[[Milliseconds]], _timeDifference_.[[Microseconds]], _timeDifference_.[[Nanoseconds]], *"day"*).
+ 1. Set _days_ to _balanceResult_.[[Days]].
1. Let _relativeResult_ be ? AddDaysToZonedDateTime(_startInstant_, _startDateTime_, _zonedRelativeTo_.[[TimeZone]], _zonedRelativeTo_.[[Calendar]], _days_).
1. If _sign_ is 1, then
1. Repeat, while _days_ > 0 and ℝ(_relativeResult_.[[EpochNanoseconds]]) > _endNs_,
From a0d4d9b5224e229ba1a53358d45bab0011e11975 Mon Sep 17 00:00:00 2001
From: Philip Chimento
Date: Fri, 10 Mar 2023 09:28:52 -0800
Subject: [PATCH 07/10] Normative: Look up getOffsetNanosecondsFor only once
when resolving ambiguous datetime
In DisambiguatePossibleInstants, when getPossibleInstantsFor has returned
an empty array, we calculate the UTC offset in the time zone one day
before and one day after. Don't look up the method twice when doing this.
Similarly, in InterpretISODateTimeOffset, when getPossibleInstantsFor has
returned more than one possibility, we compare the offset nanoseconds of
each possibility. Also don't look up the method twice when doing this.
---
polyfill/lib/ecmascript.mjs | 22 ++++++++++++++--------
spec/timezone.html | 8 +++++---
spec/zoneddatetime.html | 17 ++++++++++-------
3 files changed, 29 insertions(+), 18 deletions(-)
diff --git a/polyfill/lib/ecmascript.mjs b/polyfill/lib/ecmascript.mjs
index 6310162675..71891dadab 100644
--- a/polyfill/lib/ecmascript.mjs
+++ b/polyfill/lib/ecmascript.mjs
@@ -1500,12 +1500,16 @@ export function InterpretISODateTimeOffset(
// "prefer" or "reject"
const possibleInstants = GetPossibleInstantsFor(timeZone, dt);
- for (let index = 0; index < possibleInstants.length; index++) {
- const candidate = possibleInstants[index];
- const candidateOffset = GetOffsetNanosecondsFor(timeZone, candidate);
- const roundedCandidateOffset = RoundNumberToIncrement(bigInt(candidateOffset), 60e9, 'halfExpand').toJSNumber();
- if (candidateOffset === offsetNs || (matchMinute && roundedCandidateOffset === offsetNs)) {
- return GetSlot(candidate, EPOCHNANOSECONDS);
+ if (possibleInstants.length > 0) {
+ const getOffsetNanosecondsFor =
+ typeof timeZone !== 'string' ? GetMethod(timeZone, 'getOffsetNanosecondsFor') : undefined;
+ for (let index = 0; index < possibleInstants.length; index++) {
+ const candidate = possibleInstants[index];
+ const candidateOffset = GetOffsetNanosecondsFor(timeZone, candidate, getOffsetNanosecondsFor);
+ const roundedCandidateOffset = RoundNumberToIncrement(bigInt(candidateOffset), 60e9, 'halfExpand').toJSNumber();
+ if (candidateOffset === offsetNs || (matchMinute && roundedCandidateOffset === offsetNs)) {
+ return GetSlot(candidate, EPOCHNANOSECONDS);
+ }
}
}
@@ -2424,8 +2428,10 @@ export function DisambiguatePossibleInstants(possibleInstants, timeZone, dateTim
const dayBefore = new Instant(utcns.minus(DAY_NANOS));
const dayAfter = new Instant(utcns.plus(DAY_NANOS));
- const offsetBefore = GetOffsetNanosecondsFor(timeZone, dayBefore);
- const offsetAfter = GetOffsetNanosecondsFor(timeZone, dayAfter);
+ const getOffsetNanosecondsFor =
+ typeof timeZone !== 'string' ? GetMethod(timeZone, 'getOffsetNanosecondsFor') : undefined;
+ const offsetBefore = GetOffsetNanosecondsFor(timeZone, dayBefore, getOffsetNanosecondsFor);
+ const offsetAfter = GetOffsetNanosecondsFor(timeZone, dayAfter, getOffsetNanosecondsFor);
const nanoseconds = offsetAfter - offsetBefore;
switch (disambiguation) {
case 'earlier': {
diff --git a/spec/timezone.html b/spec/timezone.html
index 629d5296dd..5f5003bee4 100644
--- a/spec/timezone.html
+++ b/spec/timezone.html
@@ -642,7 +642,7 @@
GetOffsetNanosecondsFor (
_timeZone_: a String or Object,
_instant_: a Temporal.Instant,
- optional _getOffsetNanosecondsFor_: a function object,
+ optional _getOffsetNanosecondsFor_: a function object or ~unused~,
): either a normal completion containing an integer, or an abrupt completion