From 682b7eb0ecfdcf38ad061d830e97270ca507d6cc Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Fri, 26 Jan 2024 17:45:18 -0800 Subject: [PATCH 1/4] Normative: Fix intermediate value in ZonedDateTime difference To find the difference between two ZonedDateTimes, we first find the calendar difference between their date parts. Then we find the time difference between an intermediate ZonedDateTime value (calculated by adding the calendar parts to the first ZonedDateTime) and the second ZonedDateTime. Previously we would calculate the intermediate value by adding years, months, and weeks from the date difference, because the days were calculated as part of the time difference. This was incorrect, because the intermediate value can shift if it falls in the middle of a DST transition. However, that could be on a completely different date than would be expected, leading to surprising results like this: const duration = Temporal.Duration.from({ months: 1, days: 15, hours: 12 }); const past = Temporal.ZonedDateTime.from('2024-02-10T02:00[America/New_York]'); const future = past.add(duration); duration // => 1 month, 15 days, 12 hours past.until(future, { largestUnit: 'months' }) // => 1 month, 15 days, 11 hours This result would occur because of a DST change on 2024-03-10T02:00, unrelated to either the start date of 2024-02-10 or the end date of 2024-03-25. With this change, the intermediate value occurs on 2024-03-25T02:00 and would only shift if that was when the DST change occurred. --- polyfill/lib/ecmascript.mjs | 108 ++++++++++++++++++++++-------------- spec/zoneddatetime.html | 27 +++++++-- 2 files changed, 88 insertions(+), 47 deletions(-) diff --git a/polyfill/lib/ecmascript.mjs b/polyfill/lib/ecmascript.mjs index 700768a731..4da8de0e05 100644 --- a/polyfill/lib/ecmascript.mjs +++ b/polyfill/lib/ecmascript.mjs @@ -3908,49 +3908,75 @@ export function DifferenceZonedDateTime( const dtStart = precalculatedDtStart ?? GetPlainDateTimeFor(timeZoneRec, start, calendarRec.receiver); const dtEnd = GetPlainDateTimeFor(timeZoneRec, end, calendarRec.receiver); - let { years, months, weeks } = DifferenceISODateTime( - GetSlot(dtStart, ISO_YEAR), - GetSlot(dtStart, ISO_MONTH), - GetSlot(dtStart, ISO_DAY), - 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), - calendarRec, - largestUnit, - options - ); - let intermediateNs = AddZonedDateTime( - start, - timeZoneRec, - calendarRec, - years, - months, - weeks, - 0, - TimeDuration.ZERO, - dtStart - ); - // may disambiguate + // Simulate moving ns1 as many years/months/weeks/days as possible without + // surpassing ns2. This value is stored in intermediateDateTime/intermediate. + // We do not literally move years/months/weeks/days with calendar arithmetic, + // but rather assume intermediateDateTime will have the same time-parts as + // dtStart and the date-parts from dtEnd, and move backward from there. + // + // This loop will run 3 times max: + // 1. initial run + // 2. backoff if the time-parts of intermediateDateTime have conflicting sign + // with overall diff direction, just like how DifferenceISODateTime works + // 3. backoff if intermediateDateTime fell into a DST gap and was pushed in a + // direction that would make the diff of the time-parts conflict with the + // sign of the overall direction. (Only possible when sign is +1, because + // a DST gap uses 'compatible' disambiguation resolution and can only move + // the intermediate forward) + // + // Credit to Adam Shaw for devising this algorithm. + const sign = nsDiff.lt(0) ? -1 : 1; + const maxTries = sign === 1 ? 3 : 2; + for (let dayCorrection = 0; dayCorrection < maxTries; dayCorrection++) { + const correctedEndDate = BalanceISODate( + GetSlot(dtEnd, ISO_YEAR), + GetSlot(dtEnd, ISO_MONTH), + GetSlot(dtEnd, ISO_DAY) - dayCorrection * sign + ); - let norm = TimeDuration.fromEpochNsDiff(ns2, intermediateNs); - const intermediate = CreateTemporalZonedDateTime(intermediateNs, timeZoneRec.receiver, calendarRec.receiver); - let days; - ({ norm, days } = NormalizedTimeDurationToDays(norm, intermediate, timeZoneRec)); + // Incorporate time parts from dtStart + const intermediateDateTime = CreateTemporalDateTime( + correctedEndDate.year, + correctedEndDate.month, + correctedEndDate.day, + GetSlot(dtStart, ISO_HOUR), + GetSlot(dtStart, ISO_MINUTE), + GetSlot(dtStart, ISO_SECOND), + GetSlot(dtStart, ISO_MILLISECOND), + GetSlot(dtStart, ISO_MICROSECOND), + GetSlot(dtStart, ISO_NANOSECOND), + calendarRec.receiver + ); + const intermediate = GetInstantFor(timeZoneRec, intermediateDateTime, 'compatible'); + // may disambiguate + const intermediateNs = GetSlot(intermediate, EPOCHNANOSECONDS); + + // Did intermediateNs surpass ns2? + const norm = TimeDuration.fromEpochNsDiff(ns2, intermediateNs); + const timeSign = norm.sign(); + if (sign === 0 || timeSign == 0 || sign === timeSign) { + // sign of timeDuration now compatible with the overall sign + + // Similar to what happens in DifferenceISODateTime with date parts only: + const date1 = TemporalDateTimeToDate(dtStart); + const date2 = TemporalDateTimeToDate(intermediateDateTime); + const dateLargestUnit = LargerOfTwoTemporalUnits('day', largestUnit); + const untilOptions = SnapshotOwnProperties(options, null); + untilOptions.largestUnit = dateLargestUnit; + const dateDifference = DifferenceDate(calendarRec, date1, date2, untilOptions); + const years = GetSlot(dateDifference, YEARS); + const months = GetSlot(dateDifference, MONTHS); + const weeks = GetSlot(dateDifference, WEEKS); + const days = GetSlot(dateDifference, DAYS); - CombineDateAndNormalizedTimeDuration(years, months, weeks, days, norm); - return { years, months, weeks, days, norm }; + CombineDateAndNormalizedTimeDuration(years, months, weeks, days, norm); + return { years, months, weeks, days, norm }; + } + // Else, keep backing off... + } + throw new RangeError( + `inconsistent return from calendar or time zone method: more than ${maxTries - 1} days correction needed` + ); } export function GetDifferenceSettings(op, options, group, disallowed, fallbackSmallest, smallestLargestDefaultUnit) { diff --git a/spec/zoneddatetime.html b/spec/zoneddatetime.html index 4193144aaa..2b02ef1fed 100644 --- a/spec/zoneddatetime.html +++ b/spec/zoneddatetime.html @@ -1401,12 +1401,27 @@

1. Let _startDateTime_ be _precalculatedPlainDateTime_. 1. Let _endInstant_ be ! CreateTemporalInstant(_ns2_). 1. Let _endDateTime_ be ? GetPlainDateTimeFor(_timeZoneRec_, _endInstant_, _calendarRec_.[[Receiver]]). - 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]], _calendarRec_, _largestUnit_, _options_). - 1. Let _intermediateNs_ be ? AddZonedDateTime(_ns1_, _timeZoneRec_, _calendarRec_, _dateDifference_.[[Years]], _dateDifference_.[[Months]], _dateDifference_.[[Weeks]], 0, ZeroTimeDuration(), _startDateTime_). - 1. Let _norm_ be NormalizedTimeDurationFromEpochNanosecondsDifference(_ns2_, _intermediateNs_). - 1. Let _intermediate_ be ! CreateTemporalZonedDateTime(_intermediateNs_, _timeZoneRec_.[[Receiver]], _calendarRec_.[[Receiver]]). - 1. Let _result_ be ? NormalizedTimeDurationToDays(_norm_, _intermediate_, _timeZoneRec_). - 1. Return ! CreateNormalizedDurationRecord(_dateDifference_.[[Years]], _dateDifference_.[[Months]], _dateDifference_.[[Weeks]], _result_.[[Days]], _result_.[[Remainder]]). + 1. If _ns2_ - _ns1_ < 0, let _sign_ be -1; else let _sign_ be 1. + 1. If _sign_ = 1, let _maxTries_ be 3; else let _maxTries_ be 2. + 1. Let _dayCorrection_ be 0. + 1. Repeat _maxTries_ times: + 1. Let _correctedEndDate_ be BalanceISODate(_endDateTime_.[[ISOYear]], _endDateTime_.[[ISOMonth]], _endDateTime_.[[ISODay]] - _dayCorrection_ × _sign_). + 1. Let _intermediateDateTime_ be ! CreateTemporalDateTime(_correctedEndDate_.[[Year]], _correctedEndDate_.[[Month]], _correctedEndDate_.[[Day]], _startDateTime_.[[ISOHour]], _startDateTime_.[[ISOMinute]], _startDateTime_.[[ISOSecond]], _startDateTime_.[[ISOMillisecond]], _startDateTime_.[[ISOMicrosecond]], _startDateTime_.[[ISONanosecond]], _calendarRec_.[[Receiver]]). + 1. Let _intermediate_ be ? GetInstantFor(_timeZoneRec_, _intermediateDateTime_, *"compatible"*). + 1. Let _intermediateNs_ be _intermediate_.[[Nanoseconds]]. + 1. Let _norm_ be NormalizedTimeDurationFromEpochNanosecondsDifference(_ns2_, _intermediateNs_). + 1. Let _timeSign_ be NormalizedTimeDurationSign(_norm_). + 1. If _sign_ = 0, or _timeSign_ = 0, or _sign_ = _timeSign_, then + 1. Let _date1_ be ! CreateTemporalDate(_startDateTime_.[[ISOYear]], _startDateTime_.[[ISOMonth]], _startDateTime_.[[ISODay]], _calendarRec_.[[Receiver]]). + 1. Let _date2_ be ! CreateTemporalDate(_correctedEndDate_.[[Year]], _correctedEndDate_.[[Month]], _correctedEndDate_.[[Day]], _calendarRec_.[[Receiver]]). + 1. Let _dateLargestUnit_ be LargerOfTwoTemporalUnits(_largestUnit_, *"day"*). + 1. Let _untilOptions_ be ? SnapshotOwnProperties(_options_, *null*). + 1. Perform ! CreateDataPropertyOrThrow(_untilOptions_, *"largestUnit"*, _dateLargestUnit_). + 1. Let _dateDifference_ be ? DifferenceDate(_calendarRec_, _date1_, _date2_, _untilOptions_). + 1. Return ? CreateNormalizedDurationRecord(_dateDifference_.[[Years]], _dateDifference_.[[Months]], _dateDifference_.[[Weeks]], _dateDifference_.[[Days]], _norm_). + 1. Set _dayCorrection_ to _dayCorrection_ + 1. + 1. NOTE: This step is only reached when custom calendar or time zone methods return inconsistent values. + 1. Throw a *RangeError* exception. From d7d158c7a0fb78c3e18cc46b6b33239c187b0e52 Mon Sep 17 00:00:00 2001 From: Adam Shaw Date: Thu, 7 Mar 2024 19:03:56 -0500 Subject: [PATCH 2/4] Updates to ZonedDateTime diffing algorithm and dayCorrection. Fixes bug mentioned here: https://github.com/tc39/proposal-temporal/pull/2760#issuecomment-1971930951 --- polyfill/lib/ecmascript.mjs | 122 ++++++++++++++++++++++-------------- polyfill/test262 | 2 +- 2 files changed, 75 insertions(+), 49 deletions(-) diff --git a/polyfill/lib/ecmascript.mjs b/polyfill/lib/ecmascript.mjs index 4da8de0e05..525010a3b6 100644 --- a/polyfill/lib/ecmascript.mjs +++ b/polyfill/lib/ecmascript.mjs @@ -3900,8 +3900,9 @@ export function DifferenceZonedDateTime( norm: TimeDuration.ZERO }; } + const sign = nsDiff.lt(0) ? -1 : 1; - // Find the difference in dates only. + // Convert start/end instants to datetimes const TemporalInstant = GetIntrinsic('%Temporal.Instant%'); const start = new TemporalInstant(ns1); const end = new TemporalInstant(ns2); @@ -3909,36 +3910,56 @@ export function DifferenceZonedDateTime( const dtEnd = GetPlainDateTimeFor(timeZoneRec, end, calendarRec.receiver); // Simulate moving ns1 as many years/months/weeks/days as possible without - // surpassing ns2. This value is stored in intermediateDateTime/intermediate. + // surpassing ns2. This value is stored in intermediateDateTime/intermediateInstant/intermediateNs. // We do not literally move years/months/weeks/days with calendar arithmetic, // but rather assume intermediateDateTime will have the same time-parts as // dtStart and the date-parts from dtEnd, and move backward from there. - // - // This loop will run 3 times max: - // 1. initial run - // 2. backoff if the time-parts of intermediateDateTime have conflicting sign - // with overall diff direction, just like how DifferenceISODateTime works - // 3. backoff if intermediateDateTime fell into a DST gap and was pushed in a - // direction that would make the diff of the time-parts conflict with the - // sign of the overall direction. (Only possible when sign is +1, because - // a DST gap uses 'compatible' disambiguation resolution and can only move - // the intermediate forward) - // + // The number of days we move backward is stored in dayCorrection. // Credit to Adam Shaw for devising this algorithm. - const sign = nsDiff.lt(0) ? -1 : 1; - const maxTries = sign === 1 ? 3 : 2; - for (let dayCorrection = 0; dayCorrection < maxTries; dayCorrection++) { - const correctedEndDate = BalanceISODate( + let dayCorrection = 0; + let intermediateDateTime; + let norm; + + // The max number of allowed day corrections depends on the direction of travel. + // Both directions allow for 1 day correction due to an ISO wall-clock overshoot (see below). + // Only the forward direction allows for an additional 1 day correction caused by a push-forward + // 'compatible' DST transition causing the wall-clock to overshoot again. + // This max value is inclusive. + let maxDayCorrection = sign === 1 ? 2 : 1; + + // Detect ISO wall-clock overshoot. + // If the diff of the ISO wall-clock times is opposite to the overall diff's sign, + // we are guaranteed to need at least one day correction. + let timeDuration = 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_HOUR), + GetSlot(dtEnd, ISO_MINUTE), + GetSlot(dtEnd, ISO_SECOND), + GetSlot(dtEnd, ISO_MILLISECOND), + GetSlot(dtEnd, ISO_MICROSECOND), + GetSlot(dtEnd, ISO_NANOSECOND) + ); + if (timeDuration.sign() === -sign) { + dayCorrection++; + } + + for (; dayCorrection <= maxDayCorrection; dayCorrection++) { + const intermediateDate = BalanceISODate( GetSlot(dtEnd, ISO_YEAR), GetSlot(dtEnd, ISO_MONTH), GetSlot(dtEnd, ISO_DAY) - dayCorrection * sign ); // Incorporate time parts from dtStart - const intermediateDateTime = CreateTemporalDateTime( - correctedEndDate.year, - correctedEndDate.month, - correctedEndDate.day, + intermediateDateTime = CreateTemporalDateTime( + intermediateDate.year, + intermediateDate.month, + intermediateDate.day, GetSlot(dtStart, ISO_HOUR), GetSlot(dtStart, ISO_MINUTE), GetSlot(dtStart, ISO_SECOND), @@ -3947,36 +3968,41 @@ export function DifferenceZonedDateTime( GetSlot(dtStart, ISO_NANOSECOND), calendarRec.receiver ); - const intermediate = GetInstantFor(timeZoneRec, intermediateDateTime, 'compatible'); - // may disambiguate - const intermediateNs = GetSlot(intermediate, EPOCHNANOSECONDS); - - // Did intermediateNs surpass ns2? - const norm = TimeDuration.fromEpochNsDiff(ns2, intermediateNs); - const timeSign = norm.sign(); - if (sign === 0 || timeSign == 0 || sign === timeSign) { - // sign of timeDuration now compatible with the overall sign - - // Similar to what happens in DifferenceISODateTime with date parts only: - const date1 = TemporalDateTimeToDate(dtStart); - const date2 = TemporalDateTimeToDate(intermediateDateTime); - const dateLargestUnit = LargerOfTwoTemporalUnits('day', largestUnit); - const untilOptions = SnapshotOwnProperties(options, null); - untilOptions.largestUnit = dateLargestUnit; - const dateDifference = DifferenceDate(calendarRec, date1, date2, untilOptions); - const years = GetSlot(dateDifference, YEARS); - const months = GetSlot(dateDifference, MONTHS); - const weeks = GetSlot(dateDifference, WEEKS); - const days = GetSlot(dateDifference, DAYS); - CombineDateAndNormalizedTimeDuration(years, months, weeks, days, norm); - return { years, months, weeks, days, norm }; + // Convert intermediate datetime to epoch-nanoseconds (may disambiguate) + const intermediateInstant = GetInstantFor(timeZoneRec, intermediateDateTime, 'compatible'); + const intermediateNs = GetSlot(intermediateInstant, EPOCHNANOSECONDS); + + // Compute the nanosecond diff between the intermediate instant and the final destination + norm = TimeDuration.fromEpochNsDiff(ns2, intermediateNs); + + // Did intermediateNs NOT surpass ns2? + // If so, exit the loop with success (without incrementing dayCorrection past maxDayCorrection) + if (norm.sign() !== -sign) { + break; } - // Else, keep backing off... } - throw new RangeError( - `inconsistent return from calendar or time zone method: more than ${maxTries - 1} days correction needed` - ); + + if (dayCorrection > maxDayCorrection) { + throw new RangeError( + `inconsistent return from calendar or time zone method: more than ${maxDayCorrection} day correction needed` + ); + } + + // Similar to what happens in DifferenceISODateTime with date parts only: + const date1 = TemporalDateTimeToDate(dtStart); + const date2 = TemporalDateTimeToDate(intermediateDateTime); + const dateLargestUnit = LargerOfTwoTemporalUnits('day', largestUnit); + const untilOptions = SnapshotOwnProperties(options, null); + untilOptions.largestUnit = dateLargestUnit; + const dateDifference = DifferenceDate(calendarRec, date1, date2, untilOptions); + const years = GetSlot(dateDifference, YEARS); + const months = GetSlot(dateDifference, MONTHS); + const weeks = GetSlot(dateDifference, WEEKS); + const days = GetSlot(dateDifference, DAYS); + + CombineDateAndNormalizedTimeDuration(years, months, weeks, days, norm); + return { years, months, weeks, days, norm }; } export function GetDifferenceSettings(op, options, group, disallowed, fallbackSmallest, smallestLargestDefaultUnit) { diff --git a/polyfill/test262 b/polyfill/test262 index 0fd1675f7e..2f36b42562 160000 --- a/polyfill/test262 +++ b/polyfill/test262 @@ -1 +1 @@ -Subproject commit 0fd1675f7ed02723772d30e718f04e6af455d3c9 +Subproject commit 2f36b42562e07b8d9e3d51810b5596a2f291e57b From ac1aa00e90379fa6b73b85dc618b64ba3e8e625f Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Fri, 29 Mar 2024 17:46:54 -0700 Subject: [PATCH 3/4] Incorporate Adam's updates in spec text --- spec/zoneddatetime.html | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/spec/zoneddatetime.html b/spec/zoneddatetime.html index 2b02ef1fed..b0767b70d0 100644 --- a/spec/zoneddatetime.html +++ b/spec/zoneddatetime.html @@ -1402,24 +1402,29 @@

1. Let _endInstant_ be ! CreateTemporalInstant(_ns2_). 1. Let _endDateTime_ be ? GetPlainDateTimeFor(_timeZoneRec_, _endInstant_, _calendarRec_.[[Receiver]]). 1. If _ns2_ - _ns1_ < 0, let _sign_ be -1; else let _sign_ be 1. - 1. If _sign_ = 1, let _maxTries_ be 3; else let _maxTries_ be 2. + 1. If _sign_ = 1, let _maxDayCorrection_ be 2; else let _maxDayCorrection_ be 1. 1. Let _dayCorrection_ be 0. - 1. Repeat _maxTries_ times: - 1. Let _correctedEndDate_ be BalanceISODate(_endDateTime_.[[ISOYear]], _endDateTime_.[[ISOMonth]], _endDateTime_.[[ISODay]] - _dayCorrection_ × _sign_). - 1. Let _intermediateDateTime_ be ! CreateTemporalDateTime(_correctedEndDate_.[[Year]], _correctedEndDate_.[[Month]], _correctedEndDate_.[[Day]], _startDateTime_.[[ISOHour]], _startDateTime_.[[ISOMinute]], _startDateTime_.[[ISOSecond]], _startDateTime_.[[ISOMillisecond]], _startDateTime_.[[ISOMicrosecond]], _startDateTime_.[[ISONanosecond]], _calendarRec_.[[Receiver]]). - 1. Let _intermediate_ be ? GetInstantFor(_timeZoneRec_, _intermediateDateTime_, *"compatible"*). - 1. Let _intermediateNs_ be _intermediate_.[[Nanoseconds]]. + 1. Let _timeDuration_ 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. If NormalizedTimeDurationSign(_timeDuration_) = -_sign_, set _dayCorrection_ to _dayCorrection_ + 1. + 1. Let _success_ be *false*. + 1. Repeat, while _dayCorrection_ ≤ _maxDayCorrection_ and _success_ is *false*, + 1. Let _intermediateDate_ be BalanceISODate(_endDateTime_.[[ISOYear]], _endDateTime_.[[ISOMonth]], _endDateTime_.[[ISODay]] - _dayCorrection_ × _sign_). + 1. Let _intermediateDateTime_ be ! CreateTemporalDateTime(_intermediateDate_.[[Year]], _intermediateDate_.[[Month]], _intermediateDate_.[[Day]], _startDateTime_.[[ISOHour]], _startDateTime_.[[ISOMinute]], _startDateTime_.[[ISOSecond]], _startDateTime_.[[ISOMillisecond]], _startDateTime_.[[ISOMicrosecond]], _startDateTime_.[[ISONanosecond]], _calendarRec_.[[Receiver]]). + 1. Let _intermediateInstant_ be ? GetInstantFor(_timeZoneRec_, _intermediateDateTime_, *"compatible"*). + 1. Let _intermediateNs_ be _intermediateInstant_.[[Nanoseconds]]. 1. Let _norm_ be NormalizedTimeDurationFromEpochNanosecondsDifference(_ns2_, _intermediateNs_). 1. Let _timeSign_ be NormalizedTimeDurationSign(_norm_). - 1. If _sign_ = 0, or _timeSign_ = 0, or _sign_ = _timeSign_, then - 1. Let _date1_ be ! CreateTemporalDate(_startDateTime_.[[ISOYear]], _startDateTime_.[[ISOMonth]], _startDateTime_.[[ISODay]], _calendarRec_.[[Receiver]]). - 1. Let _date2_ be ! CreateTemporalDate(_correctedEndDate_.[[Year]], _correctedEndDate_.[[Month]], _correctedEndDate_.[[Day]], _calendarRec_.[[Receiver]]). - 1. Let _dateLargestUnit_ be LargerOfTwoTemporalUnits(_largestUnit_, *"day"*). - 1. Let _untilOptions_ be ? SnapshotOwnProperties(_options_, *null*). - 1. Perform ! CreateDataPropertyOrThrow(_untilOptions_, *"largestUnit"*, _dateLargestUnit_). - 1. Let _dateDifference_ be ? DifferenceDate(_calendarRec_, _date1_, _date2_, _untilOptions_). - 1. Return ? CreateNormalizedDurationRecord(_dateDifference_.[[Years]], _dateDifference_.[[Months]], _dateDifference_.[[Weeks]], _dateDifference_.[[Days]], _norm_). + 1. If _sign_ ≠ -_timeSign_, then + 1. Set _success_ to *true*. 1. Set _dayCorrection_ to _dayCorrection_ + 1. + 1. If _success_ is *true*, then + 1. Let _date1_ be ! CreateTemporalDate(_startDateTime_.[[ISOYear]], _startDateTime_.[[ISOMonth]], _startDateTime_.[[ISODay]], _calendarRec_.[[Receiver]]). + 1. Let _date2_ be ! CreateTemporalDate(_intermediateDateTime_.[[Year]], _intermediateDateTime_.[[Month]], _intermediateDateTime_.[[Day]], _calendarRec_.[[Receiver]]). + 1. Let _dateLargestUnit_ be LargerOfTwoTemporalUnits(_largestUnit_, *"day"*). + 1. Let _untilOptions_ be ? SnapshotOwnProperties(_options_, *null*). + 1. Perform ! CreateDataPropertyOrThrow(_untilOptions_, *"largestUnit"*, _dateLargestUnit_). + 1. Let _dateDifference_ be ? DifferenceDate(_calendarRec_, _date1_, _date2_, _untilOptions_). + 1. Return ? CreateNormalizedDurationRecord(_dateDifference_.[[Years]], _dateDifference_.[[Months]], _dateDifference_.[[Weeks]], _dateDifference_.[[Days]], _norm_). 1. NOTE: This step is only reached when custom calendar or time zone methods return inconsistent values. 1. Throw a *RangeError* exception. From 3f30bd099d93d9559460b51e0e855d65a86a9d8d Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Mon, 1 Apr 2024 17:45:55 -0700 Subject: [PATCH 4/4] Update test262 --- polyfill/test262 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/polyfill/test262 b/polyfill/test262 index 2f36b42562..1e3d8cbb37 160000 --- a/polyfill/test262 +++ b/polyfill/test262 @@ -1 +1 @@ -Subproject commit 2f36b42562e07b8d9e3d51810b5596a2f291e57b +Subproject commit 1e3d8cbb37476cd957a6a5c7295004901f30b2c4