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 +

+
+
description
+
+ The returned Record has fields [[EpochNanoseconds]] (a BigInt), [[Instant]] (a Temporal.Instant), and [[DateTime]] (a Temporal.PlainDateTime). +
+
+

+ 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

@@ -1341,13 +1346,14 @@

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 _overflow_ is not present, set _overflow_ to *"constrain"*. 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 _addedDate_ be ? AddISODate(_dateTime_.[[ISOYear]], _dateTime_.[[ISOMonth]], _dateTime_.[[ISODay]], 0, 0, 0, _days_, _overflow_). 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 { From a69e67e5b0104e72934889b5e4fea189eb04e381 Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Wed, 8 Mar 2023 17:57:13 -0800 Subject: [PATCH 03/10] Normative: Avoid calendar operations when adding days-only duration to PlainDate In a few places, we called CalendarDateAdd with a days-only duration. For days-only, it's not necessary to consult the calendar: we can just add the days in the ISO calendar space to the ISO calendar values in the internal slots. (also fixes a rebase error with truncate(_fractionalDays_)) Closes: #2685 --- polyfill/lib/ecmascript.mjs | 105 ++++++++++++++++-------------------- spec/duration.html | 4 +- spec/plainyearmonth.html | 4 +- spec/timezone.html | 10 ++-- 4 files changed, 55 insertions(+), 68 deletions(-) diff --git a/polyfill/lib/ecmascript.mjs b/polyfill/lib/ecmascript.mjs index f3d3a2e570..f2a4849ad8 100644 --- a/polyfill/lib/ecmascript.mjs +++ b/polyfill/lib/ecmascript.mjs @@ -2429,82 +2429,49 @@ export function DisambiguatePossibleInstants(possibleInstants, timeZone, dateTim const nanoseconds = offsetAfter - offsetBefore; switch (disambiguation) { case 'earlier': { - const calendar = GetSlot(dateTime, CALENDAR); - const earlier = AddDateTime( - year, - month, - day, + const earlierTime = AddTime( hour, minute, second, millisecond, microsecond, nanosecond, - calendar, - 0, - 0, - 0, 0, 0, 0, 0, 0, - 0, - -nanoseconds, - undefined + -nanoseconds ); + const earlierDate = AddISODate(year, month, day, 0, 0, 0, earlierTime.deltaDays, 'constrain'); const earlierPlainDateTime = CreateTemporalDateTime( - earlier.year, - earlier.month, - earlier.day, - earlier.hour, - earlier.minute, - earlier.second, - earlier.millisecond, - earlier.microsecond, - earlier.nanosecond, - calendar + earlierDate.year, + earlierDate.month, + earlierDate.day, + earlierTime.hour, + earlierTime.minute, + earlierTime.second, + earlierTime.millisecond, + earlierTime.microsecond, + earlierTime.nanosecond ); return GetPossibleInstantsFor(timeZone, earlierPlainDateTime)[0]; } case 'compatible': // fall through because 'compatible' means 'later' for "spring forward" transitions case 'later': { - const calendar = GetSlot(dateTime, CALENDAR); - const later = AddDateTime( - year, - month, - day, - hour, - minute, - second, - millisecond, - microsecond, - nanosecond, - calendar, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - nanoseconds, - undefined - ); + const laterTime = AddTime(hour, minute, second, millisecond, microsecond, nanosecond, 0, 0, 0, 0, 0, nanoseconds); + const laterDate = AddISODate(year, month, day, 0, 0, 0, laterTime.deltaDays, 'constrain'); const laterPlainDateTime = CreateTemporalDateTime( - later.year, - later.month, - later.day, - later.hour, - later.minute, - later.second, - later.millisecond, - later.microsecond, - later.nanosecond, - calendar + laterDate.year, + laterDate.month, + laterDate.day, + laterTime.hour, + laterTime.minute, + laterTime.second, + laterTime.millisecond, + laterTime.microsecond, + laterTime.nanosecond ); const possible = GetPossibleInstantsFor(timeZone, laterPlainDateTime); return possible[possible.length - 1]; @@ -5157,8 +5124,17 @@ export function AddDurationToOrSubtractDurationFromPlainYearMonth(operation, yea if (sign < 0) { const oneMonthDuration = new Duration(0, 1, 0, 0, 0, 0, 0, 0, 0, 0); const nextMonth = CalendarDateAdd(calendar, startDate, oneMonthDuration, undefined, dateAdd); - const minusDayDuration = new Duration(0, 0, 0, -1, 0, 0, 0, 0, 0, 0); - const endOfMonth = CalendarDateAdd(calendar, nextMonth, minusDayDuration, undefined, dateAdd); + const endOfMonthISO = AddISODate( + GetSlot(nextMonth, ISO_YEAR), + GetSlot(nextMonth, ISO_MONTH), + GetSlot(nextMonth, ISO_DAY), + 0, + 0, + 0, + -1, + 'constrain' + ); + const endOfMonth = CreateTemporalDate(endOfMonthISO.year, endOfMonthISO.month, endOfMonthISO.day, calendar); fieldsCopy.day = CalendarDay(calendar, endOfMonth); startDate = CalendarDateFromFields(calendar, fieldsCopy); } @@ -5545,8 +5521,17 @@ export function RoundDuration( plainRelativeTo = yearsLater; days += monthsWeeksInDays; - const wholeDays = new TemporalDuration(0, 0, 0, days); - const wholeDaysLater = CalendarDateAdd(calendar, plainRelativeTo, wholeDays, undefined, dateAdd); + const isoResult = AddISODate( + GetSlot(plainRelativeTo, ISO_YEAR), + GetSlot(plainRelativeTo, ISO_MONTH), + GetSlot(plainRelativeTo, ISO_DAY), + 0, + 0, + 0, + days, + 'constrain' + ); + 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); diff --git a/spec/duration.html b/spec/duration.html index 6eb318ae9f..1335699df5 100644 --- a/spec/duration.html +++ b/spec/duration.html @@ -1748,8 +1748,8 @@

1. Let _monthsWeeksInDays_ be DaysUntil(_yearsLater_, _yearsMonthsWeeksLater_). 1. Set _plainRelativeTo_ to _yearsLater_. 1. Set _fractionalDays_ to _fractionalDays_ + _monthsWeeksInDays_. - 1. Let _wholeDaysDuration_ be ? CreateTemporalDuration(0, 0, 0, truncate(_days_), 0, 0, 0, 0, 0, 0). - 1. Let _wholeDaysLater_ be ? CalendarDateAdd(_calendar_, _plainRelativeTo_, _wholeDaysDuration_, *undefined*, _dateAdd_). + 1. Let _isoResult_ be ! AddISODate(_plainRelativeTo_.[[ISOYear]]. _plainRelativeTo_.[[ISOMonth]], _plainRelativeTo_.[[ISODay]], 0, 0, 0, truncate(_fractionalDays_), *"constrain"*). + 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_). diff --git a/spec/plainyearmonth.html b/spec/plainyearmonth.html index 90e719b8cf..6466f32af6 100644 --- a/spec/plainyearmonth.html +++ b/spec/plainyearmonth.html @@ -680,8 +680,8 @@

1. If _sign_ < 0, then 1. Let _oneMonthDuration_ be ! CreateTemporalDuration(0, 1, 0, 0, 0, 0, 0, 0, 0, 0). 1. Let _nextMonth_ be ? CalendarDateAdd(_calendar_, _intermediateDate_, _oneMonthDuration_, *undefined*, _dateAdd_). - 1. Let _minusDayDuration_ be ! CreateTemporalDuration(0, 0, 0, -1, 0, 0, 0, 0, 0, 0). - 1. Let _endOfMonth_ be ? CalendarDateAdd(_calendar_, _nextMonth_, _minusDayDuration_, *undefined*, _dateAdd_). + 1. Let _endOfMonthISO_ be ! AddISODate(_nextMonth_.[[ISOYear]], _nextMonth_.[[ISOMonth]], _nextMonth_.[[ISODay]], 0, 0, 0, -1, *"constrain"*). + 1. Let _endOfMonth_ be ! CreateTemporalDate(_endOfMonthISO_.[[Year]], _endOfMonthISO_.[[Month]], _endOfMonthISO_.[[Day]], _calendar_). 1. Let _day_ be ? CalendarDay(_calendar_, _endOfMonth_). 1. Perform ! CreateDataPropertyOrThrow(_fieldsCopy_, *"day"*, _day_). 1. Let _date_ be ? CalendarDateFromFields(_calendar_, _fieldsCopy_). diff --git a/spec/timezone.html b/spec/timezone.html index 4a746ac9ab..629d5296dd 100644 --- a/spec/timezone.html +++ b/spec/timezone.html @@ -768,14 +768,16 @@

DisambiguatePossibleInstants ( _possibleInstants_, _timeZone_, _dateTime_, _ 1. Let _offsetAfter_ be ? GetOffsetNanosecondsFor(_timeZone_, _dayAfter_). 1. Let _nanoseconds_ be _offsetAfter_ - _offsetBefore_. 1. If _disambiguation_ is *"earlier"*, then - 1. Let _earlier_ be ? AddDateTime(_dateTime_.[[ISOYear]], _dateTime_.[[ISOMonth]], _dateTime_.[[ISODay]], _dateTime_.[[ISOHour]], _dateTime_.[[ISOMinute]], _dateTime_.[[ISOSecond]], _dateTime_.[[ISOMillisecond]], _dateTime_.[[ISOMicrosecond]], _dateTime_.[[ISONanosecond]], _dateTime_.[[Calendar]], 0, 0, 0, 0, 0, 0, 0, 0, 0, -_nanoseconds_, *undefined*). - 1. Let _earlierDateTime_ be ! CreateTemporalDateTime(_earlier_.[[Year]], _earlier_.[[Month]], _earlier_.[[Day]], _earlier_.[[Hour]], _earlier_.[[Minute]], _earlier_.[[Second]], _earlier_.[[Millisecond]], _earlier_.[[Microsecond]], _earlier_.[[Nanosecond]], _dateTime_.[[Calendar]]). + 1. Let _earlierTime_ be AddTime(_dateTime_.[[ISOHour]], _dateTime_.[[ISOMinute]], _dateTime_.[[ISOSecond]], _dateTime_.[[ISOMillisecond]], _dateTime_.[[ISOMicrosecond]], _dateTime_.[[ISONanosecond]], 0, 0, 0, 0, 0, -_nanoseconds_). + 1. Let _earlierDate_ be AddISODate(_dateTime_.[[ISOYear]], _dateTime_.[[ISOMonth]], _dateTime_.[[ISODay]], 0, 0, 0, _earlierTime_.[[Days]], *"constrain"*). + 1. Let _earlierDateTime_ be ! CreateTemporalDateTime(_earlierDate_.[[Year]], _earlierDate_.[[Month]], _earlierDate_.[[Day]], _earlierTime_.[[Hour]], _earlierTime_.[[Minute]], _earlierTime_.[[Second]], _earlierTime_.[[Millisecond]], _earlierTime_.[[Microsecond]], _earlierTime_.[[Nanosecond]], *"iso8601"*). 1. Set _possibleInstants_ to ? GetPossibleInstantsFor(_timeZone_, _earlierDateTime_). 1. If _possibleInstants_ is empty, throw a *RangeError* exception. 1. Return _possibleInstants_[0]. 1. Assert: _disambiguation_ is *"compatible"* or *"later"*. - 1. Let _later_ be ? AddDateTime(_dateTime_.[[ISOYear]], _dateTime_.[[ISOMonth]], _dateTime_.[[ISODay]], _dateTime_.[[ISOHour]], _dateTime_.[[ISOMinute]], _dateTime_.[[ISOSecond]], _dateTime_.[[ISOMillisecond]], _dateTime_.[[ISOMicrosecond]], _dateTime_.[[ISONanosecond]], _dateTime_.[[Calendar]], 0, 0, 0, 0, 0, 0, 0, 0, 0, _nanoseconds_, *undefined*). - 1. Let _laterDateTime_ be ! CreateTemporalDateTime(_later_.[[Year]], _later_.[[Month]], _later_.[[Day]], _later_.[[Hour]], _later_.[[Minute]], _later_.[[Second]], _later_.[[Millisecond]], _later_.[[Microsecond]], _later_.[[Nanosecond]], _dateTime_.[[Calendar]]). + 1. Let _laterTime_ be AddTime(_dateTime_.[[ISOHour]], _dateTime_.[[ISOMinute]], _dateTime_.[[ISOSecond]], _dateTime_.[[ISOMillisecond]], _dateTime_.[[ISOMicrosecond]], _dateTime_.[[ISONanosecond]], 0, 0, 0, 0, 0, _nanoseconds_). + 1. Let _laterDate_ be AddISODate(_dateTime_.[[ISOYear]], _dateTime_.[[ISOMonth]], _dateTime_.[[ISODay]], 0, 0, 0, _laterTime_.[[Days]], *"constrain"*). + 1. Let _laterDateTime_ be ! CreateTemporalDateTime(_laterDate_.[[Year]], _laterDate_.[[Month]], _laterDate_.[[Day]], _laterTime_.[[Hour]], _laterTime_.[[Minute]], _laterTime_.[[Second]], _laterTime_.[[Millisecond]], _laterTime_.[[Microsecond]], _laterTime_.[[Nanosecond]], _dateTime_.[[Calendar]]). 1. Set _possibleInstants_ to ? GetPossibleInstantsFor(_timeZone_, _laterDateTime_). 1. Set _n_ to _possibleInstants_'s length. 1. If _n_ = 0, throw a *RangeError* exception. From 42d60b67993217677fc91b68a057ab531de61be2 Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Wed, 8 Mar 2023 21:58:48 -0800 Subject: [PATCH 04/10] Normative: Fast-path dateAdd() when adding only days Introduce an operation AddDate, which has a fast-path through the ISO calendar in the case where we would otherwise call calendar.dateAdd() with years, months, and weeks all zero. --- polyfill/lib/ecmascript.mjs | 50 +++++++++++++++++++++++++++++-------- polyfill/lib/plaindate.mjs | 4 +-- spec/duration.html | 18 +++++++------ spec/plaindate.html | 36 ++++++++++++++++++++++++-- spec/plaindatetime.html | 2 +- spec/plainyearmonth.html | 2 +- 6 files changed, 88 insertions(+), 24 deletions(-) diff --git a/polyfill/lib/ecmascript.mjs b/polyfill/lib/ecmascript.mjs index f2a4849ad8..77fc15093f 100644 --- a/polyfill/lib/ecmascript.mjs +++ b/polyfill/lib/ecmascript.mjs @@ -4630,6 +4630,33 @@ export function AddISODate(year, month, day, years, months, weeks, days, overflo return { year, month, day }; } +export function AddDate(calendar, plainDate, duration, options = undefined, dateAdd = undefined) { + const years = GetSlot(duration, YEARS); + const months = GetSlot(duration, MONTHS); + const weeks = GetSlot(duration, WEEKS); + if (years !== 0 || months !== 0 || weeks !== 0) { + return CalendarDateAdd(calendar, plainDate, duration, options, dateAdd); + } + + // Fast path skipping the calendar call if we are only adding days + let year = GetSlot(plainDate, ISO_YEAR); + let month = GetSlot(plainDate, ISO_MONTH); + let day = GetSlot(plainDate, ISO_DAY); + const overflow = ToTemporalOverflow(options); + const { days } = BalanceTimeDuration( + GetSlot(duration, DAYS), + GetSlot(duration, HOURS), + GetSlot(duration, MINUTES), + GetSlot(duration, SECONDS), + GetSlot(duration, MILLISECONDS), + GetSlot(duration, MICROSECONDS), + GetSlot(duration, NANOSECONDS), + 'day' + ); + ({ year, month, day } = AddISODate(year, month, day, 0, 0, 0, days, overflow)); + return CreateTemporalDate(year, month, day, calendar); +} + export function AddTime( hour, minute, @@ -4712,9 +4739,12 @@ export function AddDuration( const dateDuration1 = new TemporalDuration(y1, mon1, w1, d1, 0, 0, 0, 0, 0, 0); const dateDuration2 = new TemporalDuration(y2, mon2, w2, d2, 0, 0, 0, 0, 0, 0); - const dateAdd = typeof calendar !== 'string' ? GetMethod(calendar, 'dateAdd') : undefined; - const intermediate = CalendarDateAdd(calendar, plainRelativeTo, dateDuration1, undefined, dateAdd); - const end = CalendarDateAdd(calendar, intermediate, dateDuration2, undefined, dateAdd); + let dateAdd; + if (typeof calendar !== 'string' && (y1 !== 0 || mon1 !== 0 || w1 !== 0 || y2 !== 0 || mon2 !== 0 || w2 !== 0)) { + dateAdd = GetMethod(calendar, 'dateAdd'); + } + const intermediate = AddDate(calendar, plainRelativeTo, dateDuration1, undefined, dateAdd); + const end = AddDate(calendar, intermediate, dateDuration2, undefined, dateAdd); const dateLargestUnit = LargerOfTwoTemporalUnits('day', largestUnit); const differenceOptions = ObjectCreate(null); @@ -4860,7 +4890,7 @@ export function AddDateTime( const TemporalDuration = GetIntrinsic('%Temporal.Duration%'); const datePart = CreateTemporalDate(year, month, day, calendar); const dateDuration = new TemporalDuration(years, months, weeks, days, 0, 0, 0, 0, 0, 0); - const addedDate = CalendarDateAdd(calendar, datePart, dateDuration, options); + const addedDate = AddDate(calendar, datePart, dateDuration, options); return { year: GetSlot(addedDate, ISO_YEAR), @@ -5141,7 +5171,7 @@ export function AddDurationToOrSubtractDurationFromPlainYearMonth(operation, yea const durationToAdd = new Duration(years, months, weeks, days, 0, 0, 0, 0, 0, 0); options = GetOptionsObject(options); const optionsCopy = SnapshotOwnProperties(options, null); - const addedDate = CalendarDateAdd(calendar, startDate, durationToAdd, options, dateAdd); + const addedDate = AddDate(calendar, startDate, durationToAdd, options, dateAdd); const addedDateFields = PrepareTemporalFields(addedDate, fieldNames, []); return CalendarYearMonthFromFields(calendar, addedDateFields, optionsCopy); @@ -5323,7 +5353,7 @@ export function DaysUntil(earlier, later) { } export function MoveRelativeDate(calendar, relativeTo, duration, dateAdd) { - const later = CalendarDateAdd(calendar, relativeTo, duration, undefined, dateAdd); + const later = AddDate(calendar, relativeTo, duration, undefined, dateAdd); const days = DaysUntil(relativeTo, later); return { relativeTo: later, days }; } @@ -5514,9 +5544,9 @@ export function RoundDuration( // relativeTo + years, relativeTo + { years, months, weeks }) const yearsDuration = new TemporalDuration(years); const dateAdd = typeof calendar !== 'string' ? GetMethod(calendar, 'dateAdd') : undefined; - const yearsLater = CalendarDateAdd(calendar, plainRelativeTo, yearsDuration, undefined, dateAdd); + const yearsLater = AddDate(calendar, plainRelativeTo, yearsDuration, undefined, dateAdd); const yearsMonthsWeeks = new TemporalDuration(years, months, weeks); - const yearsMonthsWeeksLater = CalendarDateAdd(calendar, plainRelativeTo, yearsMonthsWeeks, undefined, dateAdd); + const yearsMonthsWeeksLater = AddDate(calendar, plainRelativeTo, yearsMonthsWeeks, undefined, dateAdd); const monthsWeeksInDays = DaysUntil(yearsLater, yearsMonthsWeeksLater); plainRelativeTo = yearsLater; days += monthsWeeksInDays; @@ -5571,9 +5601,9 @@ export function RoundDuration( // { years, months }, relativeTo + { years, months, weeks }) const yearsMonths = new TemporalDuration(years, months); const dateAdd = typeof calendar !== 'string' ? GetMethod(calendar, 'dateAdd') : undefined; - const yearsMonthsLater = CalendarDateAdd(calendar, plainRelativeTo, yearsMonths, undefined, dateAdd); + const yearsMonthsLater = AddDate(calendar, plainRelativeTo, yearsMonths, undefined, dateAdd); const yearsMonthsWeeks = new TemporalDuration(years, months, weeks); - const yearsMonthsWeeksLater = CalendarDateAdd(calendar, plainRelativeTo, yearsMonthsWeeks, undefined, dateAdd); + const yearsMonthsWeeksLater = AddDate(calendar, plainRelativeTo, yearsMonthsWeeks, undefined, dateAdd); const weeksInDays = DaysUntil(yearsMonthsLater, yearsMonthsWeeksLater); plainRelativeTo = yearsMonthsLater; days += weeksInDays; diff --git a/polyfill/lib/plaindate.mjs b/polyfill/lib/plaindate.mjs index 0a419bcc7e..ddc7b117c2 100644 --- a/polyfill/lib/plaindate.mjs +++ b/polyfill/lib/plaindate.mjs @@ -117,7 +117,7 @@ export class PlainDate { const duration = ES.ToTemporalDuration(temporalDurationLike); options = ES.GetOptionsObject(options); - return ES.CalendarDateAdd(GetSlot(this, CALENDAR), this, duration, options); + return ES.AddDate(GetSlot(this, CALENDAR), this, duration, options); } subtract(temporalDurationLike, options = undefined) { if (!ES.IsTemporalDate(this)) throw new TypeError('invalid receiver'); @@ -125,7 +125,7 @@ export class PlainDate { const duration = ES.CreateNegatedTemporalDuration(ES.ToTemporalDuration(temporalDurationLike)); options = ES.GetOptionsObject(options); - return ES.CalendarDateAdd(GetSlot(this, CALENDAR), this, duration, options); + return ES.AddDate(GetSlot(this, CALENDAR), this, duration, options); } until(other, options = undefined) { if (!ES.IsTemporalDate(this)) throw new TypeError('invalid receiver'); diff --git a/spec/duration.html b/spec/duration.html index 1335699df5..45966810a7 100644 --- a/spec/duration.html +++ b/spec/duration.html @@ -1599,12 +1599,14 @@

1. Let _calendar_ be _plainRelativeTo_.[[Calendar]]. 1. Let _dateDuration1_ be ! CreateTemporalDuration(_y1_, _mon1_, _w1_, _d1_, 0, 0, 0, 0, 0, 0). 1. Let _dateDuration2_ be ! CreateTemporalDuration(_y2_, _mon2_, _w2_, _d2_, 0, 0, 0, 0, 0, 0). - 1. If _calendar_ is an Object, then + 1. Let _calendarUnitsPresent_ be *true*. + 1. If _y1_ = 0 and _mon1_ = 0 and _w1_ = 0 and _y2_ = 0 and _mon2_ = 0 and _w2_ = 0, set _calendarUnitsPresent_ to *false*. + 1. If _calendar_ is an Object and _calendarUnitsPresent_ is *true*, then 1. Let _dateAdd_ be ? GetMethod(_calendar_, *"dateAdd"*). 1. Else, 1. Let _dateAdd_ be ~unused~. - 1. Let _intermediate_ be ? CalendarDateAdd(_calendar_, _plainRelativeTo_, _dateDuration1_, *undefined*, _dateAdd_). - 1. Let _end_ be ? CalendarDateAdd(_calendar_, _intermediate_, _dateDuration2_, *undefined*, _dateAdd_). + 1. Let _intermediate_ be ? AddDate(_calendar_, _plainRelativeTo_, _dateDuration1_, *undefined*, _dateAdd_). + 1. Let _end_ be ? AddDate(_calendar_, _intermediate_, _dateDuration2_, *undefined*, _dateAdd_). 1. Let _dateLargestUnit_ be ! LargerOfTwoTemporalUnits(*"day"*, _largestUnit_). 1. Let _differenceOptions_ be OrdinaryObjectCreate(*null*). 1. Perform ! CreateDataPropertyOrThrow(_differenceOptions_, *"largestUnit"*, _dateLargestUnit_). @@ -1659,7 +1661,7 @@

- 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 +

+
+
description
+
+ It adds _duration_ to _plainDate_. + Usually this is accomplished by calling the `dateAdd` method of the given _calendar_ as in CalendarDateAdd, but if the duration only contains days, the computation will not cause an observable method call. + If _dateAdd_ is present, the `dateAdd` method will not be observably looked up. +
+
+ + 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 +

+
+
description
+
+ It determines the difference between the dates _one_ and _two_. + Usually this is accomplished by calling the `dateUntil` method of the given _calendar_ as in CalendarDateUntil, but if the dates are equal or no units greater than days are needed in the difference, the computation will not cause an observable method call. +
+
+ + 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

@@ -764,8 +764,10 @@

DisambiguatePossibleInstants ( _possibleInstants_, _timeZone_, _dateTime_, _ 1. Let _dayAfterNs_ be _epochNanoseconds_ + ℤ(nsPerDay). 1. If IsValidEpochNanoseconds(_dayAfterNs_) is *false*, throw a *RangeError* exception. 1. Let _dayAfter_ be ! CreateTemporalInstant(_dayAfterNs_). - 1. Let _offsetBefore_ be ? GetOffsetNanosecondsFor(_timeZone_, _dayBefore_). - 1. Let _offsetAfter_ be ? GetOffsetNanosecondsFor(_timeZone_, _dayAfter_). + 1. Let _getOffsetNanosecondsFor_ be ~unused~. + 1. If _timeZone_ is an Object, set _getOffsetNanosecondsFor_ to ? GetMethod(_timeZone_, *"getOffsetNanosecondsFor"*). + 1. Let _offsetBefore_ be ? GetOffsetNanosecondsFor(_timeZone_, _dayBefore_, _getOffsetNanosecondsFor_). + 1. Let _offsetAfter_ be ? GetOffsetNanosecondsFor(_timeZone_, _dayAfter_, _getOffsetNanosecondsFor_). 1. Let _nanoseconds_ be _offsetAfter_ - _offsetBefore_. 1. If _disambiguation_ is *"earlier"*, then 1. Let _earlierTime_ be AddTime(_dateTime_.[[ISOHour]], _dateTime_.[[ISOMinute]], _dateTime_.[[ISOSecond]], _dateTime_.[[ISOMillisecond]], _dateTime_.[[ISOMicrosecond]], _dateTime_.[[ISONanosecond]], 0, 0, 0, 0, 0, -_nanoseconds_). diff --git a/spec/zoneddatetime.html b/spec/zoneddatetime.html index 1b6a0077c9..792e8ea467 100644 --- a/spec/zoneddatetime.html +++ b/spec/zoneddatetime.html @@ -1129,14 +1129,17 @@

1. Assert: _offsetBehaviour_ is ~option~. 1. Assert: _offsetOption_ is *"prefer"* or *"reject"*. 1. Let _possibleInstants_ be ? GetPossibleInstantsFor(_timeZone_, _dateTime_). - 1. For each element _candidate_ of _possibleInstants_, do - 1. Let _candidateNanoseconds_ be ? GetOffsetNanosecondsFor(_timeZone_, _candidate_). - 1. If _candidateNanoseconds_ = _offsetNanoseconds_, then - 1. Return _candidate_.[[Nanoseconds]]. - 1. If _matchBehaviour_ is ~match minutes~, then - 1. Let _roundedCandidateNanoseconds_ be RoundNumberToIncrement(_candidateNanoseconds_, 60 × 109, *"halfExpand"*). - 1. If _roundedCandidateNanoseconds_ = _offsetNanoseconds_, then + 1. If _possibleInstants_ is not empty, then + 1. Let _getOffsetNanosecondsFor_ be ~unused~. + 1. If _timeZone_ is an Object, set _getOffsetNanosecondsFor_ to ? GetMethod(_timeZone_, *"getOffsetNanosecondsFor"*). + 1. For each element _candidate_ of _possibleInstants_, do + 1. Let _candidateNanoseconds_ be ? GetOffsetNanosecondsFor(_timeZone_, _candidate_, _getOffsetNanosecondsFor_). + 1. If _candidateNanoseconds_ = _offsetNanoseconds_, then 1. Return _candidate_.[[Nanoseconds]]. + 1. If _matchBehaviour_ is ~match minutes~, then + 1. Let _roundedCandidateNanoseconds_ be RoundNumberToIncrement(_candidateNanoseconds_, 60 × 109, *"halfExpand"*). + 1. If _roundedCandidateNanoseconds_ = _offsetNanoseconds_, then + 1. Return _candidate_.[[Nanoseconds]]. 1. If _offsetOption_ is *"reject"*, throw a *RangeError* exception. 1. Let _instant_ be ? DisambiguatePossibleInstants(_possibleInstants_, _timeZone_, _dateTime_, _disambiguation_). 1. Return _instant_.[[Nanoseconds]]. From 99c57408892f6205a071a97947986b25d6fa1e83 Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Wed, 13 Sep 2023 17:06:22 -0700 Subject: [PATCH 08/10] Normative: Precalculate PlainDateTime from ZonedDateTime in more places There are a few more places where we can avoid doing an additional lookup and call of getOffsetNanosecondsFor on the same ZonedDateTime, to convert it into a PlainDateTime. This affects - Temporal.Duration.prototype.add (with relativeTo ZonedDateTime) - Temporal.Duration.prototype.subtract (ditto) - Temporal.Duration.prototype.round (ditto) - Temporal.Duration.prototype.total (ditto) - Temporal.ZonedDateTime.prototype.since - Temporal.ZonedDateTime.prototype.until (also fixes "and" vs "or" prose mistakes) Closes: #2680 Closes: #2681 --- polyfill/lib/duration.mjs | 69 ++++++++++++++++----- polyfill/lib/ecmascript.mjs | 120 +++++++++++++++++++++++++++--------- spec/duration.html | 72 ++++++++++++++-------- spec/zoneddatetime.html | 32 ++++++---- 4 files changed, 211 insertions(+), 82 deletions(-) diff --git a/polyfill/lib/duration.mjs b/polyfill/lib/duration.mjs index 2e618b01d6..c5dea54aa7 100644 --- a/polyfill/lib/duration.mjs +++ b/polyfill/lib/duration.mjs @@ -300,18 +300,29 @@ export class Duration { return new Duration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds); } - const plainRelativeToWillBeUsed = + let precalculatedPlainDateTime; + const plainDateTimeOrRelativeToWillBeUsed = + !roundingGranularityIsNoop || + largestUnit === 'year' || + largestUnit === 'month' || + largestUnit === 'week' || + largestUnit === 'day' || smallestUnit === 'year' || smallestUnit === 'month' || smallestUnit === 'week' || - years !== 0 || - months !== 0 || - weeks !== 0 || + smallestUnit === 'day' || + calendarUnitsPresent || days !== 0; - if (zonedRelativeTo && plainRelativeToWillBeUsed) { - // Convert a ZonedDateTime relativeTo to PlainDate only if needed in one - // of the operations below, because the conversion is user visible - plainRelativeTo = ES.ToTemporalDate(zonedRelativeTo); + if (zonedRelativeTo && plainDateTimeOrRelativeToWillBeUsed) { + // Convert a ZonedDateTime relativeTo to PlainDateTime and PlainDate only + // if either is needed in one of the operations below, because the + // conversion is user visible + precalculatedPlainDateTime = ES.GetPlainDateTimeFor( + GetSlot(zonedRelativeTo, TIME_ZONE), + GetSlot(zonedRelativeTo, INSTANT), + GetSlot(zonedRelativeTo, CALENDAR) + ); + plainRelativeTo = ES.TemporalDateTimeToDate(precalculatedPlainDateTime); } ({ years, months, weeks, days } = ES.UnbalanceDateDurationRelative( @@ -338,7 +349,8 @@ export class Duration { smallestUnit, roundingMode, plainRelativeTo, - zonedRelativeTo + zonedRelativeTo, + precalculatedPlainDateTime )); if (zonedRelativeTo) { ({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = @@ -356,7 +368,8 @@ export class Duration { roundingIncrement, smallestUnit, roundingMode, - zonedRelativeTo + zonedRelativeTo, + precalculatedPlainDateTime )); ({ days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = ES.BalanceTimeDurationRelative( days, @@ -367,7 +380,8 @@ export class Duration { microseconds, nanoseconds, largestUnit, - zonedRelativeTo + zonedRelativeTo, + precalculatedPlainDateTime )); } else { ({ days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = ES.BalanceTimeDuration( @@ -416,12 +430,25 @@ export class Duration { let { plainRelativeTo, zonedRelativeTo } = ES.ToRelativeTemporalObject(totalOf); const unit = ES.GetTemporalUnit(totalOf, 'unit', 'datetime', ES.REQUIRED); - const plainRelativeToWillBeUsed = - unit === 'year' || unit === 'month' || unit === 'week' || years !== 0 || months !== 0 || weeks !== 0; - if (zonedRelativeTo !== undefined && plainRelativeToWillBeUsed) { + let precalculatedPlainDateTime; + const plainDateTimeOrRelativeToWillBeUsed = + unit === 'year' || + unit === 'month' || + unit === 'week' || + unit === 'day' || + years !== 0 || + months !== 0 || + weeks !== 0 || + days !== 0; + if (zonedRelativeTo && plainDateTimeOrRelativeToWillBeUsed) { // Convert a ZonedDateTime relativeTo to PlainDate only if needed in one // of the operations below, because the conversion is user visible - plainRelativeTo = ES.ToTemporalDate(zonedRelativeTo); + precalculatedPlainDateTime = ES.GetPlainDateTimeFor( + GetSlot(zonedRelativeTo, TIME_ZONE), + GetSlot(zonedRelativeTo, INSTANT), + GetSlot(zonedRelativeTo, CALENDAR) + ); + plainRelativeTo = ES.TemporalDateTimeToDate(precalculatedPlainDateTime); } // Convert larger units down to days @@ -436,7 +463,14 @@ export class Duration { // If the unit we're totalling is smaller than `days`, convert days down to that unit. let balanceResult; if (zonedRelativeTo) { - const intermediate = ES.MoveRelativeZonedDateTime(zonedRelativeTo, years, months, weeks, 0); + const intermediate = ES.MoveRelativeZonedDateTime( + zonedRelativeTo, + years, + months, + weeks, + 0, + precalculatedPlainDateTime + ); balanceResult = ES.BalancePossiblyInfiniteTimeDurationRelative( days, hours, @@ -482,7 +516,8 @@ export class Duration { unit, 'trunc', plainRelativeTo, - zonedRelativeTo + zonedRelativeTo, + precalculatedPlainDateTime ); return total; } diff --git a/polyfill/lib/ecmascript.mjs b/polyfill/lib/ecmascript.mjs index 71891dadab..ed08a39b7f 100644 --- a/polyfill/lib/ecmascript.mjs +++ b/polyfill/lib/ecmascript.mjs @@ -3261,7 +3261,7 @@ export function TotalDurationNanoseconds(hours, minutes, seconds, milliseconds, return bigInt(nanoseconds).add(microseconds.multiply(1000)); } -export function NanosecondsToDays(nanoseconds, zonedRelativeTo) { +export function NanosecondsToDays(nanoseconds, zonedRelativeTo, precalculatedPlainDateTime) { const TemporalInstant = GetIntrinsic('%Temporal.Instant%'); const sign = MathSign(nanoseconds); nanoseconds = bigInt(nanoseconds); @@ -3276,7 +3276,7 @@ export function NanosecondsToDays(nanoseconds, zonedRelativeTo) { // 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 dtStart = precalculatedPlainDateTime ?? GetPlainDateTimeFor(timeZone, start, 'iso8601'); const dtEnd = GetPlainDateTimeFor(timeZone, end, 'iso8601'); let hours, minutes, seconds, milliseconds, microseconds; ({ hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = DifferenceTime( @@ -3501,7 +3501,8 @@ export function BalanceTimeDurationRelative( microseconds, nanoseconds, largestUnit, - zonedRelativeTo + zonedRelativeTo, + precalculatedPlainDateTime ) { let result = BalancePossiblyInfiniteTimeDurationRelative( days, @@ -3512,7 +3513,8 @@ export function BalanceTimeDurationRelative( microseconds, nanoseconds, largestUnit, - zonedRelativeTo + zonedRelativeTo, + precalculatedPlainDateTime ); if (result === 'positive overflow' || result === 'negative overflow') { throw new RangeError('Duration out of range'); @@ -3529,23 +3531,32 @@ export function BalancePossiblyInfiniteTimeDurationRelative( microseconds, nanoseconds, largestUnit, - zonedRelativeTo + zonedRelativeTo, + precalculatedPlainDateTime ) { const startNs = GetSlot(zonedRelativeTo, EPOCHNANOSECONDS); + const startInstant = GetSlot(zonedRelativeTo, INSTANT); + const timeZone = GetSlot(zonedRelativeTo, TIME_ZONE); 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; + precalculatedPlainDateTime ??= GetPlainDateTimeFor(timeZone, startInstant, 'iso8601'); + intermediateNs = AddDaysToZonedDateTime( + startInstant, + precalculatedPlainDateTime, + 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') { - ({ days, nanoseconds } = NanosecondsToDays(nanoseconds, zonedRelativeTo)); + if (!nanoseconds.isZero()) precalculatedPlainDateTime ??= GetPlainDateTimeFor(timeZone, startInstant, 'iso8601'); + ({ days, nanoseconds } = NanosecondsToDays(nanoseconds, zonedRelativeTo, precalculatedPlainDateTime)); largestUnit = 'hour'; } else { days = 0; @@ -4193,7 +4204,15 @@ export function DifferenceISODateTime( return { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds }; } -export function DifferenceZonedDateTime(ns1, ns2, timeZone, calendar, largestUnit, options) { +export function DifferenceZonedDateTime( + ns1, + ns2, + timeZone, + calendar, + largestUnit, + options, + precalculatedDtStart = undefined +) { const nsDiff = ns2.subtract(ns1); if (nsDiff.isZero()) { return { @@ -4214,7 +4233,7 @@ export function DifferenceZonedDateTime(ns1, ns2, timeZone, calendar, largestUni const TemporalInstant = GetIntrinsic('%Temporal.Instant%'); const start = new TemporalInstant(ns1); const end = new TemporalInstant(ns2); - const dtStart = GetPlainDateTimeFor(timeZone, start, calendar); + const dtStart = precalculatedDtStart ?? GetPlainDateTimeFor(timeZone, start, calendar); const dtEnd = GetPlainDateTimeFor(timeZone, end, calendar); let { years, months, weeks, days } = DifferenceISODateTime( GetSlot(dtStart, ISO_YEAR), @@ -4641,13 +4660,35 @@ export function DifferenceTemporalZonedDateTime(operation, zonedDateTime, other, if (ns1.equals(ns2)) return new Duration(); + let precalculatedPlainDateTime; + let plainRelativeTo; + const roundingIsNoop = settings.smallestUnit === 'nanosecond' && settings.roundingIncrement === 1; + const plainDateTimeOrRelativeToWillBeUsed = + !roundingIsNoop || + settings.smallestUnit === 'year' || + settings.smallestUnit === 'month' || + settings.smallestUnit === 'week'; + if (plainDateTimeOrRelativeToWillBeUsed) { + precalculatedPlainDateTime = GetPlainDateTimeFor( + timeZone, + GetSlot(zonedDateTime, INSTANT), + GetSlot(zonedDateTime, CALENDAR) + ); + plainRelativeTo = TemporalDateTimeToDate(precalculatedPlainDateTime); + } + ({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = - DifferenceZonedDateTime(ns1, ns2, timeZone, calendar, settings.largestUnit, resolvedOptions)); + DifferenceZonedDateTime( + ns1, + ns2, + timeZone, + calendar, + settings.largestUnit, + resolvedOptions, + precalculatedPlainDateTime + )); - if (settings.smallestUnit !== 'nanosecond' || settings.roundingIncrement !== 1) { - const plainRelativeToWillBeUsed = - settings.smallestUnit === 'year' || settings.smallestUnit === 'month' || settings.smallestUnit === 'week'; - const plainRelativeTo = plainRelativeToWillBeUsed ? ToTemporalDate(zonedDateTime) : undefined; + if (!roundingIsNoop) { ({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = RoundDuration( years, months, @@ -4663,7 +4704,8 @@ export function DifferenceTemporalZonedDateTime(operation, zonedDateTime, other, settings.smallestUnit, settings.roundingMode, plainRelativeTo, - zonedDateTime + zonedDateTime, + precalculatedPlainDateTime )); ({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = AdjustRoundedDurationDays( @@ -4680,7 +4722,8 @@ export function DifferenceTemporalZonedDateTime(operation, zonedDateTime, other, settings.roundingIncrement, settings.smallestUnit, settings.roundingMode, - zonedDateTime + zonedDateTime, + precalculatedPlainDateTime )); } } @@ -4791,7 +4834,8 @@ export function AddDuration( µs2, ns2, plainRelativeTo, - zonedRelativeTo + zonedRelativeTo, + precalculatedPlainDateTime ) { const largestUnit1 = DefaultTemporalLargestUnit(y1, mon1, w1, d1, h1, min1, s1, ms1, µs1, ns1); const largestUnit2 = DefaultTemporalLargestUnit(y2, mon2, w2, d2, h2, min2, s2, ms2, µs2, ns2); @@ -4850,8 +4894,10 @@ export function AddDuration( const TemporalInstant = GetIntrinsic('%Temporal.Instant%'); const timeZone = GetSlot(zonedRelativeTo, TIME_ZONE); const calendar = GetSlot(zonedRelativeTo, CALENDAR); + const startInstant = GetSlot(zonedRelativeTo, INSTANT); + const startDateTime = precalculatedPlainDateTime ?? GetPlainDateTimeFor(timeZone, startInstant, calendar); const intermediateNs = AddZonedDateTime( - GetSlot(zonedRelativeTo, INSTANT), + startInstant, timeZone, calendar, y1, @@ -4863,7 +4909,8 @@ export function AddDuration( s1, ms1, µs1, - ns1 + ns1, + startDateTime ); const endNs = AddZonedDateTime( new TemporalInstant(intermediateNs), @@ -4902,7 +4949,8 @@ export function AddDuration( timeZone, calendar, largestUnit, - ObjectCreate(null) + ObjectCreate(null), + startDateTime )); } } @@ -5438,7 +5486,7 @@ export function MoveRelativeDate(calendar, relativeTo, duration, dateAdd) { return { relativeTo: later, days }; } -export function MoveRelativeZonedDateTime(relativeTo, years, months, weeks, days) { +export function MoveRelativeZonedDateTime(relativeTo, years, months, weeks, days, precalculatedPlainDateTime) { const timeZone = GetSlot(relativeTo, TIME_ZONE); const calendar = GetSlot(relativeTo, CALENDAR); const intermediateNs = AddZonedDateTime( @@ -5454,7 +5502,8 @@ export function MoveRelativeZonedDateTime(relativeTo, years, months, weeks, days 0, 0, 0, - 0 + 0, + precalculatedPlainDateTime ); return CreateTemporalZonedDateTime(intermediateNs, timeZone, calendar); } @@ -5473,7 +5522,8 @@ export function AdjustRoundedDurationDays( increment, unit, roundingMode, - zonedRelativeTo + zonedRelativeTo, + precalculatedPlainDateTime ) { if ( unit === 'year' || @@ -5511,7 +5561,8 @@ export function AdjustRoundedDurationDays( 0, 0, 0, - 0 + 0, + precalculatedPlainDateTime ); const TemporalInstant = GetIntrinsic('%Temporal.Instant%'); const dayStartInstant = new TemporalInstant(dayStart); @@ -5543,7 +5594,8 @@ export function AdjustRoundedDurationDays( 0, 0, /* plainRelativeTo = */ undefined, - zonedRelativeTo + zonedRelativeTo, + precalculatedPlainDateTime )); ({ hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = RoundDuration( years, @@ -5589,7 +5641,8 @@ export function RoundDuration( unit, roundingMode, plainRelativeTo = undefined, - zonedRelativeTo = undefined + zonedRelativeTo = undefined, + precalculatedPlainDateTime = undefined ) { const TemporalDuration = GetIntrinsic('%Temporal.Duration%'); @@ -5604,7 +5657,14 @@ export function RoundDuration( nanoseconds = TotalDurationNanoseconds(hours, minutes, seconds, milliseconds, microseconds, nanoseconds); let deltaDays; if (zonedRelativeTo) { - const intermediate = MoveRelativeZonedDateTime(zonedRelativeTo, years, months, weeks, days); + const intermediate = MoveRelativeZonedDateTime( + zonedRelativeTo, + years, + months, + weeks, + days, + precalculatedPlainDateTime + ); ({ days: deltaDays, nanoseconds, dayLengthNs } = NanosecondsToDays(nanoseconds, intermediate)); } else { ({ quotient: deltaDays, remainder: nanoseconds } = nanoseconds.divmod(DAY_NANOS)); diff --git a/spec/duration.html b/spec/duration.html index 36e0aab64a..e6a30c86db 100644 --- a/spec/duration.html +++ b/spec/duration.html @@ -454,18 +454,24 @@

Temporal.Duration.prototype.round ( _roundTo_ )

1. Let _hoursToDaysConversionMayOccur_ be *false*. 1. If _duration_.[[Days]] ≠ 0 and _zonedRelativeTo_ is not *undefined*, set _hoursToDaysConversionMayOccur_ to *true*. 1. Else if _duration_.[[Hours]] ≥ 24, set _hoursToDaysConversionMayOccur_ to *true*. - 1. If _smallestUnit_ is *"nanosecond"*, and _roundingIncrement_ = 1, and _largestUnit_ is _existingLargestUnit_, and _duration_.[[Years]] = 0, and _duration_.[[Months]] = 0, and _duration_.[[Weeks]] = 0, and _hoursToDaysConversionMayOccur_ is *false*, and _duration_.[[Minutes]] < 60, and _duration_.[[Seconds]] < 60, and _duration_.[[Milliseconds]] < 1000, and _duration_.[[Microseconds]] < 1000, and _duration_.[[Nanoseconds]] < 1000, then + 1. If _smallestUnit_ is *"nanosecond"* and _roundingIncrement_ = 1, let _roundingGranularityIsNoop_ be *true*; else let _roundingGranularityIsNoop_ be *false*. + 1. If _duration_.[[Years]] = 0 and _duration_.[[Months]] = 0 and _duration_.[[Weeks]] = 0, let _calendarUnitsPresent_ be *false*; else let _calendarUnitsPresent_ be *true*. + 1. If _roundingGranularityIsNoop_ is *true*, and _largestUnit_ is _existingLargestUnit_, and _calendarUnitsPresent_ is *false*, and _hoursToDaysConversionMayOccur_ is *false*, and _duration_.[[Minutes]] < 60, and _duration_.[[Seconds]] < 60, and _duration_.[[Milliseconds]] < 1000, and _duration_.[[Microseconds]] < 1000, and _duration_.[[Nanoseconds]] < 1000, then 1. NOTE: The above conditions mean that the operation will have no effect: the smallest unit and rounding increment will leave the total duration unchanged, and it can be determined without calling a calendar or time zone method that no balancing will take place. 1. Return ! CreateTemporalDuration(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _duration_.[[Days]], _duration_.[[Hours]], _duration_.[[Minutes]], _duration_.[[Seconds]], _duration_.[[Milliseconds]], _duration_.[[Microseconds]], _duration_.[[Nanoseconds]]). - 1. If _zonedRelativeTo_ is not *undefined*; and _smallestUnit_ is any of *"year"*, *"month"*, *"week"*; and any of _duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _duration_.[[Days]] ≠ 0; then - 1. NOTE: The above conditions mean that the corresponding `Temporal.PlainDate` for _zonedRelativeTo_ will be used in one of the operations below. - 1. Set _plainRelativeTo_ to ? ToTemporalDate(_zonedRelativeTo_). + 1. Let _precalculatedPlainDateTime_ be *undefined*. + 1. If _roundingGranularityIsNoop_ is *false*, or _largestUnit_ is *"year"*, or _largestUnit_ is *"month"*, or _largestUnit_ is *"week"*, or _largestUnit_ is *"day"*, or _smallestUnit_ is *"year"*, or _smallestUnit_ is *"month"*, or _smallestUnit_ is *"week"*, or _smallestUnit_ is *"day"*, or _calendarUnitsPresent_ is *true*, or _duration_.[[Days]] ≠ 0, let _plainDateTimeOrRelativeToWillBeUsed_ be *true*; else let _plainDateTimeOrRelativeToWillBeUsed_ be *false*. + 1. If _zonedRelativeTo_ is not *undefined* and _plainDateTimeOrRelativeToWillBeUsed_ is *true*, then + 1. NOTE: The above conditions mean that the corresponding `Temporal.PlainDateTime` or `Temporal.PlainDate` for _zonedRelativeTo_ will be used in one of the operations below. + 1. Let _instant_ be ! CreateTemporalInstant(_zonedRelativeTo_.[[Nanoseconds]]). + 1. Set _precalculatedPlainDateTime_ to ? GetPlainDateTimeFor(_zonedRelativeTo_.[[TimeZone]], _instant_, _zonedRelativeTo_.[[Calendar]]). + 1. Set _plainRelativeTo_ to ! CreateTemporalDate(_precalculatedPlainDateTime_.[[ISOYear]], _precalculatedPlainDateTime_.[[ISOMonth]], _precalculatedPlainDateTime_.[[ISODay]], _zonedRelativeTo_.[[Calendar]]). 1. Let _unbalanceResult_ be ? UnbalanceDateDurationRelative(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _duration_.[[Days]], _largestUnit_, _plainRelativeTo_). - 1. Let _roundRecord_ be ? RoundDuration(_unbalanceResult_.[[Years]], _unbalanceResult_.[[Months]], _unbalanceResult_.[[Weeks]], _unbalanceResult_.[[Days]], _duration_.[[Hours]], _duration_.[[Minutes]], _duration_.[[Seconds]], _duration_.[[Milliseconds]], _duration_.[[Microseconds]], _duration_.[[Nanoseconds]], _roundingIncrement_, _smallestUnit_, _roundingMode_, _plainRelativeTo_, _zonedRelativeTo_). + 1. Let _roundRecord_ be ? RoundDuration(_unbalanceResult_.[[Years]], _unbalanceResult_.[[Months]], _unbalanceResult_.[[Weeks]], _unbalanceResult_.[[Days]], _duration_.[[Hours]], _duration_.[[Minutes]], _duration_.[[Seconds]], _duration_.[[Milliseconds]], _duration_.[[Microseconds]], _duration_.[[Nanoseconds]], _roundingIncrement_, _smallestUnit_, _roundingMode_, _plainRelativeTo_, _zonedRelativeTo_, _precalculatedPlainDateTime_). 1. Let _roundResult_ be _roundRecord_.[[DurationRecord]]. 1. If _zonedRelativeTo_ is not *undefined*, then - 1. Set _roundResult_ to ? AdjustRoundedDurationDays(_roundResult_.[[Years]], _roundResult_.[[Months]], _roundResult_.[[Weeks]], _roundResult_.[[Days]], _roundResult_.[[Hours]], _roundResult_.[[Minutes]], _roundResult_.[[Seconds]], _roundResult_.[[Milliseconds]], _roundResult_.[[Microseconds]], _roundResult_.[[Nanoseconds]], _roundingIncrement_, _smallestUnit_, _roundingMode_, _zonedRelativeTo_). - 1. Let _balanceResult_ be ? BalanceTimeDurationRelative(_roundResult_.[[Days]], _roundResult_.[[Hours]], _roundResult_.[[Minutes]], _roundResult_.[[Seconds]], _roundResult_.[[Milliseconds]], _roundResult_.[[Microseconds]], _roundResult_.[[Nanoseconds]], _largestUnit_, _zonedRelativeTo_). + 1. Set _roundResult_ to ? AdjustRoundedDurationDays(_roundResult_.[[Years]], _roundResult_.[[Months]], _roundResult_.[[Weeks]], _roundResult_.[[Days]], _roundResult_.[[Hours]], _roundResult_.[[Minutes]], _roundResult_.[[Seconds]], _roundResult_.[[Milliseconds]], _roundResult_.[[Microseconds]], _roundResult_.[[Nanoseconds]], _roundingIncrement_, _smallestUnit_, _roundingMode_, _zonedRelativeTo_, _precalculatedPlainDateTime_). + 1. Let _balanceResult_ be ? BalanceTimeDurationRelative(_roundResult_.[[Days]], _roundResult_.[[Hours]], _roundResult_.[[Minutes]], _roundResult_.[[Seconds]], _roundResult_.[[Milliseconds]], _roundResult_.[[Microseconds]], _roundResult_.[[Nanoseconds]], _largestUnit_, _zonedRelativeTo_, _precalculatedPlainDateTime_). 1. Else, 1. Let _balanceResult_ be ? BalanceTimeDuration(_roundResult_.[[Days]], _roundResult_.[[Hours]], _roundResult_.[[Minutes]], _roundResult_.[[Seconds]], _roundResult_.[[Milliseconds]], _roundResult_.[[Microseconds]], _roundResult_.[[Nanoseconds]], _largestUnit_). 1. Let _result_ be ? BalanceDateDurationRelative(_roundResult_.[[Years]], _roundResult_.[[Months]], _roundResult_.[[Weeks]], _balanceResult_.[[Days]], _largestUnit_, _plainRelativeTo_). @@ -493,19 +499,23 @@

Temporal.Duration.prototype.total ( _totalOf_ )

1. Let _zonedRelativeTo_ be _relativeToRecord_.[[ZonedRelativeTo]]. 1. Let _plainRelativeTo_ be _relativeToRecord_.[[PlainRelativeTo]]. 1. Let _unit_ be ? GetTemporalUnit(_totalOf_, *"unit"*, ~datetime~, ~required~). - 1. If _zonedRelativeTo_ is not *undefined*; and _unit_ is any of *"year"*, *"month"*, *"week"*; and any of _duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]] ≠ 0; then - 1. NOTE: The above conditions mean that the corresponding `Temporal.PlainDate` for _zonedRelativeTo_ will be used in one of the operations below. - 1. Set _plainRelativeTo_ to ? ToTemporalDate(_zonedRelativeTo_). + 1. Let _precalculatedPlainDateTime_ be *undefined*. + 1. If _unit_ is *"year"*, or _unit_ is *"month"*, or _unit_ is *"week"*, or _unit_ is *"day"*, or _duration_.[[Years]] ≠ 0, or _duration_.[[Months]] ≠ 0, or _duration_.[[Weeks]] ≠ 0, or _duration_.[[Days]] ≠ 0, let _plainDateTimeOrRelativeToWillBeUsed_ be *true*; else let _plainDateTimeOrRelativeToWillBeUsed_ be *false*. + 1. If _zonedRelativeTo_ is not *undefined* and _plainDateTimeOrRelativeToWillBeUsed_ is *true*, then + 1. NOTE: The above conditions mean that the corresponding `Temporal.PlainDateTime` or `Temporal.PlainDate` for _zonedRelativeTo_ will be used in one of the operations below. + 1. Let _instant_ be ! CreateTemporalInstant(_zonedRelativeTo_.[[Nanoseconds]]). + 1. Set _precalculatedPlainDateTime_ to ? GetPlainDateTimeFor(_zonedRelativeTo_.[[TimeZone]], _instant_, _zonedRelativeTo_.[[Calendar]]). + 1. Set _plainRelativeTo_ to ! CreateTemporalDate(_precalculatedPlainDateTime_.[[ISOYear]], _precalculatedPlainDateTime_.[[ISOMonth]], _precalculatedPlainDateTime_.[[ISODay]], _zonedRelativeTo_.[[Calendar]]). 1. Let _unbalanceResult_ be ? UnbalanceDateDurationRelative(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _duration_.[[Days]], _unit_, _plainRelativeTo_). 1. If _zonedRelativeTo_ is not *undefined*, then - 1. Let _intermediate_ be ? MoveRelativeZonedDateTime(_zonedRelativeTo_, _unbalanceResult_.[[Years]], _unbalanceResult_.[[Months]], _unbalanceResult_.[[Weeks]], 0). + 1. Let _intermediate_ be ? MoveRelativeZonedDateTime(_zonedRelativeTo_, _unbalanceResult_.[[Years]], _unbalanceResult_.[[Months]], _unbalanceResult_.[[Weeks]], 0, _precalculatedPlainDateTime_). 1. Let _balanceResult_ be ? BalancePossiblyInfiniteTimeDurationRelative(_unbalanceResult_.[[Days]], _duration_.[[Hours]], _duration_.[[Minutes]], _duration_.[[Seconds]], _duration_.[[Milliseconds]], _duration_.[[Microseconds]], _duration_.[[Nanoseconds]], _unit_, _intermediate_). 1. Else, 1. Let _balanceResult_ be BalancePossiblyInfiniteTimeDuration(_unbalanceResult_.[[Days]], _duration_.[[Hours]], _duration_.[[Minutes]], _duration_.[[Seconds]], _duration_.[[Milliseconds]], _duration_.[[Microseconds]], _duration_.[[Nanoseconds]], _unit_). 1. If _balanceResult_ is ~positive overflow~, return *+∞*𝔽. 1. If _balanceResult_ is ~negative overflow~, return *-∞*𝔽. 1. Assert: _balanceResult_ is a Time Duration Record. - 1. Let _roundRecord_ be ? RoundDuration(_unbalanceResult_.[[Years]], _unbalanceResult_.[[Months]], _unbalanceResult_.[[Weeks]], _balanceResult_.[[Days]], _balanceResult_.[[Hours]], _balanceResult_.[[Minutes]], _balanceResult_.[[Seconds]], _balanceResult_.[[Milliseconds]], _balanceResult_.[[Microseconds]], _balanceResult_.[[Nanoseconds]], 1, _unit_, *"trunc"*, _plainRelativeTo_, _zonedRelativeTo_). + 1. Let _roundRecord_ be ? RoundDuration(_unbalanceResult_.[[Years]], _unbalanceResult_.[[Months]], _unbalanceResult_.[[Weeks]], _balanceResult_.[[Days]], _balanceResult_.[[Hours]], _balanceResult_.[[Minutes]], _balanceResult_.[[Seconds]], _balanceResult_.[[Milliseconds]], _balanceResult_.[[Microseconds]], _balanceResult_.[[Nanoseconds]], 1, _unit_, *"trunc"*, _plainRelativeTo_, _zonedRelativeTo_, _precalculatedPlainDateTime_). 1. Return 𝔽(_roundRecord_.[[Total]]). @@ -1294,6 +1304,7 @@

_nanoseconds_: an integer, _largestUnit_: a String, _zonedRelativeTo_: a Temporal.ZonedDateTime, + _precalculatedPlainDateTime_: a Temporal.PlainDateTime or *undefined*, ): either a normal completion containing a Time Duration Record, or an abrupt completion

@@ -1301,7 +1312,7 @@

It converts the time units of a duration into a form where lower units are converted into higher units as much as possible, up to _largestUnit_, taking day length of _zonedRelativeTo_ into account. If the Number value for any unit is infinite, it returns abruptly with a *RangeError*.

- 1. Let _balanceResult_ be ? BalancePossiblyInfiniteTimeDurationRelative(_days_, _hours_, _minutes_, _seconds_, _milliseconds_, _microseconds_, _nanoseconds_, _largestUnit_, _zonedRelativeTo_). + 1. Let _balanceResult_ be ? BalancePossiblyInfiniteTimeDurationRelative(_days_, _hours_, _minutes_, _seconds_, _milliseconds_, _microseconds_, _nanoseconds_, _largestUnit_, _zonedRelativeTo_, _precalculatedPlainDateTime_). 1. If _balanceResult_ is ~positive overflow~ or ~negative overflow~, then 1. Throw a *RangeError* exception. 1. Return _balanceResult_. @@ -1320,6 +1331,7 @@

_nanoseconds_: an integer, _largestUnit_: a String, _zonedRelativeTo_: a Temporal.ZonedDateTime, + optional _precalculatedPlainDateTime_: a Temporal.PlainDateTime or *undefined*, ): either a normal completion containing either Time Duration Record if there are no infinite values, ~positive overflow~, or ~negative overflow~ in case of infinite values, or an abrupt completion

@@ -1330,16 +1342,18 @@

This operation observably calls time zone and calendar methods.

+ 1. If _precalculatedPlainDateTime_ is not present, let _precalculatedPlainDateTime_ be *undefined*. 1. Let _intermediateNs_ be _zonedRelativeTo_.[[Nanoseconds]]. + 1. Let _startInstant_ be ! CreateTemporalInstant(_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. If _precalculatedPlainDateTime_ is *undefined*, set _precalculatedPlainDateTime_ to ? GetPlainDateTimeFor(_zonedRelativeTo_.[[TimeZone]], _startInstant_, *"iso8601"*). + 1. Let _intermediateResult_ be ? AddDaysToZonedDateTime(_startInstant_, _precalculatedPlainDateTime_, _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_). + 1. If _nanoseconds_ ≠ 0 and _precalculatedPlainDateTime_ is *undefined*, set _precalculatedPlainDateTime_ to ? GetPlainDateTimeFor(_zonedRelativeTo_.[[TimeZone]], _startInstant_, *"iso8601"*). + 1. Let _result_ be ? NanosecondsToDays(_nanoseconds_, _zonedRelativeTo_, _precalculatedPlainDateTime_). 1. Set _days_ to _result_.[[Days]]. 1. Set _largestUnit_ to *"hour"*. 1. Else, @@ -1579,6 +1593,7 @@

_ns2_: an integer, _plainRelativeTo_: a Temporal.PlainDate or *undefined*, _zonedRelativeTo_: a Temporal.ZonedDateTime or *undefined*, + optional _precalculatedPlainDateTime_: a Temporal.PlainDateTime or *undefined*, ): either a normal completion containing a Duration Record, or a throw completion

@@ -1614,14 +1629,19 @@

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*. + 1. If _precalculatedPlainDateTime_ is not present, let _precalculatedPlainDateTime_ be *undefined*. 1. Let _timeZone_ be _zonedRelativeTo_.[[TimeZone]]. 1. Let _calendar_ be _zonedRelativeTo_.[[Calendar]]. - 1. Let _intermediateNs_ be ? AddZonedDateTime(_zonedRelativeTo_.[[Nanoseconds]], _timeZone_, _calendar_, _y1_, _mon1_, _w1_, _d1_, _h1_, _min1_, _s1_, _ms1_, _mus1_, _ns1_). + 1. If _precalculatedPlainDateTime_ is *undefined*, then + 1. Let _startDateTime_ be ? GetPlainDateTimeFor(_timeZone_, _zonedRelativeTo_.[[Nanoseconds]], _calendar_). + 1. Else, + 1. Let _startDateTime_ be _precalculatedPlainDateTime_. + 1. Let _intermediateNs_ be ? AddZonedDateTime(_zonedRelativeTo_.[[Nanoseconds]], _timeZone_, _calendar_, _y1_, _mon1_, _w1_, _d1_, _h1_, _min1_, _s1_, _ms1_, _mus1_, _ns1_, _startDateTime_). 1. Let _endNs_ be ? AddZonedDateTime(_intermediateNs_, _timeZone_, _calendar_, _y2_, _mon2_, _w2_, _d2_, _h2_, _min2_, _s2_, _ms2_, _mus2_, _ns2_). 1. If _largestUnit_ is not one of *"year"*, *"month"*, *"week"*, or *"day"*, then 1. Let _result_ be DifferenceInstant(_zonedRelativeTo_.[[Nanoseconds]], _endNs_, 1, *"nanosecond"*, _largestUnit_, *"halfExpand"*). 1. Return ! CreateDurationRecord(0, 0, 0, 0, _result_.[[Hours]], _result_.[[Minutes]], _result_.[[Seconds]], _result_.[[Milliseconds]], _result_.[[Microseconds]], _result_.[[Nanoseconds]]). - 1. Return ? DifferenceZonedDateTime(_zonedRelativeTo_.[[Nanoseconds]], _endNs_, _timeZone_, _calendar_, _largestUnit_, OrdinaryObjectCreate(*null*)). + 1. Return ? DifferenceZonedDateTime(_zonedRelativeTo_.[[Nanoseconds]], _endNs_, _timeZone_, _calendar_, _largestUnit_, OrdinaryObjectCreate(*null*), _startDateTime_). @@ -1678,14 +1698,15 @@

_months_: an integer, _weeks_: an integer, _days_: an integer, - ) + _precalculatedPlainDateTime_: a Temporal.PlainDateTime or *undefined*, + ): either a normal completion containing a Temporal.ZonedDateTime, or a throw completion

description
It adjusts the calendar part of _zonedDateTime_ for use as the "relative-to" parameter of another operation and returns a new `Temporal.ZonedDateTime` instance.
- 1. Let _intermediateNs_ be ? AddZonedDateTime(_zonedDateTime_.[[Nanoseconds]], _zonedDateTime_.[[TimeZone]], _zonedDateTime_.[[Calendar]], _years_, _months_, _weeks_, _days_, 0, 0, 0, 0, 0, 0). + 1. Let _intermediateNs_ be ? AddZonedDateTime(_zonedDateTime_.[[Nanoseconds]], _zonedDateTime_.[[TimeZone]], _zonedDateTime_.[[Calendar]], _years_, _months_, _weeks_, _days_, 0, 0, 0, 0, 0, 0, _precalculatedPlainDateTime_). 1. Return ! CreateTemporalZonedDateTime(_intermediateNs_, _zonedDateTime_.[[TimeZone]], _zonedDateTime_.[[Calendar]]). @@ -1708,6 +1729,7 @@

_roundingMode_: a String, optional _plainRelativeTo_: *undefined* or a Temporal.PlainDate, optional _zonedRelativeTo_: *undefined* or a Temporal.ZonedDateTime, + optional _precalculatedPlainDateTime_: *undefined* or a Temporal.PlainDateTime, ): either a normal completion containing a Record with fields [[DurationRecord]] (a Duration Record) and [[Total]] (a mathematical value), or a throw completion

@@ -1721,12 +1743,13 @@

1. If _plainRelativeTo_ is not present, set _plainRelativeTo_ to *undefined*. 1. If _zonedRelativeTo_ is not present, set _zonedRelativeTo_ to *undefined*. + 1. If _precalculatedPlainDateTime_ is not present, set _precalculatedPlainDateTime_ to *undefined*. 1. If _unit_ is *"year"*, *"month"*, or *"week"*, and _plainRelativeTo_ is *undefined*, then 1. Throw a *RangeError* exception. 1. If _unit_ is one of *"year"*, *"month"*, *"week"*, or *"day"*, then 1. Let _nanoseconds_ be TotalDurationNanoseconds(_hours_, _minutes_, _seconds_, _milliseconds_, _microseconds_, _nanoseconds_). 1. If _zonedRelativeTo_ is not *undefined*, then - 1. Let _intermediate_ be ? MoveRelativeZonedDateTime(_zonedRelativeTo_, _years_, _months_, _weeks_, _days_). + 1. Let _intermediate_ be ? MoveRelativeZonedDateTime(_zonedRelativeTo_, _years_, _months_, _weeks_, _days_, _precalculatedPlainDateTime_). 1. Let _result_ be ? NanosecondsToDays(_nanoseconds_, _intermediate_). 1. Let _fractionalDays_ be _days_ + _result_.[[Days]] + _result_.[[Nanoseconds]] / _result_.[[DayLength]]. 1. Else, @@ -1874,6 +1897,7 @@

_unit_: a String, _roundingMode_: a String, _zonedRelativeTo_: a Temporal.ZonedDateTime, + _precalculatedPlainDateTime_: a Temporal.PlainDateTime, ): either a normal completion containing a Duration Record, or a throw completion

@@ -1891,7 +1915,7 @@

1. If _timeRemainderNs_ = 0, let _direction_ be 0. 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 _dayStart_ be ? AddZonedDateTime(_zonedRelativeTo_.[[Nanoseconds]], _zonedRelativeTo_.[[TimeZone]], _zonedRelativeTo_.[[Calendar]], _years_, _months_, _weeks_, _days_, 0, 0, 0, 0, 0, 0, _precalculatedPlainDateTime_). 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]]. @@ -1899,7 +1923,7 @@

1. Let _oneDayLess_ be _timeRemainderNs_ - _dayLengthNs_. 1. If _oneDayLess_ × _direction_ < 0, then 1. Return ! CreateDurationRecord(_years_, _months_, _weeks_, _days_, _hours_, _minutes_, _seconds_, _milliseconds_, _microseconds_, _nanoseconds_). - 1. Let _adjustedDateDuration_ be ? AddDuration(_years_, _months_, _weeks_, _days_, 0, 0, 0, 0, 0, 0, 0, 0, 0, _direction_, 0, 0, 0, 0, 0, 0, *undefined*, _zonedRelativeTo_). + 1. Let _adjustedDateDuration_ be ? AddDuration(_years_, _months_, _weeks_, _days_, 0, 0, 0, 0, 0, 0, 0, 0, 0, _direction_, 0, 0, 0, 0, 0, 0, *undefined*, _zonedRelativeTo_, _precalculatedPlainDateTime_). 1. Let _adjustedTimeDuration_ be ! RoundDuration(_adjustedDateDuration_.[[Years]], _adjustedDateDuration_.[[Months]], _adjustedDateDuration_.[[Weeks]], _adjustedDateDuration_.[[Days]], 0, 0, 0, 0, 0, _oneDayLess_, _increment_, _unit_, _roundingMode_). 1. Set _adjustedTimeDuration_ to ? BalanceTimeDuration(0, _adjustedTimeDuration_.[[Hours]], _adjustedTimeDuration_.[[Minutes]], _adjustedTimeDuration_.[[Seconds]], _adjustedTimeDuration_.[[Milliseconds]], _adjustedTimeDuration_.[[Microseconds]], _adjustedTimeDuration_.[[Nanoseconds]], *"hour"*). 1. Return ! CreateDurationRecord(_adjustedDateDuration_.[[Years]], _adjustedDateDuration_.[[Months]], _adjustedDateDuration_.[[Weeks]], _adjustedDateDuration_.[[Days]], _adjustedTimeDuration_.[[Hours]], _adjustedTimeDuration_.[[Minutes]], _adjustedTimeDuration_.[[Seconds]], _adjustedTimeDuration_.[[Milliseconds]], _adjustedTimeDuration_.[[Microseconds]], _adjustedTimeDuration_.[[Nanoseconds]]). diff --git a/spec/zoneddatetime.html b/spec/zoneddatetime.html index 792e8ea467..a22d1de97b 100644 --- a/spec/zoneddatetime.html +++ b/spec/zoneddatetime.html @@ -1376,6 +1376,7 @@

_calendar_: an Object, _largestUnit_: a String, _options_: an Object, + _precalculatedPlainDateTime_: a Temporal.PlainDateTime or *undefined*, ): either a normal completion containing a Duration Record or an abrupt completion

@@ -1387,8 +1388,11 @@

1. If _ns1_ is _ns2_, then 1. Return ! CreateDurationRecord(0, 0, 0, 0, 0, 0, 0, 0, 0, 0). - 1. Let _startInstant_ be ! CreateTemporalInstant(_ns1_). - 1. Let _startDateTime_ be ? GetPlainDateTimeFor(_timeZone_, _startInstant_, _calendar_). + 1. If _precalculatedPlainDateTime_ is *undefined*, then + 1. Let _startInstant_ be ! CreateTemporalInstant(_ns1_). + 1. Let _startDateTime_ be ? GetPlainDateTimeFor(_timeZone_, _startInstant_, _calendar_). + 1. Else, + 1. Let _startDateTime_ be _precalculatedPlainDateTime_. 1. Let _endInstant_ be ! CreateTemporalInstant(_ns2_). 1. Let _endDateTime_ be ? GetPlainDateTimeFor(_timeZone_, _endInstant_, _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]], _calendar_, _largestUnit_, _options_). @@ -1406,6 +1410,7 @@

NanosecondsToDays ( _nanoseconds_: an integer, _zonedRelativeTo_: a Temporal.ZonedDateTime, + optional _precalculatedPlainDateTime_: a Temporal.PlainDateTime, ): either a normal completion containing a Record with fields [[Days]] (an integer), [[Nanoseconds]] (an integer), and [[DayLength]] (an integer), or an abrupt completion

@@ -1424,7 +1429,7 @@

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_, *"iso8601"*). + 1. If _precalculatedPlainDateTime_ is present, let _startDateTime_ be _precalculatedPlainDateTime_; else 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]]). @@ -1502,16 +1507,21 @@

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]]). + 1. If _settings_.[[SmallestUnit]] is *"nanosecond"* and _settings_.[[RoundingIncrement]] is 1, let _roundingGranularityIsNoop_ be *true*; else let _roundingGranularityIsNoop_ be *false*. + 1. If _roundingGranularityIsNoop_ is *false*, or _settings_.[[SmallestUnit]] is *"year"*, or _settings_.[[SmallestUnit]] is *"month"*, or _settings_.[[SmallestUnit]] is *"week"*, let _plainDateTimeOrRelativeToWillBeUsed_ be *true*; else let _plainDateTimeOrRelativeToWillBeUsed_ be *false*. + 1. Let _precalculatedPlainDateTime_ be *undefined*. 1. Let _plainRelativeTo_ be *undefined*. - 1. If _settings_.[[SmallestUnit]] is one of *"year"*, *"month"*, *"week"*, then - 1. NOTE: The above condition means that the corresponding `Temporal.PlainDate` for _zonedDateTime_ will be used in one of the operations below. - 1. Set _plainRelativeTo_ to ? ToTemporalDate(_zonedDateTime_). - 1. Let _roundRecord_ be ? RoundDuration(_difference_.[[Years]], _difference_.[[Months]], _difference_.[[Weeks]], _difference_.[[Days]], _difference_.[[Hours]], _difference_.[[Minutes]], _difference_.[[Seconds]], _difference_.[[Milliseconds]], _difference_.[[Microseconds]], _difference_.[[Nanoseconds]], _settings_.[[RoundingIncrement]], _settings_.[[SmallestUnit]], _settings_.[[RoundingMode]], _plainRelativeTo_, _zonedDateTime_). + 1. If _plainDateTimeOrRelativeToWillBeUsed_ is *true*, then + 1. NOTE: The above conditions mean that the corresponding `Temporal.PlainDateTime` or `Temporal.PlainDate` for _zonedDateTime_ will be used in one of the operations below. + 1. Let _instant_ be ! CreateTemporalInstant(_zonedDateTime_.[[Nanoseconds]]). + 1. Set _precalculatedPlainDateTime_ to ? GetPlainDateTimeFor(_zonedDateTime_.[[TimeZone]], _instant_, _zonedDateTime_.[[Calendar]]). + 1. Set _plainRelativeTo_ to ! CreateTemporalDate(_precalculatedPlainDateTime_.[[ISOYear]], _precalculatedPlainDateTime_.[[ISOMonth]], _precalculatedPlainDateTime_.[[ISODay]], _zonedDateTime_.[[Calendar]]). + 1. Let _difference_ be ? DifferenceZonedDateTime(_zonedDateTime_.[[Nanoseconds]], _other_.[[Nanoseconds]], _zonedDateTime_.[[TimeZone]], _zonedDateTime_.[[Calendar]], _settings_.[[LargestUnit]], _resolvedOptions_, _precalculatedPlainDateTime_). + 1. If _roundingGranularityIsNoop_ is *true*, 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]]). + 1. Let _roundRecord_ be ? RoundDuration(_difference_.[[Years]], _difference_.[[Months]], _difference_.[[Weeks]], _difference_.[[Days]], _difference_.[[Hours]], _difference_.[[Minutes]], _difference_.[[Seconds]], _difference_.[[Milliseconds]], _difference_.[[Microseconds]], _difference_.[[Nanoseconds]], _settings_.[[RoundingIncrement]], _settings_.[[SmallestUnit]], _settings_.[[RoundingMode]], _plainRelativeTo_, _zonedDateTime_, _precalculatedPlainDateTime_). 1. Let _roundResult_ be _roundRecord_.[[DurationRecord]]. - 1. Let _result_ be ? AdjustRoundedDurationDays(_roundResult_.[[Years]], _roundResult_.[[Months]], _roundResult_.[[Weeks]], _roundResult_.[[Days]], _roundResult_.[[Hours]], _roundResult_.[[Minutes]], _roundResult_.[[Seconds]], _roundResult_.[[Milliseconds]], _roundResult_.[[Microseconds]], _roundResult_.[[Nanoseconds]], _settings_.[[RoundingIncrement]], _settings_.[[SmallestUnit]], _settings_.[[RoundingMode]], _zonedDateTime_). + 1. Let _result_ be ? AdjustRoundedDurationDays(_roundResult_.[[Years]], _roundResult_.[[Months]], _roundResult_.[[Weeks]], _roundResult_.[[Days]], _roundResult_.[[Hours]], _roundResult_.[[Minutes]], _roundResult_.[[Seconds]], _roundResult_.[[Milliseconds]], _roundResult_.[[Microseconds]], _roundResult_.[[Nanoseconds]], _settings_.[[RoundingIncrement]], _settings_.[[SmallestUnit]], _settings_.[[RoundingMode]], _zonedDateTime_, _precalculatedPlainDateTime_). 1. Return ! CreateTemporalDuration(_sign_ × _result_.[[Years]], _sign_ × _result_.[[Months]], _sign_ × _result_.[[Weeks]], _sign_ × _result_.[[Days]], _sign_ × _result_.[[Hours]], _sign_ × _result_.[[Minutes]], _sign_ × _result_.[[Seconds]], _sign_ × _result_.[[Milliseconds]], _sign_ × _result_.[[Microseconds]], _sign_ × _result_.[[Nanoseconds]]). From 1b18a56911bc9d9376496442923b017009253531 Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Mon, 25 Sep 2023 11:35:05 -0700 Subject: [PATCH 09/10] Normative: Fix absolute value bug in duration rounding no-op conditions Fix for bug in User Code Calls Part 2 (51ea969a), spotted by Anba. Thanks! The existing code forgot to take negative durations into account. Closes: #2679 --- polyfill/lib/duration.mjs | 9 +++++++-- spec/duration.html | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/polyfill/lib/duration.mjs b/polyfill/lib/duration.mjs index c5dea54aa7..c15e0d9b06 100644 --- a/polyfill/lib/duration.mjs +++ b/polyfill/lib/duration.mjs @@ -23,6 +23,7 @@ import { SetSlot } from './slots.mjs'; +const MathAbs = Math.abs; const ObjectCreate = Object.create; export class Duration { @@ -288,8 +289,12 @@ export class Duration { const balancingRequested = largestUnit !== existingLargestUnit; const calendarUnitsPresent = years !== 0 || months !== 0 || weeks !== 0; const timeUnitsOverflowWillOccur = - minutes >= 60 || seconds >= 60 || milliseconds >= 1000 || microseconds >= 1000 || nanoseconds >= 1000; - const hoursToDaysConversionMayOccur = (days !== 0 && zonedRelativeTo) || hours >= 24; + MathAbs(minutes) >= 60 || + MathAbs(seconds) >= 60 || + MathAbs(milliseconds) >= 1000 || + MathAbs(microseconds) >= 1000 || + MathAbs(nanoseconds) >= 1000; + const hoursToDaysConversionMayOccur = (days !== 0 && zonedRelativeTo) || MathAbs(hours) >= 24; if ( roundingGranularityIsNoop && !balancingRequested && diff --git a/spec/duration.html b/spec/duration.html index e6a30c86db..99c716f5e4 100644 --- a/spec/duration.html +++ b/spec/duration.html @@ -453,10 +453,10 @@

Temporal.Duration.prototype.round ( _roundTo_ )

1. If _maximum_ is not *undefined*, perform ? ValidateTemporalRoundingIncrement(_roundingIncrement_, _maximum_, *false*). 1. Let _hoursToDaysConversionMayOccur_ be *false*. 1. If _duration_.[[Days]] ≠ 0 and _zonedRelativeTo_ is not *undefined*, set _hoursToDaysConversionMayOccur_ to *true*. - 1. Else if _duration_.[[Hours]] ≥ 24, set _hoursToDaysConversionMayOccur_ to *true*. + 1. Else if abs(_duration_.[[Hours]]) ≥ 24, set _hoursToDaysConversionMayOccur_ to *true*. 1. If _smallestUnit_ is *"nanosecond"* and _roundingIncrement_ = 1, let _roundingGranularityIsNoop_ be *true*; else let _roundingGranularityIsNoop_ be *false*. 1. If _duration_.[[Years]] = 0 and _duration_.[[Months]] = 0 and _duration_.[[Weeks]] = 0, let _calendarUnitsPresent_ be *false*; else let _calendarUnitsPresent_ be *true*. - 1. If _roundingGranularityIsNoop_ is *true*, and _largestUnit_ is _existingLargestUnit_, and _calendarUnitsPresent_ is *false*, and _hoursToDaysConversionMayOccur_ is *false*, and _duration_.[[Minutes]] < 60, and _duration_.[[Seconds]] < 60, and _duration_.[[Milliseconds]] < 1000, and _duration_.[[Microseconds]] < 1000, and _duration_.[[Nanoseconds]] < 1000, then + 1. If _roundingGranularityIsNoop_ is *true*, and _largestUnit_ is _existingLargestUnit_, and _calendarUnitsPresent_ is *false*, and _hoursToDaysConversionMayOccur_ is *false*, and abs(_duration_.[[Minutes]]) < 60, and abs(_duration_.[[Seconds]]) < 60, and abs(_duration_.[[Milliseconds]]) < 1000, and abs(_duration_.[[Microseconds]]) < 1000, and abs(_duration_.[[Nanoseconds]]) < 1000, then 1. NOTE: The above conditions mean that the operation will have no effect: the smallest unit and rounding increment will leave the total duration unchanged, and it can be determined without calling a calendar or time zone method that no balancing will take place. 1. Return ! CreateTemporalDuration(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _duration_.[[Days]], _duration_.[[Hours]], _duration_.[[Minutes]], _duration_.[[Seconds]], _duration_.[[Milliseconds]], _duration_.[[Microseconds]], _duration_.[[Nanoseconds]]). 1. Let _precalculatedPlainDateTime_ be *undefined*. From 9a3c08b97e6460182a7481e2292a79e24a03f26d Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Wed, 4 Oct 2023 15:43:44 -0700 Subject: [PATCH 10/10] Update test262 --- polyfill/test/expected-failures.txt | 7 +++++++ polyfill/test262 | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/polyfill/test/expected-failures.txt b/polyfill/test/expected-failures.txt index 81c595ff80..2fa30daf77 100644 --- a/polyfill/test/expected-failures.txt +++ b/polyfill/test/expected-failures.txt @@ -6,3 +6,10 @@ staging/Intl402/Temporal/old/instant-toLocaleString.js staging/Intl402/Temporal/old/time-toLocaleString.js staging/Intl402/Temporal/old/zoneddatetime-toLocaleString.js intl402/DateTimeFormat/prototype/format/temporal-objects-resolved-time-zone.js + +# These are caught by the default test glob, but are unrelated to Temporal. +# They rely on Intl.DateTimeFormat supporting offset time zones. +intl402/DateTimeFormat/prototype/format/offset-timezone-gmt-same.js +intl402/DateTimeFormat/prototype/formatToParts/offset-timezone-correct.js +intl402/DateTimeFormat/prototype/resolvedOptions/offset-timezone-basic.js +intl402/DateTimeFormat/prototype/resolvedOptions/offset-timezone-change.js diff --git a/polyfill/test262 b/polyfill/test262 index e98bfb332e..dceb204259 160000 --- a/polyfill/test262 +++ b/polyfill/test262 @@ -1 +1 @@ -Subproject commit e98bfb332e653a5c8c38763574140dae1417b7a8 +Subproject commit dceb204259abc800b7a6ec7bccf07f9a7c69297d