From 20d3b249b66b8755e4323e2fa23e2d970b6f36b6 Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Tue, 10 Oct 2023 16:00:46 -0700 Subject: [PATCH 01/12] Editorial: Remove 'constrain' from AddDaysToZonedDateTime call In BalancePossiblyInfiniteTimeDurationRelative, we call AddDaysToZonedDateTime with the overflow parameter unconditionally set to 'constrain'. In this case, we can just omit it, since that's also the default value. Thanks to Anba for spotting this. Closes: #2693 --- polyfill/lib/ecmascript.mjs | 3 +-- spec/duration.html | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/polyfill/lib/ecmascript.mjs b/polyfill/lib/ecmascript.mjs index ed08a39b7f..508c3a3991 100644 --- a/polyfill/lib/ecmascript.mjs +++ b/polyfill/lib/ecmascript.mjs @@ -3546,8 +3546,7 @@ export function BalancePossiblyInfiniteTimeDurationRelative( precalculatedPlainDateTime, timeZone, 'iso8601', - days, - 'constrain' + days ).epochNs; } diff --git a/spec/duration.html b/spec/duration.html index 99c716f5e4..deccdae7b9 100644 --- a/spec/duration.html +++ b/spec/duration.html @@ -1347,7 +1347,7 @@

1. Let _startInstant_ be ! CreateTemporalInstant(_zonedRelativeTo_.[[Nanoseconds]]). 1. If _days_ ≠ 0, then 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. Let _intermediateResult_ be ? AddDaysToZonedDateTime(_startInstant_, _precalculatedPlainDateTime_, _zonedRelativeTo_.[[TimeZone]], *"iso8601"*, _days_). 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]]). From 97698d3255a3c59a8d07a607d1d4986c33668353 Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Tue, 10 Oct 2023 16:19:43 -0700 Subject: [PATCH 02/12] Editorial: Remove incorrect assertion in UnbalanceDateDurationRelative I thought we were always calling this with largestUnit "day" if the largestUnit was smaller than days, but that's not the case. Just remove this assertion because it doesn't make any sense, and the spec is correct without it. Closes: #2695 --- spec/duration.html | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/duration.html b/spec/duration.html index deccdae7b9..d8ef124501 100644 --- a/spec/duration.html +++ b/spec/duration.html @@ -1435,7 +1435,6 @@

1. Set _days_ to _days_ + _moveResult_.[[Days]]. 1. Set _months_ to _months_ - _sign_. 1. Return ? CreateDateDurationRecord(0, 0, _weeks_, _days_). - 1. Assert: _largestUnit_ is *"day"*. 1. If _years_ = 0, and _months_ = 0, and _weeks_ = 0, return ! CreateDateDurationRecord(0, 0, 0, _days_). 1. If _calendar_ is *undefined*, then 1. Throw a *RangeError* exception. From 3b507f042429d63aa08ddf17bd9e27a68de31bb0 Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Tue, 10 Oct 2023 16:15:03 -0700 Subject: [PATCH 03/12] Editorial: Return zero early in BalancePossiblyInfinite...Relative This avoids calling NanosecondsToDays with the precalculated datetime parameter present but undefined, which is disallowed according to that operation's type assertions. Credits to Anba for catching this. Closes: #2694 --- polyfill/lib/ecmascript.mjs | 5 ++++- spec/duration.html | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/polyfill/lib/ecmascript.mjs b/polyfill/lib/ecmascript.mjs index 508c3a3991..c981098f55 100644 --- a/polyfill/lib/ecmascript.mjs +++ b/polyfill/lib/ecmascript.mjs @@ -3552,9 +3552,12 @@ export function BalancePossiblyInfiniteTimeDurationRelative( const endNs = AddInstant(intermediateNs, hours, minutes, seconds, milliseconds, microseconds, nanoseconds); nanoseconds = endNs.subtract(startNs); + if (nanoseconds.isZero()) { + return { days: 0, hours: 0, minutes: 0, seconds: 0, milliseconds: 0, microseconds: 0, nanoseconds: 0 }; + } if (largestUnit === 'year' || largestUnit === 'month' || largestUnit === 'week' || largestUnit === 'day') { - if (!nanoseconds.isZero()) precalculatedPlainDateTime ??= GetPlainDateTimeFor(timeZone, startInstant, 'iso8601'); + precalculatedPlainDateTime ??= GetPlainDateTimeFor(timeZone, startInstant, 'iso8601'); ({ days, nanoseconds } = NanosecondsToDays(nanoseconds, zonedRelativeTo, precalculatedPlainDateTime)); largestUnit = 'hour'; } else { diff --git a/spec/duration.html b/spec/duration.html index d8ef124501..4b432b4e49 100644 --- a/spec/duration.html +++ b/spec/duration.html @@ -1351,8 +1351,9 @@

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 _nanoseconds_ = 0, return ! CreateTimeDurationRecord(0, 0, 0, 0, 0, 0, 0). 1. If _largestUnit_ is one of *"year"*, *"month"*, *"week"*, or *"day"*, then - 1. If _nanoseconds_ ≠ 0 and _precalculatedPlainDateTime_ is *undefined*, set _precalculatedPlainDateTime_ to ? GetPlainDateTimeFor(_zonedRelativeTo_.[[TimeZone]], _startInstant_, *"iso8601"*). + 1. If _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"*. From 9356ea8a0852c5a3041a2b94ef67dbcfa9d44ea3 Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Tue, 10 Oct 2023 17:19:35 -0700 Subject: [PATCH 04/12] Editorial: Make CreateTemporalDate fallible in a few places The AddISODate call in the previous lines may return a result record that is out of range and cannot successfully be passed to CreateTemporalDate, and so this step should be fallible. Thanks to Anba for spotting this. Closes: #2697 Closes: #2700 --- spec/duration.html | 2 +- spec/plainyearmonth.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/duration.html b/spec/duration.html index 4b432b4e49..c5e40a3632 100644 --- a/spec/duration.html +++ b/spec/duration.html @@ -1774,7 +1774,7 @@

1. Set _plainRelativeTo_ to _yearsLater_. 1. Set _fractionalDays_ to _fractionalDays_ + _monthsWeeksInDays_. 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 _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 ? DifferenceDate(_calendar_, _plainRelativeTo_, _wholeDaysLater_, _untilOptions_). diff --git a/spec/plainyearmonth.html b/spec/plainyearmonth.html index 58c3e6f382..154955fd12 100644 --- a/spec/plainyearmonth.html +++ b/spec/plainyearmonth.html @@ -683,7 +683,7 @@

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 _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 _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_). From cd0a3ac14cf6d310a2a13620868a8c66a16eed4d Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Wed, 11 Oct 2023 11:50:14 -0700 Subject: [PATCH 05/12] Editorial: Fix rebase error In a rebase of 1ff91a9730ace0af31177afab5c57bc343a5ab2f I mistakenly left out the change from _dateTime_.[[Calendar]] to *"iso8601"* in the second call of CreateTemporalDateTime. Closes: #2701 --- spec/timezone.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/timezone.html b/spec/timezone.html index 5f5003bee4..d0388ce21b 100644 --- a/spec/timezone.html +++ b/spec/timezone.html @@ -779,7 +779,7 @@

DisambiguatePossibleInstants ( _possibleInstants_, _timeZone_, _dateTime_, _ 1. Assert: _disambiguation_ is *"compatible"* or *"later"*. 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. Let _laterDateTime_ be ! CreateTemporalDateTime(_laterDate_.[[Year]], _laterDate_.[[Month]], _laterDate_.[[Day]], _laterTime_.[[Hour]], _laterTime_.[[Minute]], _laterTime_.[[Second]], _laterTime_.[[Millisecond]], _laterTime_.[[Microsecond]], _laterTime_.[[Nanosecond]], *"iso8601"*). 1. Set _possibleInstants_ to ? GetPossibleInstantsFor(_timeZone_, _laterDateTime_). 1. Set _n_ to _possibleInstants_'s length. 1. If _n_ = 0, throw a *RangeError* exception. From 9ce13a0cb2948e82044384a23c0336894a2de58c Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Wed, 11 Oct 2023 15:27:26 -0700 Subject: [PATCH 06/12] Editorial: Create instant at the right place in AddZonedDateTime Thanks again to Anba for spotting that this _instant_ is used lower down even if it wasn't created. Closes: #2702 --- spec/zoneddatetime.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/zoneddatetime.html b/spec/zoneddatetime.html index a22d1de97b..f0d87c9fac 100644 --- a/spec/zoneddatetime.html +++ b/spec/zoneddatetime.html @@ -1309,10 +1309,10 @@

1. Assert: Type(_options_) is Object or Undefined. 1. If _years_ = 0, _months_ = 0, _weeks_ = 0, and _days_ = 0, then 1. Return ? AddInstant(_epochNanoseconds_, _hours_, _minutes_, _seconds_, _milliseconds_, _microseconds_, _nanoseconds_). + 1. Let _instant_ be ! CreateTemporalInstant(_epochNanoseconds_). 1. If _precalculatedPlainDateTime_ is not *undefined*, then 1. Let _temporalDateTime_ be _precalculatedPlainDateTime_. 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_). From 6ad12984f5e06420a4846d28d7e4b142bf768352 Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Wed, 11 Oct 2023 15:44:01 -0700 Subject: [PATCH 07/12] Polyfill: Implement CompareISODateTime and CompareTemporalTime as in spec Change the reference code so that these operations work exactly like in the spec text, instead of the looping implementation that the code previously had. --- polyfill/lib/ecmascript.mjs | 16 ++++++++ polyfill/lib/plaindatetime.mjs | 71 +++++++++++++++++++++------------- polyfill/lib/plaintime.mjs | 20 +++++++--- 3 files changed, 74 insertions(+), 33 deletions(-) diff --git a/polyfill/lib/ecmascript.mjs b/polyfill/lib/ecmascript.mjs index c981098f55..039af519a1 100644 --- a/polyfill/lib/ecmascript.mjs +++ b/polyfill/lib/ecmascript.mjs @@ -5907,6 +5907,22 @@ export function CompareISODate(y1, m1, d1, y2, m2, d2) { return 0; } +export function CompareTemporalTime(h1, min1, s1, ms1, µs1, ns1, h2, min2, s2, ms2, µs2, ns2) { + if (h1 !== h2) return ComparisonResult(h1 - h2); + if (min1 !== min2) return ComparisonResult(min1 - min2); + if (s1 !== s2) return ComparisonResult(s1 - s2); + if (ms1 !== ms2) return ComparisonResult(ms1 - ms2); + if (µs1 !== µs2) return ComparisonResult(µs1 - µs2); + if (ns1 !== ns2) return ComparisonResult(ns1 - ns2); + return 0; +} + +export function CompareISODateTime(y1, m1, d1, h1, min1, s1, ms1, µs1, ns1, y2, m2, d2, h2, min2, s2, ms2, µs2, ns2) { + const dateResult = CompareISODate(y1, m1, d1, y2, m2, d2); + if (dateResult !== 0) return dateResult; + return CompareTemporalTime(h1, min1, s1, ms1, µs1, ns1, h2, min2, s2, ms2, µs2, ns2); +} + // Not abstract operations from the spec export function NonNegativeBigIntDivmod(x, y) { diff --git a/polyfill/lib/plaindatetime.mjs b/polyfill/lib/plaindatetime.mjs index 72aa4c6a83..b119d7ddc1 100644 --- a/polyfill/lib/plaindatetime.mjs +++ b/polyfill/lib/plaindatetime.mjs @@ -355,15 +355,30 @@ export class PlainDateTime { equals(other) { if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver'); other = ES.ToTemporalDateTime(other); - if (GetSlot(this, ISO_YEAR) !== GetSlot(other, ISO_YEAR)) return false; - if (GetSlot(this, ISO_MONTH) !== GetSlot(other, ISO_MONTH)) return false; - if (GetSlot(this, ISO_DAY) !== GetSlot(other, ISO_DAY)) return false; - if (GetSlot(this, ISO_HOUR) !== GetSlot(other, ISO_HOUR)) return false; - if (GetSlot(this, ISO_MINUTE) !== GetSlot(other, ISO_MINUTE)) return false; - if (GetSlot(this, ISO_SECOND) !== GetSlot(other, ISO_SECOND)) return false; - if (GetSlot(this, ISO_MILLISECOND) !== GetSlot(other, ISO_MILLISECOND)) return false; - if (GetSlot(this, ISO_MICROSECOND) !== GetSlot(other, ISO_MICROSECOND)) return false; - if (GetSlot(this, ISO_NANOSECOND) !== GetSlot(other, ISO_NANOSECOND)) return false; + if ( + ES.CompareISODateTime( + GetSlot(this, ISO_YEAR), + GetSlot(this, ISO_MONTH), + GetSlot(this, ISO_DAY), + GetSlot(this, ISO_HOUR), + GetSlot(this, ISO_MINUTE), + GetSlot(this, ISO_SECOND), + GetSlot(this, ISO_MILLISECOND), + GetSlot(this, ISO_MICROSECOND), + GetSlot(this, ISO_NANOSECOND), + GetSlot(other, ISO_YEAR), + GetSlot(other, ISO_MONTH), + GetSlot(other, ISO_DAY), + GetSlot(other, ISO_HOUR), + GetSlot(other, ISO_MINUTE), + GetSlot(other, ISO_SECOND), + GetSlot(other, ISO_MILLISECOND), + GetSlot(other, ISO_MICROSECOND), + GetSlot(other, ISO_NANOSECOND) + ) !== 0 + ) { + return false; + } return ES.CalendarEquals(GetSlot(this, CALENDAR), GetSlot(other, CALENDAR)); } toString(options = undefined) { @@ -461,24 +476,26 @@ export class PlainDateTime { static compare(one, two) { one = ES.ToTemporalDateTime(one); two = ES.ToTemporalDateTime(two); - const slots = [ - ISO_YEAR, - ISO_MONTH, - ISO_DAY, - ISO_HOUR, - ISO_MINUTE, - ISO_SECOND, - ISO_MILLISECOND, - ISO_MICROSECOND, - ISO_NANOSECOND - ]; - for (let index = 0; index < slots.length; index++) { - const slot = slots[index]; - const val1 = GetSlot(one, slot); - const val2 = GetSlot(two, slot); - if (val1 !== val2) return ES.ComparisonResult(val1 - val2); - } - return 0; + return ES.CompareISODateTime( + GetSlot(one, ISO_YEAR), + GetSlot(one, ISO_MONTH), + GetSlot(one, ISO_DAY), + GetSlot(one, ISO_HOUR), + GetSlot(one, ISO_MINUTE), + GetSlot(one, ISO_SECOND), + GetSlot(one, ISO_MILLISECOND), + GetSlot(one, ISO_MICROSECOND), + GetSlot(one, ISO_NANOSECOND), + GetSlot(two, ISO_YEAR), + GetSlot(two, ISO_MONTH), + GetSlot(two, ISO_DAY), + GetSlot(two, ISO_HOUR), + GetSlot(two, ISO_MINUTE), + GetSlot(two, ISO_SECOND), + GetSlot(two, ISO_MILLISECOND), + GetSlot(two, ISO_MICROSECOND), + GetSlot(two, ISO_NANOSECOND) + ); } } diff --git a/polyfill/lib/plaintime.mjs b/polyfill/lib/plaintime.mjs index 4c12163aaa..524fde65b8 100644 --- a/polyfill/lib/plaintime.mjs +++ b/polyfill/lib/plaintime.mjs @@ -324,12 +324,20 @@ export class PlainTime { static compare(one, two) { one = ES.ToTemporalTime(one); two = ES.ToTemporalTime(two); - for (const slot of [ISO_HOUR, ISO_MINUTE, ISO_SECOND, ISO_MILLISECOND, ISO_MICROSECOND, ISO_NANOSECOND]) { - const val1 = GetSlot(one, slot); - const val2 = GetSlot(two, slot); - if (val1 !== val2) return ES.ComparisonResult(val1 - val2); - } - return 0; + return ES.CompareTemporalTime( + GetSlot(one, ISO_HOUR), + GetSlot(one, ISO_MINUTE), + GetSlot(one, ISO_SECOND), + GetSlot(one, ISO_MILLISECOND), + GetSlot(one, ISO_MICROSECOND), + GetSlot(one, ISO_NANOSECOND), + GetSlot(two, ISO_HOUR), + GetSlot(two, ISO_MINUTE), + GetSlot(two, ISO_SECOND), + GetSlot(two, ISO_MILLISECOND), + GetSlot(two, ISO_MICROSECOND), + GetSlot(two, ISO_NANOSECOND) + ); } } From e92f6b4e00bfc9254c8c363f37db5eddda9b281c Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Wed, 11 Oct 2023 15:57:23 -0700 Subject: [PATCH 08/12] Editorial: Simplify reconciliation of date/time signs in NanosecondsToDays This approach was suggested by Anba. All of these steps are unobservable in any case, so they can be edited and rearranged as necessary. Closes: #2703 --- polyfill/lib/ecmascript.mjs | 40 ++++++++++--------------------------- spec/zoneddatetime.html | 24 +++++++--------------- 2 files changed, 17 insertions(+), 47 deletions(-) diff --git a/polyfill/lib/ecmascript.mjs b/polyfill/lib/ecmascript.mjs index 039af519a1..25098fb20d 100644 --- a/polyfill/lib/ecmascript.mjs +++ b/polyfill/lib/ecmascript.mjs @@ -3278,8 +3278,11 @@ export function NanosecondsToDays(nanoseconds, zonedRelativeTo, precalculatedPla // don't need the path that potentially calls calendar methods. 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( + const date1 = TemporalDateTimeToDate(dtStart); + const date2 = TemporalDateTimeToDate(dtEnd); + let days = DaysUntil(date1, date2); + + const timeSign = CompareTemporalTime( GetSlot(dtStart, ISO_HOUR), GetSlot(dtStart, ISO_MINUTE), GetSlot(dtStart, ISO_SECOND), @@ -3292,37 +3295,14 @@ export function NanosecondsToDays(nanoseconds, zonedRelativeTo, precalculatedPla GetSlot(dtEnd, ISO_MILLISECOND), GetSlot(dtEnd, ISO_MICROSECOND), 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' - )); + if (days > 0 && timeSign > 0) { + days--; + } else if (days < 0 && timeSign < 0) { + days++; } - 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 diff --git a/spec/zoneddatetime.html b/spec/zoneddatetime.html index f0d87c9fac..31f34d75f4 100644 --- a/spec/zoneddatetime.html +++ b/spec/zoneddatetime.html @@ -1431,24 +1431,14 @@

1. Let _endInstant_ be ! CreateTemporalInstant(ℤ(_endNs_)). 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]]). - 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 _date1_ be ! CreateTemporalDate(_startDateTime_.[[ISOYear]], _startDateTime_.[[ISOMonth]], _startDateTime_.[[ISODay]], *"iso8601"*). + 1. Let _date2_ be ! CreateTemporalDate(_endDateTime_.[[ISOYear]], _endDateTime_.[[ISOMonth]], _endDateTime_.[[ISODay]], *"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 _timeSign_ be CompareTemporalTime(_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. If _days_ > 0 and _timeSign_ > 0, then + 1. Set _days_ to _days_ - 1. + 1. Else if _days_ < 0 and _timeSign_ < 0, then + 1. Set _days_ to _days_ + 1. 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 6b83032ccd1f07011e8ba0844d32a5b7e3d1a8e5 Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Wed, 11 Oct 2023 16:11:11 -0700 Subject: [PATCH 09/12] Editorial: Simplify precalculation of start datetime in ZDT difference As suggested by Anba, the precalculation will be done at the start of DifferenceZonedDateTime if it is not done prior, so we can unconditionally do it prior. Closes: #2704 --- polyfill/lib/ecmascript.mjs | 23 +++++++---------------- spec/zoneddatetime.html | 13 ++++--------- 2 files changed, 11 insertions(+), 25 deletions(-) diff --git a/polyfill/lib/ecmascript.mjs b/polyfill/lib/ecmascript.mjs index 25098fb20d..b9a70686d5 100644 --- a/polyfill/lib/ecmascript.mjs +++ b/polyfill/lib/ecmascript.mjs @@ -4642,22 +4642,12 @@ 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); - } + const precalculatedPlainDateTime = GetPlainDateTimeFor( + timeZone, + GetSlot(zonedDateTime, INSTANT), + GetSlot(zonedDateTime, CALENDAR) + ); + const plainRelativeTo = TemporalDateTimeToDate(precalculatedPlainDateTime); ({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = DifferenceZonedDateTime( @@ -4670,6 +4660,7 @@ export function DifferenceTemporalZonedDateTime(operation, zonedDateTime, other, precalculatedPlainDateTime )); + const roundingIsNoop = settings.smallestUnit === 'nanosecond' && settings.roundingIncrement === 1; if (!roundingIsNoop) { ({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = RoundDuration( years, diff --git a/spec/zoneddatetime.html b/spec/zoneddatetime.html index 31f34d75f4..87534486b1 100644 --- a/spec/zoneddatetime.html +++ b/spec/zoneddatetime.html @@ -1497,16 +1497,11 @@

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. 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 _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 _instant_ be ! CreateTemporalInstant(_zonedDateTime_.[[Nanoseconds]]). + 1. Let _precalculatedPlainDateTime_ be ? GetPlainDateTimeFor(_zonedDateTime_.[[TimeZone]], _instant_, _zonedDateTime_.[[Calendar]]). + 1. Let _plainRelativeTo_ be ! 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 _settings_.[[SmallestUnit]] is *"nanosecond"* and _settings_.[[RoundingIncrement]] is 1, let _roundingGranularityIsNoop_ be *true*; else let _roundingGranularityIsNoop_ be *false*. 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_). From a47aa7a3aa0c9a977357bc915b9878c8b4eb4c03 Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Wed, 11 Oct 2023 16:16:41 -0700 Subject: [PATCH 10/12] Editorial: Correct type of parameter in AdjustRoundedDurationDays AdjustRoundedDurationDays can be called with _precalculatedPlainDateTime_ undefined, but it may only be undefined if we take the fast path. Express this better with an assertion. Thanks to Anba for pointing this out. Closes: #2705 --- spec/duration.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/duration.html b/spec/duration.html index c5e40a3632..21b6d4ec17 100644 --- a/spec/duration.html +++ b/spec/duration.html @@ -1897,7 +1897,7 @@

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

@@ -1911,6 +1911,7 @@

1. If _unit_ is one of *"year"*, *"month"*, *"week"*, or *"day"*; or _unit_ is *"nanosecond"* and _increment_ is 1, then 1. Return ! CreateDurationRecord(_years_, _months_, _weeks_, _days_, _hours_, _minutes_, _seconds_, _milliseconds_, _microseconds_, _nanoseconds_). + 1. Assert: _precalculatedPlainDateTime_ is not *undefined*. 1. Let _timeRemainderNs_ be TotalDurationNanoseconds(_hours_, _minutes_, _seconds_, _milliseconds_, _microseconds_, _nanoseconds_). 1. If _timeRemainderNs_ = 0, let _direction_ be 0. 1. Else if _timeRemainderNs_ < 0, let _direction_ be -1. From cad78f389f586d67e3d3eac436ec1a130d7e5264 Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Wed, 11 Oct 2023 16:25:15 -0700 Subject: [PATCH 11/12] Editorial: Remove redundant condition for calculating datetime In Duration.p.round(), the smallestUnit condition for precalculating the PlainDateTime is already subsumed by the roundingGranularityIsNoop condition, so also checking smallestUnit is redundant. Thanks to Anba for pointing this out. Closes: #2698 --- polyfill/lib/duration.mjs | 4 ---- spec/duration.html | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/polyfill/lib/duration.mjs b/polyfill/lib/duration.mjs index c15e0d9b06..9d46cd1887 100644 --- a/polyfill/lib/duration.mjs +++ b/polyfill/lib/duration.mjs @@ -312,10 +312,6 @@ export class Duration { largestUnit === 'month' || largestUnit === 'week' || largestUnit === 'day' || - smallestUnit === 'year' || - smallestUnit === 'month' || - smallestUnit === 'week' || - smallestUnit === 'day' || calendarUnitsPresent || days !== 0; if (zonedRelativeTo && plainDateTimeOrRelativeToWillBeUsed) { diff --git a/spec/duration.html b/spec/duration.html index 21b6d4ec17..10262e4f0c 100644 --- a/spec/duration.html +++ b/spec/duration.html @@ -460,7 +460,7 @@

Temporal.Duration.prototype.round ( _roundTo_ )

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*. - 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 _roundingGranularityIsNoop_ is *false*, or _largestUnit_ is *"year"*, or _largestUnit_ is *"month"*, or _largestUnit_ is *"week"*, or _largestUnit_ 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]]). From f35608bfd8a25692da60b355ae5406b750609fff Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Wed, 11 Oct 2023 16:31:49 -0700 Subject: [PATCH 12/12] Editorial: Simplify calculation of time duration in AdjustRounded...Days As pointed out by Anba, in this call to RoundDuration the years...days parameters are not used within RoundDuration because unit is less than days. They are returned unchanged and discarded, so we may as well not pass them in. This change isn't observable by userland code. Closes: #2617 --- polyfill/lib/ecmascript.mjs | 8 ++++---- spec/duration.html | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/polyfill/lib/ecmascript.mjs b/polyfill/lib/ecmascript.mjs index b9a70686d5..e14ddbe75f 100644 --- a/polyfill/lib/ecmascript.mjs +++ b/polyfill/lib/ecmascript.mjs @@ -5571,10 +5571,10 @@ export function AdjustRoundedDurationDays( precalculatedPlainDateTime )); ({ hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = RoundDuration( - years, - months, - weeks, - days, + 0, + 0, + 0, + 0, 0, 0, 0, diff --git a/spec/duration.html b/spec/duration.html index 10262e4f0c..02e92ddbf1 100644 --- a/spec/duration.html +++ b/spec/duration.html @@ -1925,7 +1925,7 @@

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_, _precalculatedPlainDateTime_). - 1. Let _adjustedTimeDuration_ be ! RoundDuration(_adjustedDateDuration_.[[Years]], _adjustedDateDuration_.[[Months]], _adjustedDateDuration_.[[Weeks]], _adjustedDateDuration_.[[Days]], 0, 0, 0, 0, 0, _oneDayLess_, _increment_, _unit_, _roundingMode_). + 1. Let _adjustedTimeDuration_ be ! RoundDuration(0, 0, 0, 0, 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]]).