From 289936913abdcb09ee690b0c892b3a0d8aaebbec Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Mon, 12 Jun 2023 07:49:14 -0700 Subject: [PATCH] Editorial?: Use normalized time duration in operations This introduces Normalized Time Duration Records, which we use to encapsulate 96-bit integer operations on duration times. (In the reference polyfill, the TimeDuration class fulfills the same purpose.) These operations are specified naively in the mathematical value domain, but can be changed in a later editorial commit to correspond to how implementations would write 64+32 bit operations, if we so desire. (The results must be exactly the same, so that can be decided later, outside of a TC39 plenary.) This commit also replaces TotalDurationNanoseconds with NormalizeTimeDuration, and NanosecondsToDays with NormalizedTimeDurationToDays. Several operations are changed to return a Normalized Duration Record, which is a Normalized Time Duration record combined with a Date Duration Record. Having already limited time units of durations in the previous commit, this does not affect any results, nor any existing tests in test262. But I can't prove conclusively that there isn't some edge case somewhere that makes this change observable. (also obsoletes several pre-existing editorial mistakes) Closes: #2536 Closes: #2638 Closes: #2616 --- polyfill/lib/calendar.mjs | 8 +- polyfill/lib/duration.mjs | 221 +++----- polyfill/lib/ecmascript.mjs | 929 +++++++++++---------------------- polyfill/lib/math.mjs | 23 + polyfill/lib/timeduration.mjs | 148 ++++++ polyfill/lib/zoneddatetime.mjs | 6 +- polyfill/test/all.mjs | 3 + polyfill/test/math.mjs | 45 +- polyfill/test/timeduration.mjs | 514 ++++++++++++++++++ spec/abstractops.html | 13 +- spec/calendar.html | 5 +- spec/duration.html | 677 ++++++++++++++++++------ spec/instant.html | 40 +- spec/intl.html | 7 +- spec/plaindate.html | 7 +- spec/plaindatetime.html | 45 +- spec/plaintime.html | 36 +- spec/plainyearmonth.html | 12 +- spec/timezone.html | 6 +- spec/zoneddatetime.html | 99 ++-- 20 files changed, 1798 insertions(+), 1046 deletions(-) create mode 100644 polyfill/lib/timeduration.mjs create mode 100644 polyfill/test/timeduration.mjs diff --git a/polyfill/lib/calendar.mjs b/polyfill/lib/calendar.mjs index 8fcddb9164..ee6bc9ea06 100644 --- a/polyfill/lib/calendar.mjs +++ b/polyfill/lib/calendar.mjs @@ -22,6 +22,7 @@ import { HasSlot, SetSlot } from './slots.mjs'; +import { TimeDuration } from './timeduration.mjs'; const ArrayFrom = Array.from; const ArrayIncludes = Array.prototype.includes; @@ -157,16 +158,15 @@ export class Calendar { duration = ES.ToTemporalDuration(duration); options = ES.GetOptionsObject(options); const overflow = ES.ToTemporalOverflow(options); - const { days } = ES.BalanceTimeDuration( - GetSlot(duration, DAYS), + const norm = TimeDuration.normalize( GetSlot(duration, HOURS), GetSlot(duration, MINUTES), GetSlot(duration, SECONDS), GetSlot(duration, MILLISECONDS), GetSlot(duration, MICROSECONDS), - GetSlot(duration, NANOSECONDS), - 'day' + GetSlot(duration, NANOSECONDS) ); + const days = GetSlot(duration, DAYS) + ES.BalanceTimeDuration(norm, 'day').days; const id = GetSlot(this, CALENDAR_ID); return impl[id].dateAdd( date, diff --git a/polyfill/lib/duration.mjs b/polyfill/lib/duration.mjs index c49c41384a..ec9a0b5168 100644 --- a/polyfill/lib/duration.mjs +++ b/polyfill/lib/duration.mjs @@ -1,7 +1,5 @@ /* global __debug__ */ -import bigInt from 'big-integer'; - import * as ES from './ecmascript.mjs'; import { MakeIntrinsicClass } from './intrinsicclass.mjs'; import { CalendarMethodRecord } from './methodrecord.mjs'; @@ -18,10 +16,12 @@ import { NANOSECONDS, CALENDAR, INSTANT, + EPOCHNANOSECONDS, CreateSlots, GetSlot, SetSlot } from './slots.mjs'; +import { TimeDuration } from './timeduration.mjs'; const MathAbs = Math.abs; const ObjectCreate = Object.create; @@ -65,6 +65,7 @@ export class Duration { SetSlot(this, NANOSECONDS, nanoseconds); if (typeof __debug__ !== 'undefined' && __debug__) { + const normSeconds = TimeDuration.normalize(0, 0, seconds, milliseconds, microseconds, nanoseconds); Object.defineProperty(this, '_repr_', { value: `Temporal.Duration <${ES.TemporalDurationToString( years, @@ -73,10 +74,7 @@ export class Duration { days, hours, minutes, - seconds, - milliseconds, - microseconds, - nanoseconds + normSeconds )}>`, writable: false, enumerable: false, @@ -340,56 +338,40 @@ export class Duration { plainRelativeTo, calendarRec )); - ({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = - ES.RoundDuration( + let norm = TimeDuration.normalize(hours, minutes, seconds, milliseconds, microseconds, nanoseconds); + ({ years, months, weeks, days, norm } = ES.RoundDuration( + years, + months, + weeks, + days, + norm, + roundingIncrement, + smallestUnit, + roundingMode, + plainRelativeTo, + calendarRec, + zonedRelativeTo, + timeZoneRec, + precalculatedPlainDateTime + )); + if (zonedRelativeTo) { + ({ years, months, weeks, days, norm } = ES.AdjustRoundedDurationDays( years, months, weeks, days, - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, + norm, roundingIncrement, smallestUnit, roundingMode, - plainRelativeTo, - calendarRec, zonedRelativeTo, + calendarRec, timeZoneRec, precalculatedPlainDateTime )); - if (zonedRelativeTo) { - ({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = - ES.AdjustRoundedDurationDays( - years, - months, - weeks, - days, - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, - roundingIncrement, - smallestUnit, - roundingMode, - zonedRelativeTo, - calendarRec, - timeZoneRec, - precalculatedPlainDateTime - )); ({ days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = ES.BalanceTimeDurationRelative( days, - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, + norm, largestUnit, zonedRelativeTo, timeZoneRec, @@ -397,13 +379,7 @@ export class Duration { )); } else { ({ days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = ES.BalanceTimeDuration( - days, - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, + norm.add24HourDays(days), largestUnit )); } @@ -480,6 +456,7 @@ export class Duration { plainRelativeTo, calendarRec )); + let norm; // If the unit we're totalling is smaller than `days`, convert days down to that unit. if (zonedRelativeTo) { const intermediate = ES.MoveRelativeZonedDateTime( @@ -492,29 +469,29 @@ export class Duration { 0, precalculatedPlainDateTime ); - ({ days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = ES.BalanceTimeDurationRelative( - days, - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, - unit, - intermediate, - timeZoneRec - )); + norm = TimeDuration.normalize(hours, minutes, seconds, milliseconds, microseconds, nanoseconds); + + // Inline BalanceTimeDurationRelative, without the final balance step + const start = GetSlot(intermediate, INSTANT); + const startNs = GetSlot(intermediate, EPOCHNANOSECONDS); + let intermediateNs = startNs; + let startDt; + if (days !== 0) { + startDt = ES.GetPlainDateTimeFor(timeZoneRec, start, 'iso8601'); + intermediateNs = ES.AddDaysToZonedDateTime(start, startDt, timeZoneRec, 'iso8601', days).epochNs; + } + const endNs = ES.AddInstant(intermediateNs, norm); + norm = TimeDuration.fromEpochNsDiff(endNs, startNs); + if (unit === 'year' || unit === 'month' || unit === 'week' || unit === 'day') { + if (!norm.isZero()) startDt ??= ES.GetPlainDateTimeFor(timeZoneRec, start, 'iso8601'); + ({ days, norm } = ES.NormalizedTimeDurationToDays(norm, intermediate, timeZoneRec, startDt)); + } else { + days = 0; + } } else { - ({ days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = ES.BalanceTimeDuration( - days, - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, - unit - )); + norm = TimeDuration.normalize(hours, minutes, seconds, milliseconds, microseconds, nanoseconds); + norm = norm.add24HourDays(days); + days = 0; } // Finally, truncate to the correct unit and calculate remainder const { total } = ES.RoundDuration( @@ -522,12 +499,7 @@ export class Duration { months, weeks, days, - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, + norm, 1, unit, 'trunc', @@ -562,6 +534,7 @@ export class Duration { let nanoseconds = GetSlot(this, NANOSECONDS); if (unit !== 'nanosecond' || increment !== 1) { + let norm = TimeDuration.normalize(hours, minutes, seconds, milliseconds, microseconds, nanoseconds); const largestUnit = ES.DefaultTemporalLargestUnit( years, months, @@ -574,49 +547,33 @@ export class Duration { microseconds, nanoseconds ); - ({ seconds, milliseconds, microseconds, nanoseconds } = ES.RoundDuration( - 0, - 0, - 0, - 0, - 0, - 0, - seconds, - milliseconds, - microseconds, - nanoseconds, - increment, - unit, - roundingMode - )); - ({ days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = ES.BalanceTimeDuration( - days, + ({ norm } = ES.RoundDuration(0, 0, 0, 0, norm, increment, unit, roundingMode)); + let deltaDays; + ({ + days: deltaDays, hours, minutes, seconds, milliseconds, microseconds, - nanoseconds, - largestUnit - )); + nanoseconds + } = ES.BalanceTimeDuration(norm, largestUnit)); + days += deltaDays; } - return ES.TemporalDurationToString( - years, - months, - weeks, - days, - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, - precision - ); + const normSeconds = TimeDuration.normalize(0, 0, seconds, milliseconds, microseconds, nanoseconds); + return ES.TemporalDurationToString(years, months, weeks, days, hours, minutes, normSeconds, precision); } toJSON() { if (!ES.IsTemporalDuration(this)) throw new TypeError('invalid receiver'); + const normSeconds = TimeDuration.normalize( + 0, + 0, + GetSlot(this, SECONDS), + GetSlot(this, MILLISECONDS), + GetSlot(this, MICROSECONDS), + GetSlot(this, NANOSECONDS) + ); return ES.TemporalDurationToString( GetSlot(this, YEARS), GetSlot(this, MONTHS), @@ -624,10 +581,7 @@ export class Duration { GetSlot(this, DAYS), GetSlot(this, HOURS), GetSlot(this, MINUTES), - GetSlot(this, SECONDS), - GetSlot(this, MILLISECONDS), - GetSlot(this, MICROSECONDS), - GetSlot(this, NANOSECONDS) + normSeconds ); } toLocaleString(locales = undefined, options = undefined) { @@ -636,6 +590,14 @@ export class Duration { return new Intl.DurationFormat(locales, options).format(this); } console.warn('Temporal.Duration.prototype.toLocaleString() requires Intl.DurationFormat.'); + const normSeconds = TimeDuration.normalize( + 0, + 0, + GetSlot(this, SECONDS), + GetSlot(this, MILLISECONDS), + GetSlot(this, MICROSECONDS), + GetSlot(this, NANOSECONDS) + ); return ES.TemporalDurationToString( GetSlot(this, YEARS), GetSlot(this, MONTHS), @@ -643,10 +605,7 @@ export class Duration { GetSlot(this, DAYS), GetSlot(this, HOURS), GetSlot(this, MINUTES), - GetSlot(this, SECONDS), - GetSlot(this, MILLISECONDS), - GetSlot(this, MICROSECONDS), - GetSlot(this, NANOSECONDS) + normSeconds ); } valueOf() { @@ -718,6 +677,8 @@ export class Duration { const instant = GetSlot(zonedRelativeTo, INSTANT); const precalculatedPlainDateTime = ES.GetPlainDateTimeFor(timeZoneRec, instant, calendarRec.receiver); + const norm1 = TimeDuration.normalize(h1, min1, s1, ms1, µs1, ns1); + const norm2 = TimeDuration.normalize(h2, min2, s2, ms2, µs2, ns2); const after1 = ES.AddZonedDateTime( instant, timeZoneRec, @@ -726,12 +687,7 @@ export class Duration { mon1, w1, d1, - h1, - min1, - s1, - ms1, - µs1, - ns1, + norm1, precalculatedPlainDateTime ); const after2 = ES.AddZonedDateTime( @@ -742,12 +698,7 @@ export class Duration { mon2, w2, d2, - h2, - min2, - s2, - ms2, - µs2, - ns2, + norm2, precalculatedPlainDateTime ); return ES.ComparisonResult(after1.minus(after2).toJSNumber()); @@ -758,11 +709,9 @@ export class Duration { ({ days: d1 } = ES.UnbalanceDateDurationRelative(y1, mon1, w1, d1, 'day', plainRelativeTo, calendarRec)); ({ days: d2 } = ES.UnbalanceDateDurationRelative(y2, mon2, w2, d2, 'day', plainRelativeTo, calendarRec)); } - h1 = bigInt(h1).add(bigInt(d1).multiply(24)); - h2 = bigInt(h2).add(bigInt(d2).multiply(24)); - ns1 = ES.TotalDurationNanoseconds(h1, min1, s1, ms1, µs1, ns1); - ns2 = ES.TotalDurationNanoseconds(h2, min2, s2, ms2, µs2, ns2); - return ES.ComparisonResult(ns1.minus(ns2).toJSNumber()); + const norm1 = TimeDuration.normalize(h1, min1, s1, ms1, µs1, ns1).add24HourDays(d1); + const norm2 = TimeDuration.normalize(h2, min2, s2, ms2, µs2, ns2).add24HourDays(d2); + return norm1.cmp(norm2); } } diff --git a/polyfill/lib/ecmascript.mjs b/polyfill/lib/ecmascript.mjs index be437b5d87..5415b059b9 100644 --- a/polyfill/lib/ecmascript.mjs +++ b/polyfill/lib/ecmascript.mjs @@ -59,8 +59,9 @@ import OwnPropertyKeys from 'es-abstract/helpers/OwnPropertyKeys.js'; import some from 'es-abstract/helpers/some.js'; import { GetIntrinsic } from './intrinsicclass.mjs'; -import { TruncatingDivModByPowerOf10 } from './math.mjs'; +import { FMAPowerOf10, TruncatingDivModByPowerOf10 } from './math.mjs'; import { CalendarMethodRecord, TimeZoneMethodRecord } from './methodrecord.mjs'; +import { TimeDuration } from './timeduration.mjs'; import { CreateSlots, GetSlot, @@ -683,6 +684,7 @@ export function ParseTemporalDurationString(isoString) { seconds += MathTrunc(excessNanoseconds / 1e9) % 60; minutes += MathTrunc(excessNanoseconds / 60e9); + RejectDuration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds); return { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds }; } @@ -2418,20 +2420,8 @@ export function DisambiguatePossibleInstants(possibleInstants, timeZoneRec, date const nanoseconds = offsetAfter - offsetBefore; switch (disambiguation) { case 'earlier': { - const earlierTime = AddTime( - hour, - minute, - second, - millisecond, - microsecond, - nanosecond, - 0, - 0, - 0, - 0, - 0, - -nanoseconds - ); + const norm = TimeDuration.normalize(0, 0, 0, 0, 0, -nanoseconds); + const earlierTime = AddTime(hour, minute, second, millisecond, microsecond, nanosecond, norm); const earlierDate = AddISODate(year, month, day, 0, 0, 0, earlierTime.deltaDays, 'constrain'); const earlierPlainDateTime = CreateTemporalDateTime( earlierDate.year, @@ -2449,7 +2439,8 @@ export function DisambiguatePossibleInstants(possibleInstants, timeZoneRec, date case 'compatible': // fall through because 'compatible' means 'later' for "spring forward" transitions case 'later': { - const laterTime = AddTime(hour, minute, second, millisecond, microsecond, nanosecond, 0, 0, 0, 0, 0, nanoseconds); + const norm = TimeDuration.normalize(0, 0, 0, 0, 0, nanoseconds); + const laterTime = AddTime(hour, minute, second, millisecond, microsecond, nanosecond, norm); const laterDate = AddISODate(year, month, day, 0, 0, 0, laterTime.deltaDays, 'constrain'); const laterPlainDateTime = CreateTemporalDateTime( laterDate.year, @@ -2546,25 +2537,8 @@ function formatAsDecimalNumber(num) { return bigInt(num).toString(); } -export function TemporalDurationToString( - years, - months, - weeks, - days, - hours, - minutes, - seconds, - ms, - µs, - ns, - precision = 'auto' -) { - const sign = DurationSign(years, months, weeks, days, hours, minutes, seconds, ms, µs, ns); - - let total = TotalDurationNanoseconds(0, 0, seconds, ms, µs, ns); - ({ quotient: total, remainder: ns } = total.divmod(1000)); - ({ quotient: total, remainder: µs } = total.divmod(1000)); - ({ quotient: seconds, remainder: ms } = total.divmod(1000)); +export function TemporalDurationToString(years, months, weeks, days, hours, minutes, normSeconds, precision = 'auto') { + const sign = DurationSign(years, months, weeks, days, hours, minutes, normSeconds.sec, 0, 0, normSeconds.subsec); let datePart = ''; if (years !== 0) datePart += `${formatAsDecimalNumber(MathAbs(years))}Y`; @@ -2577,17 +2551,12 @@ export function TemporalDurationToString( if (minutes !== 0) timePart += `${formatAsDecimalNumber(MathAbs(minutes))}M`; if ( - !seconds.isZero() || - !ms.isZero() || - !µs.isZero() || - !ns.isZero() || + !normSeconds.isZero() || (years === 0 && months === 0 && weeks === 0 && days === 0 && hours === 0 && minutes === 0) || precision !== 'auto' ) { - const secondsPart = formatAsDecimalNumber(seconds.abs()); - const subSecondNanoseconds = - MathAbs(ms.toJSNumber()) * 1e6 + MathAbs(µs.toJSNumber()) * 1e3 + MathAbs(ns.toJSNumber()); - const subSecondsPart = FormatFractionalSeconds(subSecondNanoseconds, precision); + const secondsPart = formatAsDecimalNumber(MathAbs(normSeconds.sec)); + const subSecondsPart = FormatFractionalSeconds(MathAbs(normSeconds.subsec), precision); timePart += `${secondsPart}${subSecondsPart}S`; } let result = `${sign < 0 ? '-' : ''}P${datePart}`; @@ -3235,24 +3204,15 @@ export function BalanceTime(hour, minute, second, millisecond, microsecond, nano }; } -export function TotalDurationNanoseconds(hours, minutes, seconds, milliseconds, microseconds, nanoseconds) { - minutes = bigInt(minutes).add(bigInt(hours).multiply(60)); - seconds = bigInt(seconds).add(minutes.multiply(60)); - milliseconds = bigInt(milliseconds).add(seconds.multiply(1000)); - microseconds = bigInt(microseconds).add(milliseconds.multiply(1000)); - return bigInt(nanoseconds).add(microseconds.multiply(1000)); -} - -export function NanosecondsToDays(nanoseconds, zonedRelativeTo, timeZoneRec, precalculatedPlainDateTime) { +export function NormalizedTimeDurationToDays(norm, zonedRelativeTo, timeZoneRec, precalculatedPlainDateTime) { // getOffsetNanosecondsFor and getPossibleInstantsFor must be looked up const TemporalInstant = GetIntrinsic('%Temporal.Instant%'); - const sign = MathSign(nanoseconds); - nanoseconds = bigInt(nanoseconds); - if (sign === 0) return { days: 0, nanoseconds: bigInt.zero, dayLengthNs: DAY_NANOS.toJSNumber() }; + const sign = norm.sign(); + if (sign === 0) return { days: 0, norm, dayLengthNs: DAY_NANOS }; const startNs = GetSlot(zonedRelativeTo, EPOCHNANOSECONDS); const start = GetSlot(zonedRelativeTo, INSTANT); - const endNs = startNs.add(nanoseconds); + const endNs = norm.addToEpochNs(startNs); const end = new TemporalInstant(endNs); const calendar = GetSlot(zonedRelativeTo, CALENDAR); @@ -3303,7 +3263,7 @@ export function NanosecondsToDays(nanoseconds, zonedRelativeTo, timeZoneRec, pre // may do disambiguation } } - nanoseconds = endNs.subtract(relativeResult.epochNs); + norm = TimeDuration.fromEpochNsDiff(endNs, relativeResult.epochNs); let isOverflow = false; let dayLengthNs; @@ -3317,10 +3277,11 @@ export function NanosecondsToDays(nanoseconds, zonedRelativeTo, timeZoneRec, pre sign ); - dayLengthNs = oneDayFarther.epochNs.subtract(relativeResult.epochNs).toJSNumber(); - isOverflow = nanoseconds.subtract(dayLengthNs).multiply(sign).geq(0); + dayLengthNs = TimeDuration.fromEpochNsDiff(oneDayFarther.epochNs, relativeResult.epochNs); + const oneDayLess = norm.subtract(dayLengthNs); + isOverflow = oneDayLess.sign() * sign >= 0; if (isOverflow) { - nanoseconds = nanoseconds.subtract(dayLengthNs); + norm = oneDayLess; relativeResult = oneDayFarther; days += sign; } @@ -3328,85 +3289,103 @@ export function NanosecondsToDays(nanoseconds, zonedRelativeTo, timeZoneRec, pre if (days !== 0 && MathSign(days) != sign) { throw new RangeError('Time zone or calendar converted nanoseconds into a number of days with the opposite sign'); } - if (!nanoseconds.isZero() && MathSign(nanoseconds.toJSNumber()) != sign) { - if (nanoseconds.lt(0) && sign === 1) { + if (!norm.isZero() && norm.sign() !== sign) { + if (norm.sign() === -1 && sign === 1) { throw new Error('assert not reached'); } throw new RangeError('Time zone or calendar ended up with a remainder of nanoseconds with the opposite sign'); } - if (nanoseconds.abs().geq(MathAbs(dayLengthNs))) { + if (norm.abs().cmp(dayLengthNs.abs()) >= 0) { throw new Error('assert not reached'); } - return { days, nanoseconds, dayLengthNs: MathAbs(dayLengthNs) }; + return { days, norm, dayLengthNs: dayLengthNs.abs().totalNs }; } -export function BalanceTimeDuration( - days, - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, - largestUnit -) { - hours = bigInt(hours).add(bigInt(days).multiply(24)); - nanoseconds = TotalDurationNanoseconds(hours, minutes, seconds, milliseconds, microseconds, nanoseconds); - - const sign = nanoseconds.lesser(0) ? -1 : 1; - nanoseconds = nanoseconds.abs(); - microseconds = milliseconds = seconds = minutes = hours = days = bigInt.zero; +export function BalanceTimeDuration(norm, largestUnit) { + const sign = norm.sign(); + let nanoseconds = norm.abs().subsec; + let microseconds = 0; + let milliseconds = 0; + let seconds = norm.abs().sec; + let minutes = 0; + let hours = 0; + let days = 0; switch (largestUnit) { case 'year': case 'month': case 'week': case 'day': - ({ quotient: microseconds, remainder: nanoseconds } = nanoseconds.divmod(1000)); - ({ quotient: milliseconds, remainder: microseconds } = microseconds.divmod(1000)); - ({ quotient: seconds, remainder: milliseconds } = milliseconds.divmod(1000)); - ({ quotient: minutes, remainder: seconds } = seconds.divmod(60)); - ({ quotient: hours, remainder: minutes } = minutes.divmod(60)); - ({ quotient: days, remainder: hours } = hours.divmod(24)); + microseconds = MathTrunc(nanoseconds / 1000); + nanoseconds %= 1000; + milliseconds = MathTrunc(microseconds / 1000); + microseconds %= 1000; + seconds += MathTrunc(milliseconds / 1000); + milliseconds %= 1000; + minutes = MathTrunc(seconds / 60); + seconds %= 60; + hours = MathTrunc(minutes / 60); + minutes %= 60; + days = MathTrunc(hours / 24); + hours %= 24; break; case 'hour': - ({ quotient: microseconds, remainder: nanoseconds } = nanoseconds.divmod(1000)); - ({ quotient: milliseconds, remainder: microseconds } = microseconds.divmod(1000)); - ({ quotient: seconds, remainder: milliseconds } = milliseconds.divmod(1000)); - ({ quotient: minutes, remainder: seconds } = seconds.divmod(60)); - ({ quotient: hours, remainder: minutes } = minutes.divmod(60)); + microseconds = MathTrunc(nanoseconds / 1000); + nanoseconds %= 1000; + milliseconds = MathTrunc(microseconds / 1000); + microseconds %= 1000; + seconds += MathTrunc(milliseconds / 1000); + milliseconds %= 1000; + minutes = MathTrunc(seconds / 60); + seconds %= 60; + hours = MathTrunc(minutes / 60); + minutes %= 60; break; case 'minute': - ({ quotient: microseconds, remainder: nanoseconds } = nanoseconds.divmod(1000)); - ({ quotient: milliseconds, remainder: microseconds } = microseconds.divmod(1000)); - ({ quotient: seconds, remainder: milliseconds } = milliseconds.divmod(1000)); - ({ quotient: minutes, remainder: seconds } = seconds.divmod(60)); + microseconds = MathTrunc(nanoseconds / 1000); + nanoseconds %= 1000; + milliseconds = MathTrunc(microseconds / 1000); + microseconds %= 1000; + seconds += MathTrunc(milliseconds / 1000); + milliseconds %= 1000; + minutes = MathTrunc(seconds / 60); + seconds %= 60; break; case 'second': - ({ quotient: microseconds, remainder: nanoseconds } = nanoseconds.divmod(1000)); - ({ quotient: milliseconds, remainder: microseconds } = microseconds.divmod(1000)); - ({ quotient: seconds, remainder: milliseconds } = milliseconds.divmod(1000)); + microseconds = MathTrunc(nanoseconds / 1000); + nanoseconds %= 1000; + milliseconds = MathTrunc(microseconds / 1000); + microseconds %= 1000; + seconds += MathTrunc(milliseconds / 1000); + milliseconds %= 1000; break; case 'millisecond': - ({ quotient: microseconds, remainder: nanoseconds } = nanoseconds.divmod(1000)); - ({ quotient: milliseconds, remainder: microseconds } = microseconds.divmod(1000)); + microseconds = MathTrunc(nanoseconds / 1000); + nanoseconds %= 1000; + milliseconds = FMAPowerOf10(seconds, 3, MathTrunc(microseconds / 1000)); + microseconds %= 1000; + seconds = 0; break; case 'microsecond': - ({ quotient: microseconds, remainder: nanoseconds } = nanoseconds.divmod(1000)); + microseconds = FMAPowerOf10(seconds, 6, MathTrunc(nanoseconds / 1000)); + nanoseconds %= 1000; + seconds = 0; break; case 'nanosecond': + nanoseconds = FMAPowerOf10(seconds, 9, nanoseconds); + seconds = 0; break; default: throw new Error('assert not reached'); } - days = days.toJSNumber() * sign; - hours = hours.toJSNumber() * sign; - minutes = minutes.toJSNumber() * sign; - seconds = seconds.toJSNumber() * sign; - milliseconds = milliseconds.toJSNumber() * sign; - microseconds = microseconds.toJSNumber() * sign; - nanoseconds = nanoseconds.toJSNumber() * sign; + days *= sign; + hours *= sign; + minutes *= sign; + seconds *= sign; + milliseconds *= sign; + microseconds *= sign; + nanoseconds *= sign; RejectDuration(0, 0, 0, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds); return { days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds }; @@ -3414,12 +3393,7 @@ export function BalanceTimeDuration( export function BalanceTimeDurationRelative( days, - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, + norm, largestUnit, zonedRelativeTo, timeZoneRec, @@ -3440,31 +3414,21 @@ export function BalanceTimeDurationRelative( ).epochNs; } - const endNs = AddInstant(intermediateNs, hours, minutes, seconds, milliseconds, microseconds, nanoseconds); - nanoseconds = endNs.subtract(startNs); - if (nanoseconds.isZero()) { + const endNs = AddInstant(intermediateNs, norm); + norm = TimeDuration.fromEpochNsDiff(endNs, startNs); + if (norm.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') { precalculatedPlainDateTime ??= GetPlainDateTimeFor(timeZoneRec, startInstant, 'iso8601'); - ({ days, nanoseconds } = NanosecondsToDays(nanoseconds, zonedRelativeTo, timeZoneRec, precalculatedPlainDateTime)); + ({ days, norm } = NormalizedTimeDurationToDays(norm, zonedRelativeTo, timeZoneRec, precalculatedPlainDateTime)); largestUnit = 'hour'; } else { days = 0; } - ({ hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = BalanceTimeDuration( - 0, - 0, - 0, - 0, - 0, - 0, - nanoseconds, - largestUnit - )); - + const { hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = BalanceTimeDuration(norm, largestUnit); return { days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds }; } @@ -3705,6 +3669,14 @@ export function RejectDuration(y, mon, w, d, h, min, s, ms, µs, ns) { } } +function CombineDateAndNormalizedTimeDuration(y, m, w, d, norm) { + const dateSign = DurationSign(y, m, w, d, 0, 0, 0, 0, 0, 0); + const timeSign = norm.sign(); + if (dateSign !== 0 && timeSign !== 0 && dateSign !== timeSign) { + throw new RangeError('mixed-sign values not allowed as duration fields'); + } +} + export function DifferenceISODate(y1, m1, d1, y2, m2, d2, largestUnit = 'days') { switch (largestUnit) { case 'year': @@ -3799,71 +3771,24 @@ export function DifferenceISODate(y1, m1, d1, y2, m2, d2, largestUnit = 'days') } export function DifferenceTime(h1, min1, s1, ms1, µs1, ns1, h2, min2, s2, ms2, µs2, ns2) { - let hours = h2 - h1; - let minutes = min2 - min1; - let seconds = s2 - s1; - let milliseconds = ms2 - ms1; - let microseconds = µs2 - µs1; - let nanoseconds = ns2 - ns1; - - const sign = DurationSign(0, 0, 0, 0, hours, minutes, seconds, milliseconds, microseconds, nanoseconds); - hours *= sign; - minutes *= sign; - seconds *= sign; - milliseconds *= sign; - microseconds *= sign; - nanoseconds *= sign; + const hours = h2 - h1; + const minutes = min2 - min1; + const seconds = s2 - s1; + const milliseconds = ms2 - ms1; + const microseconds = µs2 - µs1; + const nanoseconds = ns2 - ns1; + const norm = TimeDuration.normalize(hours, minutes, seconds, milliseconds, microseconds, nanoseconds); - let deltaDays = 0; - ({ - deltaDays, - hour: hours, - minute: minutes, - second: seconds, - millisecond: milliseconds, - microsecond: microseconds, - nanosecond: nanoseconds - } = BalanceTime(hours, minutes, seconds, milliseconds, microseconds, nanoseconds)); - - if (deltaDays != 0) throw new Error('assertion failure in DifferenceTime: _bt_.[[Days]] should be 0'); - hours *= sign; - minutes *= sign; - seconds *= sign; - milliseconds *= sign; - microseconds *= sign; - nanoseconds *= sign; + if (norm.abs().sec >= 86400) throw new Error('assertion failure in DifferenceTime: _bt_.[[Days]] should be 0'); - return { hours, minutes, seconds, milliseconds, microseconds, nanoseconds }; + return norm; } -export function DifferenceInstant(ns1, ns2, increment, smallestUnit, largestUnit, roundingMode) { - const diff = ns2.minus(ns1); - - let hours = 0; - let minutes = 0; - let nanoseconds = diff.mod(1e3).toJSNumber(); - let microseconds = diff.divide(1e3).mod(1e3).toJSNumber(); - let milliseconds = diff.divide(1e6).mod(1e3).toJSNumber(); - let seconds = diff.divide(1e9).toJSNumber(); +export function DifferenceInstant(ns1, ns2, increment, smallestUnit, roundingMode) { + const diff = TimeDuration.fromEpochNsDiff(ns2, ns1); + if (smallestUnit === 'nanosecond' && increment === 1) return diff; - if (smallestUnit !== 'nanosecond' || increment !== 1) { - ({ hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = RoundDuration( - 0, - 0, - 0, - 0, - 0, - 0, - seconds, - milliseconds, - microseconds, - nanoseconds, - increment, - smallestUnit, - roundingMode - )); - } - return BalanceTimeDuration(0, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, largestUnit); + return RoundDuration(0, 0, 0, 0, diff, increment, smallestUnit, roundingMode).norm; } export function DifferenceDate(calendarRec, plainDate1, plainDate2, options) { @@ -3906,35 +3831,13 @@ export function DifferenceISODateTime( ) { // dateUntil must be looked up if date parts are not identical and largestUnit // is greater than 'day' - let { hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = DifferenceTime( - h1, - min1, - s1, - ms1, - µs1, - ns1, - h2, - min2, - s2, - ms2, - µs2, - ns2 - ); + let timeDuration = DifferenceTime(h1, min1, s1, ms1, µs1, ns1, h2, min2, s2, ms2, µs2, ns2); - const timeSign = DurationSign(0, 0, 0, 0, hours, minutes, seconds, milliseconds, microseconds, nanoseconds); + const timeSign = timeDuration.sign(); 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, - largestUnit - )); + timeDuration = timeDuration.add24HourDays(-timeSign); } const date1 = CreateTemporalDate(y1, mon1, d1, calendarRec.receiver); @@ -3946,19 +3849,8 @@ export function DifferenceISODateTime( const years = GetSlot(untilResult, YEARS); const months = GetSlot(untilResult, MONTHS); const weeks = GetSlot(untilResult, WEEKS); - let days = GetSlot(untilResult, DAYS); - // Signs of date part and time part may not agree; balance them together - ({ days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = BalanceTimeDuration( - days, - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, - largestUnit - )); - return { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds }; + const days = GetSlot(untilResult, DAYS); + return { years, months, weeks, days, norm: timeDuration }; } export function DifferenceZonedDateTime( @@ -3982,12 +3874,7 @@ export function DifferenceZonedDateTime( months: 0, weeks: 0, days: 0, - hours: 0, - minutes: 0, - seconds: 0, - milliseconds: 0, - microseconds: 0, - nanoseconds: 0 + norm: TimeDuration.ZERO }; } @@ -3998,7 +3885,7 @@ export function DifferenceZonedDateTime( const dtStart = precalculatedDtStart ?? GetPlainDateTimeFor(timeZoneRec, start, calendarRec.receiver); const dtEnd = GetPlainDateTimeFor(timeZoneRec, end, calendarRec.receiver); - let { years, months, weeks, days } = DifferenceISODateTime( + let { years, months, weeks } = DifferenceISODateTime( GetSlot(dtStart, ISO_YEAR), GetSlot(dtStart, ISO_MONTH), GetSlot(dtStart, ISO_DAY), @@ -4029,31 +3916,18 @@ export function DifferenceZonedDateTime( months, weeks, 0, - 0, - 0, - 0, - 0, - 0, - 0, + TimeDuration.ZERO, dtStart ); // may disambiguate - let timeRemainderNs = ns2.subtract(intermediateNs); + + let norm = TimeDuration.fromEpochNsDiff(ns2, intermediateNs); const intermediate = CreateTemporalZonedDateTime(intermediateNs, timeZoneRec.receiver, calendarRec.receiver); - ({ nanoseconds: timeRemainderNs, days } = NanosecondsToDays(timeRemainderNs, intermediate, timeZoneRec)); + let days; + ({ norm, days } = NormalizedTimeDurationToDays(norm, intermediate, timeZoneRec)); - // Finally, merge the date and time durations and return the merged result. - let { hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = BalanceTimeDuration( - 0, - 0, - 0, - 0, - 0, - 0, - timeRemainderNs, - 'hour' - ); - return { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds }; + CombineDateAndNormalizedTimeDuration(years, months, weeks, days, norm); + return { years, months, weeks, days, norm }; } export function GetDifferenceSettings(op, options, group, disallowed, fallbackSmallest, smallestLargestDefaultUnit) { @@ -4110,14 +3984,17 @@ export function DifferenceTemporalInstant(operation, instant, other, options) { const onens = GetSlot(instant, EPOCHNANOSECONDS); const twons = GetSlot(other, EPOCHNANOSECONDS); - let { hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = DifferenceInstant( + const norm = DifferenceInstant( onens, twons, settings.roundingIncrement, settings.smallestUnit, - settings.largestUnit, settings.roundingMode ); + const { hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = BalanceTimeDuration( + norm, + settings.largestUnit + ); const Duration = GetIntrinsic('%Temporal.Duration%'); return new Duration( 0, @@ -4168,12 +4045,7 @@ export function DifferenceTemporalPlainDate(operation, plainDate, other, options months, weeks, days, - 0, - 0, - 0, - 0, - 0, - 0, + TimeDuration.ZERO, settings.roundingIncrement, settings.smallestUnit, settings.roundingMode, @@ -4224,45 +4096,40 @@ export function DifferenceTemporalPlainDateTime(operation, plainDateTime, other, const calendarRec = new CalendarMethodRecord(calendar, ['dateAdd', 'dateUntil']); - let { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = - DifferenceISODateTime( - GetSlot(plainDateTime, ISO_YEAR), - GetSlot(plainDateTime, ISO_MONTH), - GetSlot(plainDateTime, ISO_DAY), - GetSlot(plainDateTime, ISO_HOUR), - GetSlot(plainDateTime, ISO_MINUTE), - GetSlot(plainDateTime, ISO_SECOND), - GetSlot(plainDateTime, ISO_MILLISECOND), - GetSlot(plainDateTime, ISO_MICROSECOND), - GetSlot(plainDateTime, 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), - calendarRec, - settings.largestUnit, - resolvedOptions - ); + let { years, months, weeks, days, norm } = DifferenceISODateTime( + GetSlot(plainDateTime, ISO_YEAR), + GetSlot(plainDateTime, ISO_MONTH), + GetSlot(plainDateTime, ISO_DAY), + GetSlot(plainDateTime, ISO_HOUR), + GetSlot(plainDateTime, ISO_MINUTE), + GetSlot(plainDateTime, ISO_SECOND), + GetSlot(plainDateTime, ISO_MILLISECOND), + GetSlot(plainDateTime, ISO_MICROSECOND), + GetSlot(plainDateTime, 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), + calendarRec, + settings.largestUnit, + resolvedOptions + ); + let hours, minutes, seconds, milliseconds, microseconds, nanoseconds; const roundingIsNoop = settings.smallestUnit === 'nanosecond' && settings.roundingIncrement === 1; if (!roundingIsNoop) { const relativeTo = TemporalDateTimeToDate(plainDateTime); - ({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = RoundDuration( + ({ years, months, weeks, days, norm } = RoundDuration( years, months, weeks, days, - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, + norm, settings.roundingIncrement, settings.smallestUnit, settings.roundingMode, @@ -4270,13 +4137,7 @@ export function DifferenceTemporalPlainDateTime(operation, plainDateTime, other, calendarRec )); ({ days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = BalanceTimeDuration( - days, - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, + norm.add24HourDays(days), settings.largestUnit )); ({ years, months, weeks, days } = BalanceDateDurationRelative( @@ -4289,6 +4150,11 @@ export function DifferenceTemporalPlainDateTime(operation, plainDateTime, other, relativeTo, calendarRec )); + } else { + ({ days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = BalanceTimeDuration( + norm.add24HourDays(days), + settings.largestUnit + )); } return new Duration( @@ -4312,7 +4178,7 @@ export function DifferenceTemporalPlainTime(operation, plainTime, other, options const resolvedOptions = SnapshotOwnProperties(GetOptionsObject(options), null); const settings = GetDifferenceSettings(operation, resolvedOptions, 'time', [], 'nanosecond', 'hour'); - let { hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = DifferenceTime( + let norm = DifferenceTime( GetSlot(plainTime, ISO_HOUR), GetSlot(plainTime, ISO_MINUTE), GetSlot(plainTime, ISO_SECOND), @@ -4327,32 +4193,21 @@ export function DifferenceTemporalPlainTime(operation, plainTime, other, options GetSlot(other, ISO_NANOSECOND) ); if (settings.smallestUnit !== 'nanosecond' || settings.roundingIncrement !== 1) { - ({ hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = RoundDuration( + ({ norm } = RoundDuration( 0, 0, 0, 0, - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, + norm, settings.roundingIncrement, settings.smallestUnit, settings.roundingMode )); } - ({ hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = BalanceTimeDuration( - 0, - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, + const { hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = BalanceTimeDuration( + norm, settings.largestUnit - )); + ); const Duration = GetIntrinsic('%Temporal.Duration%'); return new Duration( 0, @@ -4407,12 +4262,7 @@ export function DifferenceTemporalPlainYearMonth(operation, yearMonth, other, op months, 0, 0, - 0, - 0, - 0, - 0, - 0, - 0, + TimeDuration.ZERO, settings.roundingIncrement, settings.smallestUnit, settings.roundingMode, @@ -4461,13 +4311,10 @@ export function DifferenceTemporalZonedDateTime(operation, zonedDateTime, other, months = 0; weeks = 0; days = 0; - ({ hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = DifferenceInstant( - ns1, - ns2, - settings.roundingIncrement, - settings.smallestUnit, - settings.largestUnit, - settings.roundingMode + const norm = DifferenceInstant(ns1, ns2, settings.roundingIncrement, settings.smallestUnit, settings.roundingMode); + ({ hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = BalanceTimeDuration( + norm, + settings.largestUnit )); } else { const timeZone = GetSlot(zonedDateTime, TIME_ZONE); @@ -4494,30 +4341,25 @@ export function DifferenceTemporalZonedDateTime(operation, zonedDateTime, other, const plainRelativeTo = TemporalDateTimeToDate(precalculatedPlainDateTime); resolvedOptions.largestUnit = settings.largestUnit; - ({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = - DifferenceZonedDateTime( - ns1, - ns2, - timeZoneRec, - calendarRec, - settings.largestUnit, - resolvedOptions, - precalculatedPlainDateTime - )); + let norm; + ({ years, months, weeks, days, norm } = DifferenceZonedDateTime( + ns1, + ns2, + timeZoneRec, + calendarRec, + settings.largestUnit, + resolvedOptions, + precalculatedPlainDateTime + )); const roundingIsNoop = settings.smallestUnit === 'nanosecond' && settings.roundingIncrement === 1; if (!roundingIsNoop) { - ({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = RoundDuration( + ({ years, months, weeks, days, norm } = RoundDuration( years, months, weeks, days, - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, + norm, settings.roundingIncrement, settings.smallestUnit, settings.roundingMode, @@ -4527,26 +4369,23 @@ export function DifferenceTemporalZonedDateTime(operation, zonedDateTime, other, timeZoneRec, precalculatedPlainDateTime )); - ({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = - AdjustRoundedDurationDays( - years, - months, - weeks, - days, - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, - settings.roundingIncrement, - settings.smallestUnit, - settings.roundingMode, - zonedDateTime, - calendarRec, - timeZoneRec, - precalculatedPlainDateTime - )); + let deltaDays; + ({ days: deltaDays, norm } = NormalizedTimeDurationToDays(norm, zonedDateTime, timeZoneRec)); + days += deltaDays; + ({ years, months, weeks, days, norm } = AdjustRoundedDurationDays( + years, + months, + weeks, + days, + norm, + settings.roundingIncrement, + settings.smallestUnit, + settings.roundingMode, + zonedDateTime, + calendarRec, + timeZoneRec, + precalculatedPlainDateTime + )); // BalanceTimeDuration already performed in AdjustRoundedDurationDays ({ years, months, weeks, days } = BalanceDateDurationRelative( years, @@ -4558,7 +4397,9 @@ export function DifferenceTemporalZonedDateTime(operation, zonedDateTime, other, plainRelativeTo, calendarRec )); + CombineDateAndNormalizedTimeDuration(years, months, weeks, days, norm); } + ({ hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = BalanceTimeDuration(norm, 'hour')); } return new Duration( @@ -4600,50 +4441,23 @@ export function AddDate(calendarRec, plainDate, duration, options = undefined) { let month = GetSlot(plainDate, ISO_MONTH); let day = GetSlot(plainDate, ISO_DAY); const overflow = ToTemporalOverflow(options); - const { days } = BalanceTimeDuration( - GetSlot(duration, DAYS), + const norm = TimeDuration.normalize( GetSlot(duration, HOURS), GetSlot(duration, MINUTES), GetSlot(duration, SECONDS), GetSlot(duration, MILLISECONDS), GetSlot(duration, MICROSECONDS), - GetSlot(duration, NANOSECONDS), - 'day' + GetSlot(duration, NANOSECONDS) ); + const days = GetSlot(duration, DAYS) + BalanceTimeDuration(norm, 'day').days; ({ year, month, day } = AddISODate(year, month, day, 0, 0, 0, days, overflow)); return CreateTemporalDate(year, month, day, calendarRec.receiver); } -export function AddTime( - hour, - minute, - second, - millisecond, - microsecond, - nanosecond, - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds -) { - hour += hours; - minute += minutes; - second += seconds; - millisecond += milliseconds; - microsecond += microseconds; - nanosecond += nanoseconds; - let deltaDays = 0; - ({ deltaDays, hour, minute, second, millisecond, microsecond, nanosecond } = BalanceTime( - hour, - minute, - second, - millisecond, - microsecond, - nanosecond - )); - return { deltaDays, hour, minute, second, millisecond, microsecond, nanosecond }; +export function AddTime(hour, minute, second, millisecond, microsecond, nanosecond, norm) { + second += norm.sec; + nanosecond += norm.subsec; + return BalanceTime(hour, minute, second, millisecond, microsecond, nanosecond); } export function AddDuration( @@ -4686,14 +4500,10 @@ export function AddDuration( throw new RangeError('relativeTo is required for years, months, or weeks arithmetic'); } years = months = weeks = 0; + const norm1 = TimeDuration.normalize(h1, min1, s1, ms1, µs1, ns1); + const norm2 = TimeDuration.normalize(h2, min2, s2, ms2, µs2, ns2); ({ days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = BalanceTimeDuration( - d1 + d2, - bigInt(h1).add(h2), - bigInt(min1).add(min2), - bigInt(s1).add(s2), - bigInt(ms1).add(ms2), - bigInt(µs1).add(µs2), - bigInt(ns1).add(ns2), + norm1.add(norm2).add24HourDays(d1 + d2), largestUnit )); } else if (plainRelativeTo) { @@ -4713,14 +4523,10 @@ export function AddDuration( weeks = GetSlot(untilResult, WEEKS); days = GetSlot(untilResult, DAYS); // Signs of date part and time part may not agree; balance them together + const norm1 = TimeDuration.normalize(h1, min1, s1, ms1, µs1, ns1); + const norm2 = TimeDuration.normalize(h2, min2, s2, ms2, µs2, ns2); ({ days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = BalanceTimeDuration( - days, - bigInt(h1).add(h2), - bigInt(min1).add(min2), - bigInt(s1).add(s2), - bigInt(ms1).add(ms2), - bigInt(µs1).add(µs2), - bigInt(ns1).add(ns2), + norm1.add(norm2).add24HourDays(days), largestUnit )); } else { @@ -4732,6 +4538,8 @@ export function AddDuration( if (largestUnit === 'year' || largestUnit === 'month' || largestUnit === 'week' || largestUnit === 'day') { startDateTime ??= GetPlainDateTimeFor(timeZoneRec, startInstant, calendar); } + const norm1 = TimeDuration.normalize(h1, min1, s1, ms1, µs1, ns1); + const norm2 = TimeDuration.normalize(h2, min2, s2, ms2, µs2, ns2); const intermediateNs = AddZonedDateTime( startInstant, timeZoneRec, @@ -4740,12 +4548,7 @@ export function AddDuration( mon1, w1, d1, - h1, - min1, - s1, - ms1, - µs1, - ns1, + norm1, startDateTime ); const endNs = AddZonedDateTime( @@ -4756,12 +4559,7 @@ export function AddDuration( mon2, w2, d2, - h2, - min2, - s2, - ms2, - µs2, - ns2 + norm2 ); if (largestUnit !== 'year' && largestUnit !== 'month' && largestUnit !== 'week' && largestUnit !== 'day') { // The user is only asking for a time difference, so return difference of instants. @@ -4769,42 +4567,28 @@ export function AddDuration( months = 0; weeks = 0; days = 0; - ({ hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = DifferenceInstant( + const norm = TimeDuration.fromEpochNsDiff(endNs, GetSlot(zonedRelativeTo, EPOCHNANOSECONDS)); + ({ hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = BalanceTimeDuration(norm, largestUnit)); + } else { + let norm; + ({ years, months, weeks, days, norm } = DifferenceZonedDateTime( GetSlot(zonedRelativeTo, EPOCHNANOSECONDS), endNs, - 1, - 'nanosecond', + timeZoneRec, + calendarRec, largestUnit, - 'halfExpand' + ObjectCreate(null), + startDateTime )); - } else { - ({ years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = - DifferenceZonedDateTime( - GetSlot(zonedRelativeTo, EPOCHNANOSECONDS), - endNs, - timeZoneRec, - calendarRec, - largestUnit, - ObjectCreate(null), - startDateTime - )); + ({ hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = BalanceTimeDuration(norm, 'hour')); } } - RejectDuration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds); return { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds }; } -export function AddInstant(epochNanoseconds, h, min, s, ms, µs, ns) { - let sum = bigInt.zero; - sum = sum.plus(bigInt(ns)); - sum = sum.plus(bigInt(µs).multiply(1e3)); - sum = sum.plus(bigInt(ms).multiply(1e6)); - sum = sum.plus(bigInt(s).multiply(1e9)); - sum = sum.plus(bigInt(min).multiply(60 * 1e9)); - sum = sum.plus(bigInt(h).multiply(60 * 60 * 1e9)); - - const result = bigInt(epochNanoseconds).plus(sum); +export function AddInstant(epochNanoseconds, norm) { + const result = norm.addToEpochNs(epochNanoseconds); ValidateEpochNanoseconds(result); return result; } @@ -4824,12 +4608,7 @@ export function AddDateTime( months, weeks, days, - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, + norm, options ) { // dateAdd must be looked up if years, months, weeks != 0 @@ -4842,12 +4621,7 @@ export function AddDateTime( millisecond, microsecond, nanosecond, - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds + norm )); days += deltaDays; @@ -4878,12 +4652,7 @@ export function AddZonedDateTime( months, weeks, days, - h, - min, - s, - ms, - µs, - ns, + norm, precalculatedPlainDateTime = undefined, options = undefined ) { @@ -4905,14 +4674,14 @@ export function AddZonedDateTime( // BTW, this behavior is similar in spirit to offset: 'prefer' in `with`. const TemporalDuration = GetIntrinsic('%Temporal.Duration%'); if (DurationSign(years, months, weeks, days, 0, 0, 0, 0, 0, 0) === 0) { - return AddInstant(GetSlot(instant, EPOCHNANOSECONDS), h, min, s, ms, µs, ns); + return AddInstant(GetSlot(instant, EPOCHNANOSECONDS), norm); } const dt = precalculatedPlainDateTime ?? GetPlainDateTimeFor(timeZoneRec, instant, calendarRec.receiver); if (DurationSign(years, months, weeks, 0, 0, 0, 0, 0, 0, 0) === 0) { const overflow = ToTemporalOverflow(options); const intermediate = AddDaysToZonedDateTime(instant, dt, timeZoneRec, calendarRec.receiver, days, overflow).epochNs; - return AddInstant(intermediate, h, min, s, ms, µs, ns); + return AddInstant(intermediate, norm); } // RFC 5545 requires the date portion to be added in calendar days and the @@ -4941,7 +4710,7 @@ export function AddZonedDateTime( // Note that 'compatible' is used below because this disambiguation behavior // is required by RFC 5545. const instantIntermediate = GetInstantFor(timeZoneRec, dtIntermediate, 'compatible'); - return AddInstant(GetSlot(instantIntermediate, EPOCHNANOSECONDS), h, min, s, ms, µs, ns); + return AddInstant(GetSlot(instantIntermediate, EPOCHNANOSECONDS), norm); } export function AddDaysToZonedDateTime(instant, dateTime, timeZoneRec, calendar, days, overflow = 'constrain') { @@ -5037,8 +4806,7 @@ export function AddDurationToOrSubtractDurationFromInstant(operation, instant, d 'weeks', 'days' ]); - const ns = AddInstant( - GetSlot(instant, EPOCHNANOSECONDS), + const norm = TimeDuration.normalize( sign * hours, sign * minutes, sign * seconds, @@ -5046,6 +4814,7 @@ export function AddDurationToOrSubtractDurationFromInstant(operation, instant, d sign * microseconds, sign * nanoseconds ); + const ns = AddInstant(GetSlot(instant, EPOCHNANOSECONDS), norm); const Instant = GetIntrinsic('%Temporal.Instant%'); return new Instant(ns); } @@ -5058,6 +4827,14 @@ export function AddDurationToOrSubtractDurationFromPlainDateTime(operation, date const calendarRec = new CalendarMethodRecord(GetSlot(dateTime, CALENDAR), ['dateAdd']); + const norm = TimeDuration.normalize( + sign * hours, + sign * minutes, + sign * seconds, + sign * milliseconds, + sign * microseconds, + sign * nanoseconds + ); const { year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = AddDateTime( GetSlot(dateTime, ISO_YEAR), GetSlot(dateTime, ISO_MONTH), @@ -5073,12 +4850,7 @@ export function AddDurationToOrSubtractDurationFromPlainDateTime(operation, date sign * months, sign * weeks, sign * days, - sign * hours, - sign * minutes, - sign * seconds, - sign * milliseconds, - sign * microseconds, - sign * nanoseconds, + norm, options ); return CreateTemporalDateTime( @@ -5098,6 +4870,14 @@ export function AddDurationToOrSubtractDurationFromPlainDateTime(operation, date export function AddDurationToOrSubtractDurationFromPlainTime(operation, temporalTime, durationLike) { const sign = operation === 'subtract' ? -1 : 1; const { hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = ToTemporalDurationRecord(durationLike); + const norm = TimeDuration.normalize( + sign * hours, + sign * minutes, + sign * seconds, + sign * milliseconds, + sign * microseconds, + sign * nanoseconds + ); let { hour, minute, second, millisecond, microsecond, nanosecond } = AddTime( GetSlot(temporalTime, ISO_HOUR), GetSlot(temporalTime, ISO_MINUTE), @@ -5105,12 +4885,7 @@ export function AddDurationToOrSubtractDurationFromPlainTime(operation, temporal GetSlot(temporalTime, ISO_MILLISECOND), GetSlot(temporalTime, ISO_MICROSECOND), GetSlot(temporalTime, ISO_NANOSECOND), - sign * hours, - sign * minutes, - sign * seconds, - sign * milliseconds, - sign * microseconds, - sign * nanoseconds + norm ); ({ hour, minute, second, millisecond, microsecond, nanosecond } = RegulateTime( hour, @@ -5142,8 +4917,9 @@ export function AddDurationToOrSubtractDurationFromPlainYearMonth(operation, yea }; } let { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = duration; - ({ days } = BalanceTimeDuration(days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, 'day')); options = GetOptionsObject(options); + const norm = TimeDuration.normalize(hours, minutes, seconds, milliseconds, microseconds, nanoseconds); + days += BalanceTimeDuration(norm, 'day').days; const sign = DurationSign(years, months, weeks, days, 0, 0, 0, 0, 0, 0); const calendarRec = new CalendarMethodRecord(GetSlot(yearMonth, CALENDAR), [ @@ -5200,6 +4976,14 @@ export function AddDurationToOrSubtractDurationFromZonedDateTime(operation, zone 'getPossibleInstantsFor' ]); const calendarRec = new CalendarMethodRecord(GetSlot(zonedDateTime, CALENDAR), ['dateAdd']); + const norm = TimeDuration.normalize( + sign * hours, + sign * minutes, + sign * seconds, + sign * milliseconds, + sign * microseconds, + sign * nanoseconds + ); const epochNanoseconds = AddZonedDateTime( GetSlot(zonedDateTime, INSTANT), timeZoneRec, @@ -5208,12 +4992,7 @@ export function AddDurationToOrSubtractDurationFromZonedDateTime(operation, zone sign * months, sign * weeks, sign * days, - sign * hours, - sign * minutes, - sign * seconds, - sign * milliseconds, - sign * microseconds, - sign * nanoseconds, + norm, undefined, options ); @@ -5395,12 +5174,7 @@ export function MoveRelativeZonedDateTime( months, weeks, days, - 0, - 0, - 0, - 0, - 0, - 0, + TimeDuration.ZERO, precalculatedPlainDateTime ); return CreateTemporalZonedDateTime(intermediateNs, timeZoneRec.receiver, calendarRec.receiver); @@ -5411,12 +5185,7 @@ export function AdjustRoundedDurationDays( months, weeks, days, - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, + norm, increment, unit, roundingMode, @@ -5434,7 +5203,7 @@ export function AdjustRoundedDurationDays( unit === 'day' || (unit === 'nanosecond' && increment === 1) ) { - return { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds }; + return { years, months, weeks, days, norm }; } // There's one more round of rounding possible: if relativeTo is a @@ -5445,8 +5214,7 @@ export function AdjustRoundedDurationDays( // duration, there's no way for another full day to come from the next // round of rounding. And if it were possible (e.g. contrived calendar // with 30-minute-long "days") then it'd risk an infinite loop. - let timeRemainderNs = TotalDurationNanoseconds(hours, minutes, seconds, milliseconds, microseconds, nanoseconds); - const direction = MathSign(timeRemainderNs.toJSNumber()); + const direction = norm.sign(); const calendar = GetSlot(zonedRelativeTo, CALENDAR); // requires dateAdd if years...weeks != 0 @@ -5458,22 +5226,17 @@ export function AdjustRoundedDurationDays( months, weeks, days, - 0, - 0, - 0, - 0, - 0, - 0, + TimeDuration.ZERO, precalculatedPlainDateTime ); const TemporalInstant = GetIntrinsic('%Temporal.Instant%'); const dayStartInstant = new TemporalInstant(dayStart); const dayStartDateTime = GetPlainDateTimeFor(timeZoneRec, dayStartInstant, calendar); const dayEnd = AddDaysToZonedDateTime(dayStartInstant, dayStartDateTime, timeZoneRec, calendar, direction).epochNs; - const dayLengthNs = dayEnd.subtract(dayStart); + const dayLength = TimeDuration.fromEpochNsDiff(dayEnd, dayStart); - const oneDayLess = timeRemainderNs.subtract(dayLengthNs); - if (oneDayLess.multiply(direction).geq(0)) { + const oneDayLess = norm.subtract(dayLength); + if (oneDayLess.sign() * direction >= 0) { // requires dateAdd and dateUntil if years...weeks != 0 ({ years, months, weeks, days } = AddDuration( years, @@ -5502,34 +5265,10 @@ export function AdjustRoundedDurationDays( timeZoneRec, precalculatedPlainDateTime )); - // no calendar calls - ({ hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = RoundDuration( - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - oneDayLess.toJSNumber(), - increment, - unit, - roundingMode - )); - ({ hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = BalanceTimeDuration( - 0, - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, - 'hour' - )); + ({ norm } = RoundDuration(0, 0, 0, 0, oneDayLess, increment, unit, roundingMode)); } - return { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds }; + CombineDateAndNormalizedTimeDuration(years, months, weeks, days, norm); + return { years, months, weeks, days, norm }; } export function RoundDuration( @@ -5537,12 +5276,7 @@ export function RoundDuration( months, weeks, days, - hours, - minutes, - seconds, - milliseconds, - microseconds, - nanoseconds, + norm, increment, unit, roundingMode, @@ -5563,7 +5297,6 @@ export function RoundDuration( // If rounding relative to a ZonedDateTime, then some days may not be 24h. let dayLengthNs; if (unit === 'year' || unit === 'month' || unit === 'week' || unit === 'day') { - nanoseconds = TotalDurationNanoseconds(hours, minutes, seconds, milliseconds, microseconds, nanoseconds); let deltaDays; if (zonedRelativeTo) { const intermediate = MoveRelativeZonedDateTime( @@ -5576,14 +5309,12 @@ export function RoundDuration( days, precalculatedPlainDateTime ); - ({ days: deltaDays, nanoseconds, dayLengthNs } = NanosecondsToDays(nanoseconds, intermediate, timeZoneRec)); + ({ days: deltaDays, norm, dayLengthNs } = NormalizedTimeDurationToDays(norm, intermediate, timeZoneRec)); } else { - ({ quotient: deltaDays, remainder: nanoseconds } = nanoseconds.divmod(DAY_NANOS)); - deltaDays = deltaDays.toJSNumber(); - dayLengthNs = DAY_NANOS.toJSNumber(); + ({ quotient: deltaDays, remainder: norm } = norm.divmod(DAY_NANOS)); + dayLengthNs = DAY_NANOS; } days += deltaDays; - hours = minutes = seconds = milliseconds = microseconds = 0; } let total; @@ -5634,12 +5365,13 @@ export function RoundDuration( oneYearDays = MathAbs(oneYearDays); if (oneYearDays === 0) throw new RangeError('custom calendar reported that a year is 0 days long'); const divisor = bigInt(oneYearDays).multiply(dayLengthNs); - nanoseconds = divisor.multiply(years).plus(bigInt(days).multiply(dayLengthNs)).plus(nanoseconds); + const nanoseconds = divisor.multiply(years).plus(bigInt(days).multiply(dayLengthNs)).plus(norm.totalNs); const rounded = RoundNumberToIncrement(nanoseconds, divisor.multiply(increment).toJSNumber(), roundingMode); const { quotient, remainder } = nanoseconds.divmod(divisor); total = quotient.toJSNumber() + remainder.toJSNumber() / divisor; years = rounded.divide(divisor).toJSNumber(); - nanoseconds = months = weeks = days = 0; + months = weeks = days = 0; + norm = TimeDuration.ZERO; break; } case 'month': { @@ -5682,12 +5414,13 @@ export function RoundDuration( oneMonthDays = MathAbs(oneMonthDays); if (oneMonthDays === 0) throw new RangeError('custom calendar reported that a month is 0 days long'); const divisor = bigInt(oneMonthDays).multiply(dayLengthNs); - nanoseconds = divisor.multiply(months).plus(bigInt(days).multiply(dayLengthNs)).plus(nanoseconds); + const nanoseconds = divisor.multiply(months).plus(bigInt(days).multiply(dayLengthNs)).plus(norm.totalNs); const rounded = RoundNumberToIncrement(nanoseconds, divisor.multiply(increment), roundingMode); const { quotient, remainder } = nanoseconds.divmod(divisor); total = quotient.toJSNumber() + remainder.toJSNumber() / divisor; months = rounded.divide(divisor).toJSNumber(); - nanoseconds = weeks = days = 0; + weeks = days = 0; + norm = TimeDuration.ZERO; break; } case 'week': { @@ -5720,97 +5453,63 @@ export function RoundDuration( oneWeekDays = MathAbs(oneWeekDays); if (oneWeekDays === 0) throw new RangeError('custom calendar reported that a week is 0 days long'); const divisor = bigInt(oneWeekDays).multiply(dayLengthNs); - nanoseconds = divisor.multiply(weeks).plus(bigInt(days).multiply(dayLengthNs)).plus(nanoseconds); + const nanoseconds = divisor.multiply(weeks).plus(bigInt(days).multiply(dayLengthNs)).plus(norm.totalNs); const rounded = RoundNumberToIncrement(nanoseconds, divisor.multiply(increment), roundingMode); const { quotient, remainder } = nanoseconds.divmod(divisor); total = quotient.toJSNumber() + remainder.toJSNumber() / divisor; weeks = rounded.divide(divisor).toJSNumber(); - nanoseconds = days = 0; + days = 0; + norm = TimeDuration.ZERO; break; } case 'day': { const divisor = bigInt(dayLengthNs); - nanoseconds = divisor.multiply(days).plus(nanoseconds); + const nanoseconds = divisor.multiply(days).plus(norm.totalNs); const rounded = RoundNumberToIncrement(nanoseconds, divisor.multiply(increment), roundingMode); const { quotient, remainder } = nanoseconds.divmod(divisor); total = quotient.toJSNumber() + remainder.toJSNumber() / divisor; days = rounded.divide(divisor).toJSNumber(); - nanoseconds = 0; + norm = TimeDuration.ZERO; break; } case 'hour': { const divisor = 3600e9; - nanoseconds = bigInt(hours) - .multiply(3600e9) - .plus(bigInt(minutes).multiply(60e9)) - .plus(bigInt(seconds).multiply(1e9)) - .plus(bigInt(milliseconds).multiply(1e6)) - .plus(bigInt(microseconds).multiply(1e3)) - .plus(nanoseconds); - const { quotient, remainder } = nanoseconds.divmod(divisor); - total = quotient.toJSNumber() + remainder.toJSNumber() / divisor; - const rounded = RoundNumberToIncrement(nanoseconds, divisor * increment, roundingMode); - hours = rounded.divide(divisor).toJSNumber(); - minutes = seconds = milliseconds = microseconds = nanoseconds = 0; + total = norm.fdiv(divisor); + norm = norm.round(divisor * increment, roundingMode); break; } case 'minute': { const divisor = 60e9; - nanoseconds = bigInt(minutes) - .multiply(60e9) - .plus(bigInt(seconds).multiply(1e9)) - .plus(bigInt(milliseconds).multiply(1e6)) - .plus(bigInt(microseconds).multiply(1e3)) - .plus(nanoseconds); - const { quotient, remainder } = nanoseconds.divmod(divisor); - total = quotient.toJSNumber() + remainder.toJSNumber() / divisor; - const rounded = RoundNumberToIncrement(nanoseconds, divisor * increment, roundingMode); - minutes = rounded.divide(divisor).toJSNumber(); - seconds = milliseconds = microseconds = nanoseconds = 0; + total = norm.fdiv(divisor); + norm = norm.round(divisor * increment, roundingMode); break; } case 'second': { const divisor = 1e9; - nanoseconds = bigInt(seconds) - .multiply(1e9) - .plus(bigInt(milliseconds).multiply(1e6)) - .plus(bigInt(microseconds).multiply(1e3)) - .plus(nanoseconds); - const { quotient, remainder } = nanoseconds.divmod(divisor); - total = quotient.toJSNumber() + remainder.toJSNumber() / divisor; - const rounded = RoundNumberToIncrement(nanoseconds, divisor * increment, roundingMode); - seconds = rounded.divide(divisor).toJSNumber(); - milliseconds = microseconds = nanoseconds = 0; + total = norm.fdiv(divisor); + norm = norm.round(divisor * increment, roundingMode); break; } case 'millisecond': { const divisor = 1e6; - nanoseconds = bigInt(milliseconds).multiply(1e6).plus(bigInt(microseconds).multiply(1e3)).plus(nanoseconds); - const { quotient, remainder } = nanoseconds.divmod(divisor); - total = quotient.toJSNumber() + remainder.toJSNumber() / divisor; - const rounded = RoundNumberToIncrement(nanoseconds, divisor * increment, roundingMode); - milliseconds = rounded.divide(divisor).toJSNumber(); - microseconds = nanoseconds = 0; + total = norm.fdiv(divisor); + norm = norm.round(divisor * increment, roundingMode); break; } case 'microsecond': { const divisor = 1e3; - nanoseconds = bigInt(microseconds).multiply(1e3).plus(nanoseconds); - const { quotient, remainder } = nanoseconds.divmod(divisor); - total = quotient.toJSNumber() + remainder.toJSNumber() / divisor; - const rounded = RoundNumberToIncrement(nanoseconds, divisor * increment, roundingMode); - microseconds = rounded.divide(divisor).toJSNumber(); - nanoseconds = 0; + total = norm.fdiv(divisor); + norm = norm.round(divisor * increment, roundingMode); break; } case 'nanosecond': { - total = nanoseconds; - nanoseconds = RoundNumberToIncrement(bigInt(nanoseconds), increment, roundingMode).toJSNumber(); + total = norm.totalNs.toJSNumber(); + norm = norm.round(increment, roundingMode); break; } } - RejectDuration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds); - return { years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, total }; + CombineDateAndNormalizedTimeDuration(years, months, weeks, days, norm); + return { years, months, weeks, days, norm, total }; } export function CompareISODate(y1, m1, d1, y2, m2, d2) { diff --git a/polyfill/lib/math.mjs b/polyfill/lib/math.mjs index 751e3eac2d..fc8fcb6cb6 100644 --- a/polyfill/lib/math.mjs +++ b/polyfill/lib/math.mjs @@ -4,6 +4,8 @@ const MathSign = Math.sign; const MathTrunc = Math.trunc; const NumberParseInt = Number.parseInt; const NumberPrototypeToPrecision = Number.prototype.toPrecision; +const StringPrototypePadStart = String.prototype.padStart; +const StringPrototypeRepeat = String.prototype.repeat; const StringPrototypeSlice = String.prototype.slice; import Call from 'es-abstract/2022/Call.js'; @@ -30,3 +32,24 @@ export function TruncatingDivModByPowerOf10(x, p) { return { div, mod }; } + +// Computes x * 10**p + z with precision loss only at the end, by string +// manipulation. If the result is a safe integer, then it is exact. x must be +// an integer. p must be a non-negative integer. z must have the same sign as +// x and be less than 10**p. +export function FMAPowerOf10(x, p, z) { + if (x === 0) return z; + + const sign = MathSign(x) || MathSign(z); + x = MathAbs(x); + z = MathAbs(z); + + const xStr = Call(NumberPrototypeToPrecision, x, [MathTrunc(1 + MathLog10(x))]); + + if (z === 0) return sign * NumberParseInt(xStr + Call(StringPrototypeRepeat, '0', [p]), 10); + + const zStr = Call(NumberPrototypeToPrecision, z, [MathTrunc(1 + MathLog10(z))]); + + const resStr = xStr + Call(StringPrototypePadStart, zStr, [p, '0']); + return sign * NumberParseInt(resStr, 10); +} diff --git a/polyfill/lib/timeduration.mjs b/polyfill/lib/timeduration.mjs new file mode 100644 index 0000000000..1abb06e399 --- /dev/null +++ b/polyfill/lib/timeduration.mjs @@ -0,0 +1,148 @@ +import bigInt from 'big-integer'; + +const MathAbs = Math.abs; +const MathSign = Math.sign; +const NumberIsInteger = Number.isInteger; +const NumberIsSafeInteger = Number.isSafeInteger; + +export class TimeDuration { + static MAX = bigInt('9007199254740991999999999'); + static ZERO = new TimeDuration(bigInt.zero); + + constructor(totalNs) { + if (typeof totalNs === 'number') throw new Error('assertion failed: big integer required'); + this.totalNs = bigInt(totalNs); + if (this.totalNs.abs().greater(TimeDuration.MAX)) throw new Error('assertion failed: integer too big'); + + const { quotient, remainder } = this.totalNs.divmod(1e9); + this.sec = quotient.toJSNumber(); + this.subsec = remainder.toJSNumber(); + if (!NumberIsSafeInteger(this.sec)) throw new Error('assertion failed: seconds too big'); + if (MathAbs(this.subsec) > 999_999_999) throw new Error('assertion failed: subseconds too big'); + } + + static #validateNew(totalNs, operation) { + if (totalNs.abs().greater(TimeDuration.MAX)) { + throw new RangeError(`${operation} of duration time units cannot exceed ${TimeDuration.MAX} s`); + } + return new TimeDuration(totalNs); + } + + static fromEpochNsDiff(epochNs1, epochNs2) { + const diff = bigInt(epochNs1).subtract(epochNs2); + // No extra validate step. Should instead fail assertion if too big + return new TimeDuration(diff); + } + + static normalize(h, min, s, ms, µs, ns) { + const totalNs = bigInt(ns) + .add(bigInt(µs).multiply(1e3)) + .add(bigInt(ms).multiply(1e6)) + .add(bigInt(s).multiply(1e9)) + .add(bigInt(min).multiply(60e9)) + .add(bigInt(h).multiply(3600e9)); + return TimeDuration.#validateNew(totalNs, 'total'); + } + + abs() { + return new TimeDuration(this.totalNs.abs()); + } + + add(other) { + return TimeDuration.#validateNew(this.totalNs.add(other.totalNs), 'sum'); + } + + add24HourDays(days) { + if (!NumberIsInteger(days)) throw new Error('assertion failed: days is an integer'); + return TimeDuration.#validateNew(this.totalNs.add(bigInt(days).multiply(86400e9)), 'sum'); + } + + addToEpochNs(epochNs) { + return bigInt(epochNs).add(this.totalNs); + } + + cmp(other) { + return this.totalNs.compare(other.totalNs); + } + + divmod(n) { + if (n === 0) throw new Error('division by zero'); + const { quotient, remainder } = this.totalNs.divmod(n); + const q = quotient.toJSNumber(); + const r = new TimeDuration(remainder); + return { quotient: q, remainder: r }; + } + + fdiv(n) { + if (n === 0) throw new Error('division by zero'); + let { quotient, remainder } = this.totalNs.divmod(n); + + // Perform long division to calculate the fractional part of the quotient + // remainder / n with more accuracy than 64-bit floating point division + const precision = 50; + const decimalDigits = []; + let digit; + const sign = (this.totalNs.geq(0) ? 1 : -1) * MathSign(n); + while (!remainder.isZero() && decimalDigits.length < precision) { + remainder = remainder.multiply(10); + ({ quotient: digit, remainder } = remainder.divmod(n)); + decimalDigits.push(MathAbs(digit.toJSNumber())); + } + return sign * Number(quotient.abs().toString() + '.' + decimalDigits.join('')); + } + + isZero() { + return this.totalNs.isZero(); + } + + round(increment, mode) { + if (increment === 1) return this; + let { quotient, remainder } = this.totalNs.divmod(increment); + if (remainder.equals(bigInt.zero)) return this; + const sign = remainder.lt(bigInt.zero) ? -1 : 1; + const tiebreaker = remainder.multiply(2).abs(); + const tie = tiebreaker.equals(increment); + const expandIsNearer = tiebreaker.gt(increment); + switch (mode) { + case 'ceil': + if (sign > 0) quotient = quotient.add(sign); + break; + case 'floor': + if (sign < 0) quotient = quotient.add(sign); + break; + case 'expand': + // always expand if there is a remainder + quotient = quotient.add(sign); + break; + case 'trunc': + // no change needed, because divmod is a truncation + break; + case 'halfCeil': + if (expandIsNearer || (tie && sign > 0)) quotient = quotient.add(sign); + break; + case 'halfFloor': + if (expandIsNearer || (tie && sign < 0)) quotient = quotient.add(sign); + break; + case 'halfExpand': + // "half up away from zero" + if (expandIsNearer || tie) quotient = quotient.add(sign); + break; + case 'halfTrunc': + if (expandIsNearer) quotient = quotient.add(sign); + break; + case 'halfEven': { + if (expandIsNearer || (tie && quotient.isOdd())) quotient = quotient.add(sign); + break; + } + } + return TimeDuration.#validateNew(quotient.multiply(increment), 'rounding'); + } + + sign() { + return this.cmp(new TimeDuration(0n)); + } + + subtract(other) { + return TimeDuration.#validateNew(this.totalNs.subtract(other.totalNs), 'difference'); + } +} diff --git a/polyfill/lib/zoneddatetime.mjs b/polyfill/lib/zoneddatetime.mjs index d0036c66d2..7f292133f8 100644 --- a/polyfill/lib/zoneddatetime.mjs +++ b/polyfill/lib/zoneddatetime.mjs @@ -18,6 +18,7 @@ import { TIME_ZONE, GetSlot } from './slots.mjs'; +import { TimeDuration } from './timeduration.mjs'; import bigInt from 'big-integer'; @@ -157,9 +158,8 @@ export class ZonedDateTime { ); const todayNs = GetSlot(ES.GetInstantFor(timeZoneRec, today, 'compatible'), EPOCHNANOSECONDS); const tomorrowNs = GetSlot(ES.GetInstantFor(timeZoneRec, tomorrow, 'compatible'), EPOCHNANOSECONDS); - const diffNs = tomorrowNs.subtract(todayNs); - const { quotient, remainder } = diffNs.divmod(3.6e12); - return quotient.toJSNumber() + remainder.toJSNumber() / 3.6e12; + const diff = TimeDuration.fromEpochNsDiff(tomorrowNs, todayNs); + return diff.fdiv(3.6e12); } get daysInWeek() { if (!ES.IsTemporalZonedDateTime(this)) throw new TypeError('invalid receiver'); diff --git a/polyfill/test/all.mjs b/polyfill/test/all.mjs index fa738de130..d25fd9ae04 100644 --- a/polyfill/test/all.mjs +++ b/polyfill/test/all.mjs @@ -17,6 +17,9 @@ import './ecmascript.mjs'; // Power-of-10 math import './math.mjs'; +// Internal 96-bit integer implementation, not suitable for test262 +import './timeduration.mjs'; + Promise.resolve() .then(() => { return Demitasse.report(Pretty.reporter); diff --git a/polyfill/test/math.mjs b/polyfill/test/math.mjs index bd46381267..2e86b9c555 100644 --- a/polyfill/test/math.mjs +++ b/polyfill/test/math.mjs @@ -5,9 +5,9 @@ import Pretty from '@pipobscure/demitasse-pretty'; const { reporter } = Pretty; import { strict as assert } from 'assert'; -const { deepEqual } = assert; +const { deepEqual, equal } = assert; -import { TruncatingDivModByPowerOf10 as div } from '../lib/math.mjs'; +import { TruncatingDivModByPowerOf10 as div, FMAPowerOf10 as fma } from '../lib/math.mjs'; describe('Math', () => { describe('TruncatingDivModByPowerOf10', () => { @@ -61,6 +61,47 @@ describe('Math', () => { it('-9007199254740990926258176/10**9 = -MAX_SAFE_INTEGER+1, -926258176', () => deepEqual(div(-9007199254740990926258176, 9), { div: -Number.MAX_SAFE_INTEGER + 1, mod: -926258176 })); }); + + describe('FMAPowerOf10', () => { + it('0*10**0+0 = 0', () => equal(fma(0, 0, 0), 0)); + it('-0*10**0-0 = -0', () => equal(fma(-0, 0, -0), -0)); + it('1*10**0+0 = 1', () => equal(fma(1, 0, 0), 1)); + it('-1*10**0+0 = -1', () => equal(fma(-1, 0, 0), -1)); + it('0*10**50+1234 = 1234', () => equal(fma(0, 50, 1234), 1234)); + it('-0*10**50-1234 = -1234', () => equal(fma(-0, 50, -1234), -1234)); + it('1234*10**12+0', () => equal(fma(1234, 12, 0), 1234000000000000)); + it('-1234*10**12-0', () => equal(fma(-1234, 12, -0), -1234000000000000)); + + it('2*10**2+45 = 245', () => equal(fma(2, 2, 45), 245)); + it('2*10**3+45 = 2045', () => equal(fma(2, 3, 45), 2045)); + it('2*10**4+45 = 20045', () => equal(fma(2, 4, 45), 20045)); + it('2*10**5+45 = 200045', () => equal(fma(2, 5, 45), 200045)); + it('2*10**6+45 = 2000045', () => equal(fma(2, 6, 45), 2000045)); + + it('-2*10**2-45 = -245', () => equal(fma(-2, 2, -45), -245)); + it('-2*10**3-45 = -2045', () => equal(fma(-2, 3, -45), -2045)); + it('-2*10**4-45 = -20045', () => equal(fma(-2, 4, -45), -20045)); + it('-2*10**5-45 = -200045', () => equal(fma(-2, 5, -45), -200045)); + it('-2*10**6-45 = -2000045', () => equal(fma(-2, 6, -45), -2000045)); + + it('8692288669465520*10**9+321414345 = 8692288669465520321414345, rounded to 8692288669465520839327744', () => + equal(fma(8692288669465520, 9, 321414345), 8692288669465520839327744)); + it('-8692288669465520*10**9-321414345 = -8692288669465520321414345, rounded to -8692288669465520839327744', () => + equal(fma(-8692288669465520, 9, -321414345), -8692288669465520839327744)); + + it('MAX_SAFE_INTEGER*10**3+999 rounded to 9007199254740992000', () => + equal(fma(Number.MAX_SAFE_INTEGER, 3, 999), 9007199254740992000)); + it('-MAX_SAFE_INTEGER*10**3-999 rounded to -9007199254740992000', () => + equal(fma(-Number.MAX_SAFE_INTEGER, 3, -999), -9007199254740992000)); + it('MAX_SAFE_INTEGER*10**6+999999 rounded to 9007199254740992000000', () => + equal(fma(Number.MAX_SAFE_INTEGER, 6, 999999), 9007199254740992000000)); + it('-MAX_SAFE_INTEGER*10**6-999999 rounded to -9007199254740992000000', () => + equal(fma(-Number.MAX_SAFE_INTEGER, 6, -999999), -9007199254740992000000)); + it('MAX_SAFE_INTEGER*10**3+999 rounded to 9007199254740992000', () => + equal(fma(Number.MAX_SAFE_INTEGER, 9, 999999999), 9007199254740992000000000)); + it('-MAX_SAFE_INTEGER*10**3-999 rounded to -9007199254740992000', () => + equal(fma(-Number.MAX_SAFE_INTEGER, 9, -999999999), -9007199254740992000000000)); + }); }); import { normalize } from 'path'; diff --git a/polyfill/test/timeduration.mjs b/polyfill/test/timeduration.mjs new file mode 100644 index 0000000000..185aa011a4 --- /dev/null +++ b/polyfill/test/timeduration.mjs @@ -0,0 +1,514 @@ +import Demitasse from '@pipobscure/demitasse'; +const { describe, it, report } = Demitasse; + +import Pretty from '@pipobscure/demitasse-pretty'; +const { reporter } = Pretty; + +import { strict as assert, AssertionError } from 'assert'; +const { equal, throws } = assert; + +import { TimeDuration } from '../lib/timeduration.mjs'; + +function check(timeDuration, sec, subsec) { + equal(timeDuration.sec, sec); + equal(timeDuration.subsec, subsec); +} + +function checkBigInt(value, bigint) { + if (value && typeof value === 'object') { + equal(value.value, bigint); // bigInteger wrapper + } else { + equal(value, bigint); // real bigint + } +} + +function checkFloat(value, float) { + if (!Number.isFinite(value) || Math.abs(value - float) > Number.EPSILON) { + throw new AssertionError({ + message: `Expected ${value} to be within ɛ of ${float}`, + expected: float, + actual: value, + operator: 'checkFloat' + }); + } +} + +describe('Normalized time duration', () => { + describe('construction', () => { + it('basic', () => { + check(new TimeDuration(123456789_987654321n), 123456789, 987654321); + check(new TimeDuration(-987654321_123456789n), -987654321, -123456789); + }); + + it('either sign with zero in the other component', () => { + check(new TimeDuration(123n), 0, 123); + check(new TimeDuration(-123n), 0, -123); + check(new TimeDuration(123_000_000_000n), 123, 0); + check(new TimeDuration(-123_000_000_000n), -123, 0); + }); + }); + + describe('construction impossible', () => { + it('out of range', () => { + throws(() => new TimeDuration(2n ** 53n * 1_000_000_000n)); + throws(() => new TimeDuration(-(2n ** 53n * 1_000_000_000n))); + }); + + it('not an integer', () => { + throws(() => new TimeDuration(Math.PI)); + }); + }); + + describe('fromEpochNsDiff()', () => { + it('basic', () => { + check(TimeDuration.fromEpochNsDiff(1695930183_043174412n, 1695930174_412168313n), 8, 631006099); + check(TimeDuration.fromEpochNsDiff(1695930174_412168313n, 1695930183_043174412n), -8, -631006099); + }); + + it('pre-epoch', () => { + check(TimeDuration.fromEpochNsDiff(-80000_987_654_321n, -86400_123_456_789n), 6399, 135802468); + check(TimeDuration.fromEpochNsDiff(-86400_123_456_789n, -80000_987_654_321n), -6399, -135802468); + }); + + it('cross-epoch', () => { + check(TimeDuration.fromEpochNsDiff(1_000_001_000n, -2_000_002_000n), 3, 3000); + check(TimeDuration.fromEpochNsDiff(-2_000_002_000n, 1_000_001_000n), -3, -3000); + }); + + it('maximum epoch difference', () => { + const max = 86400_0000_0000_000_000_000n; + check(TimeDuration.fromEpochNsDiff(max, -max), 172800_0000_0000, 0); + check(TimeDuration.fromEpochNsDiff(-max, max), -172800_0000_0000, 0); + }); + }); + + describe('normalize()', () => { + it('basic', () => { + check(TimeDuration.normalize(1, 1, 1, 1, 1, 1), 3661, 1001001); + check(TimeDuration.normalize(-1, -1, -1, -1, -1, -1), -3661, -1001001); + }); + + it('overflow from one unit to another', () => { + check(TimeDuration.normalize(1, 61, 61, 998, 1000, 1000), 7321, 999001000); + check(TimeDuration.normalize(-1, -61, -61, -998, -1000, -1000), -7321, -999001000); + }); + + it('overflow from subseconds to seconds', () => { + check(TimeDuration.normalize(0, 0, 1, 1000, 0, 0), 2, 0); + check(TimeDuration.normalize(0, 0, -1, -1000, 0, 0), -2, 0); + }); + + it('multiple overflows from subseconds to seconds', () => { + check(TimeDuration.normalize(0, 0, 0, 1234567890, 1234567890, 1234567890), 1235803, 692457890); + check(TimeDuration.normalize(0, 0, 0, -1234567890, -1234567890, -1234567890), -1235803, -692457890); + }); + + it('fails on overflow', () => { + throws(() => TimeDuration.normalize(2501999792984, 0, 0, 0, 0, 0), RangeError); + throws(() => TimeDuration.normalize(-2501999792984, 0, 0, 0, 0, 0), RangeError); + throws(() => TimeDuration.normalize(0, 150119987579017, 0, 0, 0, 0), RangeError); + throws(() => TimeDuration.normalize(0, -150119987579017, 0, 0, 0, 0), RangeError); + throws(() => TimeDuration.normalize(0, 0, 2 ** 53, 0, 0, 0), RangeError); + throws(() => TimeDuration.normalize(0, 0, -(2 ** 53), 0, 0, 0), RangeError); + throws(() => TimeDuration.normalize(0, 0, Number.MAX_SAFE_INTEGER, 1000, 0, 0), RangeError); + throws(() => TimeDuration.normalize(0, 0, -Number.MAX_SAFE_INTEGER, -1000, 0, 0), RangeError); + throws(() => TimeDuration.normalize(0, 0, Number.MAX_SAFE_INTEGER, 0, 1000000, 0), RangeError); + throws(() => TimeDuration.normalize(0, 0, -Number.MAX_SAFE_INTEGER, 0, -1000000, 0), RangeError); + throws(() => TimeDuration.normalize(0, 0, Number.MAX_SAFE_INTEGER, 0, 0, 1000000000), RangeError); + throws(() => TimeDuration.normalize(0, 0, -Number.MAX_SAFE_INTEGER, 0, 0, -1000000000), RangeError); + }); + }); + + describe('abs()', () => { + it('positive', () => { + const d = new TimeDuration(123_456_654_321n); + check(d.abs(), 123, 456_654_321); + }); + + it('negative', () => { + const d = new TimeDuration(-123_456_654_321n); + check(d.abs(), 123, 456_654_321); + }); + + it('zero', () => { + const d = new TimeDuration(0n); + check(d.abs(), 0, 0); + }); + }); + + describe('add()', () => { + it('basic', () => { + const d1 = new TimeDuration(123_456_654_321_123_456n); + const d2 = new TimeDuration(654_321_123_456_654_321n); + check(d1.add(d2), 777_777_777, 777_777_777); + }); + + it('negative', () => { + const d1 = new TimeDuration(-123_456_654_321_123_456n); + const d2 = new TimeDuration(-654_321_123_456_654_321n); + check(d1.add(d2), -777_777_777, -777_777_777); + }); + + it('signs differ', () => { + const d1 = new TimeDuration(333_333_333_333_333_333n); + const d2 = new TimeDuration(-222_222_222_222_222_222n); + check(d1.add(d2), 111_111_111, 111_111_111); + + const d3 = new TimeDuration(-333_333_333_333_333_333n); + const d4 = new TimeDuration(222_222_222_222_222_222n); + check(d3.add(d4), -111_111_111, -111_111_111); + }); + + it('cross zero', () => { + const d1 = new TimeDuration(222_222_222_222_222_222n); + const d2 = new TimeDuration(-333_333_333_333_333_333n); + check(d1.add(d2), -111_111_111, -111_111_111); + }); + + it('overflow from subseconds to seconds', () => { + const d1 = new TimeDuration(999_999_999n); + const d2 = new TimeDuration(2n); + check(d1.add(d2), 1, 1); + }); + + it('fails on overflow', () => { + const d1 = new TimeDuration(2n ** 52n * 1_000_000_000n); + throws(() => d1.add(d1), RangeError); + }); + }); + + describe('add24HourDays()', () => { + it('basic', () => { + const d = new TimeDuration(111_111_111_111_111_111n); + check(d.add24HourDays(10), 111_975_111, 111_111_111); + }); + + it('negative', () => { + const d = new TimeDuration(-111_111_111_111_111_111n); + check(d.add24HourDays(-10), -111_975_111, -111_111_111); + }); + + it('signs differ', () => { + const d1 = new TimeDuration(864000_000_000_000n); + check(d1.add24HourDays(-5), 432000, 0); + + const d2 = new TimeDuration(-864000_000_000_000n); + check(d2.add24HourDays(5), -432000, 0); + }); + + it('cross zero', () => { + const d1 = new TimeDuration(86400_000_000_000n); + check(d1.add24HourDays(-2), -86400, 0); + + const d2 = new TimeDuration(-86400_000_000_000n); + check(d2.add24HourDays(3), 172800, 0); + }); + + it('overflow from subseconds to seconds', () => { + const d1 = new TimeDuration(-86400_333_333_333n); + check(d1.add24HourDays(2), 86399, 666_666_667); + + const d2 = new TimeDuration(86400_333_333_333n); + check(d2.add24HourDays(-2), -86399, -666_666_667); + }); + + it('does not accept non-integers', () => { + const d = new TimeDuration(0n); + throws(() => d.add24HourDays(1.5), Error); + }); + + it('fails on overflow', () => { + const d = new TimeDuration(0n); + throws(() => d.add24HourDays(104249991375), RangeError); + throws(() => d.add24HourDays(-104249991375), RangeError); + }); + }); + + describe('addToEpochNs()', () => { + it('basic', () => { + const d = new TimeDuration(123_456_654_321_123_456n); + checkBigInt(d.addToEpochNs(654_321_123_456_654_321n), 777_777_777_777_777_777n); + }); + + it('negative', () => { + const d = new TimeDuration(-123_456_654_321_123_456n); + checkBigInt(d.addToEpochNs(-654_321_123_456_654_321n), -777_777_777_777_777_777n); + }); + + it('signs differ', () => { + const d1 = new TimeDuration(333_333_333_333_333_333n); + checkBigInt(d1.addToEpochNs(-222_222_222_222_222_222n), 111_111_111_111_111_111n); + + const d2 = new TimeDuration(-333_333_333_333_333_333n); + checkBigInt(d2.addToEpochNs(222_222_222_222_222_222n), -111_111_111_111_111_111n); + }); + + it('cross zero', () => { + const d = new TimeDuration(222_222_222_222_222_222n); + checkBigInt(d.addToEpochNs(-333_333_333_333_333_333n), -111_111_111_111_111_111n); + }); + + it('does not fail on overflow, epochNs overflow is checked elsewhere', () => { + const d = new TimeDuration(86400_0000_0000_000_000_000n); + checkBigInt(d.addToEpochNs(86400_0000_0000_000_000_000n), 172800_0000_0000_000_000_000n); + }); + }); + + describe('cmp()', () => { + it('equal', () => { + const d1 = new TimeDuration(123_000_000_456n); + const d2 = new TimeDuration(123_000_000_456n); + equal(d1.cmp(d2), 0); + equal(d2.cmp(d1), 0); + }); + + it('unequal', () => { + const smaller = new TimeDuration(123_000_000_456n); + const larger = new TimeDuration(654_000_000_321n); + equal(smaller.cmp(larger), -1); + equal(larger.cmp(smaller), 1); + }); + + it('cross sign', () => { + const neg = new TimeDuration(-654_000_000_321n); + const pos = new TimeDuration(123_000_000_456n); + equal(neg.cmp(pos), -1); + equal(pos.cmp(neg), 1); + }); + }); + + describe('divmod()', () => { + it('divide by 1', () => { + const d = new TimeDuration(1_234_567_890_987n); + const { quotient, remainder } = d.divmod(1); + equal(quotient, 1234567890987); + check(remainder, 0, 0); + }); + + it('divide by self', () => { + const d = new TimeDuration(1_234_567_890n); + const { quotient, remainder } = d.divmod(1_234_567_890); + equal(quotient, 1); + check(remainder, 0, 0); + }); + + it('no remainder', () => { + const d = new TimeDuration(1_234_000_000n); + const { quotient, remainder } = d.divmod(1e6); + equal(quotient, 1234); + check(remainder, 0, 0); + }); + + it('divide by -1', () => { + const d = new TimeDuration(1_234_567_890_987n); + const { quotient, remainder } = d.divmod(-1); + equal(quotient, -1_234_567_890_987); + check(remainder, 0, 0); + }); + + it('zero seconds remainder has sign of dividend', () => { + const d1 = new TimeDuration(1_234_567_890n); + let { quotient, remainder } = d1.divmod(-1e6); + equal(quotient, -1234); + check(remainder, 0, 567890); + const d2 = new TimeDuration(-1_234_567_890n); + ({ quotient, remainder } = d2.divmod(1e6)); + equal(quotient, -1234); + check(remainder, 0, -567890); + }); + + it('nonzero seconds remainder has sign of dividend', () => { + const d1 = new TimeDuration(10_234_567_890n); + let { quotient, remainder } = d1.divmod(-9e9); + equal(quotient, -1); + check(remainder, 1, 234567890); + const d2 = new TimeDuration(-10_234_567_890n); + ({ quotient, remainder } = d2.divmod(9e9)); + equal(quotient, -1); + check(remainder, -1, -234567890); + }); + + it('negative with zero seconds remainder', () => { + const d = new TimeDuration(-1_234_567_890n); + const { quotient, remainder } = d.divmod(-1e6); + equal(quotient, 1234); + check(remainder, 0, -567890); + }); + + it('negative with nonzero seconds remainder', () => { + const d = new TimeDuration(-10_234_567_890n); + const { quotient, remainder } = d.divmod(-9e9); + equal(quotient, 1); + check(remainder, -1, -234567890); + }); + + it('quotient larger than seconds', () => { + const d = TimeDuration.normalize(25 + 5 * 24, 0, 86401, 333, 666, 999); + const { quotient, remainder } = d.divmod(86400e9); + equal(quotient, 7); + check(remainder, 3601, 333666999); + }); + + it('quotient smaller than seconds', () => { + const d = new TimeDuration(90061_333666999n); + const result1 = d.divmod(1000); + equal(result1.quotient, 90061333666); + check(result1.remainder, 0, 999); + + const result2 = d.divmod(10); + equal(result2.quotient, 9006133366699); + check(result2.remainder, 0, 9); + + const result3 = d.divmod(3); + equal(result3.quotient, 30020444555666); + check(result3.remainder, 0, 1); + }); + + it('divide by 0', () => { + const d = new TimeDuration(90061_333666999n); + throws(() => d.divmod(0), Error); + }); + }); + + describe('fdiv()', () => { + it('divide by 1', () => { + const d = new TimeDuration(1_234_567_890_987n); + equal(d.fdiv(1), 1_234_567_890_987); + }); + + it('no remainder', () => { + const d = new TimeDuration(1_234_000_000n); + equal(d.fdiv(1e6), 1234); + }); + + it('divide by -1', () => { + const d = new TimeDuration(1_234_567_890_987n); + equal(d.fdiv(-1), -1_234_567_890_987); + }); + + it('opposite sign', () => { + const d1 = new TimeDuration(1_234_567_890n); + checkFloat(d1.fdiv(-1e6), -1234.56789); + const d2 = new TimeDuration(-1_234_567_890n); + checkFloat(d2.fdiv(1e6), -1234.56789); + const d3 = new TimeDuration(-432n); + checkFloat(d3.fdiv(864), -0.5); + }); + + it('negative', () => { + const d = new TimeDuration(-1_234_567_890n); + checkFloat(d.fdiv(-1e6), 1234.56789); + }); + + it('quotient larger than seconds', () => { + const d = TimeDuration.normalize(25 + 5 * 24, 0, 86401, 333, 666, 999); + checkFloat(d.fdiv(86400e9), 7.041682102627303); + }); + + it('quotient smaller than seconds', () => { + const d = new TimeDuration(90061_333666999n); + checkFloat(d.fdiv(1000), 90061333666.999); + checkFloat(d.fdiv(10), 9006133366699.9); + // eslint-disable-next-line @typescript-eslint/no-loss-of-precision + checkFloat(d.fdiv(3), 30020444555666.333); + }); + + it('divide by 0', () => { + const d = new TimeDuration(90061_333666999n); + throws(() => d.fdiv(0), Error); + }); + + it('large number', () => { + const d = new TimeDuration(2939649_187497660n); + checkFloat(d.fdiv(3600e9), 816.56921874935); + }); + }); + + it('isZero()', () => { + assert(new TimeDuration(0n).isZero()); + assert(!new TimeDuration(1_000_000_000n).isZero()); + assert(!new TimeDuration(-1n).isZero()); + assert(!new TimeDuration(1_000_000_001n).isZero()); + }); + + describe('round()', () => { + it('basic', () => { + const d = new TimeDuration(1_234_567_890n); + check(d.round(1000, 'halfExpand'), 1, 234568000); + }); + + it('increment 1', () => { + const d = new TimeDuration(1_234_567_890n); + check(d.round(1, 'ceil'), 1, 234567890); + }); + + it('rounds up from subseconds to seconds', () => { + const d = new TimeDuration(1_999_999_999n); + check(d.round(1e9, 'halfExpand'), 2, 0); + }); + + describe('Rounding modes', () => { + const increment = 100; + const testValues = [-150, -100, -80, -50, -30, 0, 30, 50, 80, 100, 150]; + const expectations = { + ceil: [-100, -100, 0, 0, 0, 0, 100, 100, 100, 100, 200], + floor: [-200, -100, -100, -100, -100, 0, 0, 0, 0, 100, 100], + trunc: [-100, -100, 0, 0, 0, 0, 0, 0, 0, 100, 100], + expand: [-200, -100, -100, -100, -100, 0, 100, 100, 100, 100, 200], + halfCeil: [-100, -100, -100, 0, 0, 0, 0, 100, 100, 100, 200], + halfFloor: [-200, -100, -100, -100, 0, 0, 0, 0, 100, 100, 100], + halfTrunc: [-100, -100, -100, 0, 0, 0, 0, 0, 100, 100, 100], + halfExpand: [-200, -100, -100, -100, 0, 0, 0, 100, 100, 100, 200], + halfEven: [-200, -100, -100, 0, 0, 0, 0, 0, 100, 100, 200] + }; + for (const roundingMode of Object.keys(expectations)) { + describe(roundingMode, () => { + testValues.forEach((value, ix) => { + const expected = expectations[roundingMode][ix]; + + it(`rounds ${value} ns to ${expected} ns`, () => { + const d = new TimeDuration(BigInt(value)); + const result = d.round(increment, roundingMode); + check(result, 0, expected); + }); + + it(`rounds ${value} s to ${expected} s`, () => { + const d = new TimeDuration(BigInt(value * 1e9)); + const result = d.round(increment * 1e9, roundingMode); + check(result, expected, 0); + }); + }); + }); + } + }); + }); + + it('sign()', () => { + equal(new TimeDuration(0n).sign(), 0); + equal(new TimeDuration(-1n).sign(), -1); + equal(new TimeDuration(-1_000_000_000n).sign(), -1); + equal(new TimeDuration(1n).sign(), 1); + equal(new TimeDuration(1_000_000_000n).sign(), 1); + }); + + describe('subtract', () => { + it('basic', () => { + const d1 = new TimeDuration(321_987654321n); + const d2 = new TimeDuration(123_123456789n); + check(d1.subtract(d2), 198, 864197532); + check(d2.subtract(d1), -198, -864197532); + }); + + it('signs differ in result', () => { + const d1 = new TimeDuration(3661_001001001n); + const d2 = new TimeDuration(86400_000_000_000n); + check(d1.subtract(d2), -82738, -998998999); + check(d2.subtract(d1), 82738, 998998999); + }); + }); +}); + +import { normalize } from 'path'; +if (normalize(import.meta.url.slice(8)) === normalize(process.argv[1])) { + report(reporter).then((failed) => process.exit(failed ? 1 : 0)); +} diff --git a/spec/abstractops.html b/spec/abstractops.html index 2aae60e2f8..a3aecdb79c 100644 --- a/spec/abstractops.html +++ b/spec/abstractops.html @@ -1751,7 +1751,18 @@

1. Let _factor_ be -1. 1. Else, 1. Let _factor_ be 1. - 1. Return ! CreateDurationRecord(_yearsMV_ × _factor_, _monthsMV_ × _factor_, _weeksMV_ × _factor_, _daysMV_ × _factor_, _hoursMV_ × _factor_, floor(_minutesMV_) × _factor_, floor(_secondsMV_) × _factor_, floor(_millisecondsMV_) × _factor_, floor(_microsecondsMV_) × _factor_, floor(_nanosecondsMV_) × _factor_). + 1. Set _yearsMV_ to _yearsMV_ × _factor_. + 1. Set _monthsMV_ to _monthsMV_ × _factor_. + 1. Set _weeksMV_ to _weeksMV_ × _factor_. + 1. Set _daysMV_ to _daysMV_ × _factor_. + 1. Set _hoursMV_ to _hoursMV_ × _factor_. + 1. Set _minutesMV_ to floor(_minutesMV_) × _factor_. + 1. Set _secondsMV_ to floor(_secondsMV_) × _factor_. + 1. Set _millisecondsMV_ to floor(_millisecondsMV_) × _factor_. + 1. Set _microsecondsMV_ to floor(_microsecondsMV_) × _factor_. + 1. Set _nanosecondsMV_ to floor(_nanosecondsMV_) × _factor_. + 1. If IsValidDuration(_yearsMV_, _monthsMV_, _weeksMV_, _daysMV_, _hoursMV_, _minutesMV_, _secondsMV_, _millisecondsMV_, _microsecondsMV_, _nanosecondsMV_) is *false*, throw a *RangeError* exception. + 1. Return CreateDurationRecord(_yearsMV_, _monthsMV_, _weeksMV_, _daysMV_, _hoursMV_, _minutesMV_, _secondsMV_, _millisecondsMV_, _microsecondsMV_, _nanosecondsMV_). diff --git a/spec/calendar.html b/spec/calendar.html index 2b9cc47404..a5fe85369d 100644 --- a/spec/calendar.html +++ b/spec/calendar.html @@ -1448,8 +1448,9 @@

Temporal.Calendar.prototype.dateAdd ( _date_, _duration_ [ , _options_ ] ) diff --git a/spec/duration.html b/spec/duration.html index a62336e58b..dde5cbc88b 100644 --- a/spec/duration.html +++ b/spec/duration.html @@ -106,8 +106,10 @@

Temporal.Duration.compare ( _one_, _two_ [ , _options_ ] )

1. If _zonedRelativeTo_ is not *undefined*, and either _calendarUnitsPresent_ is *true*, or _one_.[[Days]] ≠ 0, or _two_.[[Days]] ≠ 0, then 1. Let _instant_ be ! CreateTemporalInstant(_zonedRelativeTo_.[[Nanoseconds]]). 1. Let _precalculatedPlainDateTime_ be ? GetPlainDateTimeFor(_timeZoneRec_, _instant_, _calendarRec_.[[Receiver]]). - 1. Let _after1_ be ? AddZonedDateTime(_zonedRelativeTo_.[[Nanoseconds]], _timeZoneRec_, _calendarRec_, _one_.[[Years]], _one_.[[Months]], _one_.[[Weeks]], _one_.[[Days]], _one_.[[Hours]], _one_.[[Minutes]], _one_.[[Seconds]], _one_.[[Milliseconds]], _one_.[[Microseconds]], _one_.[[Nanoseconds]], _precalculatedPlainDateTime_). - 1. Let _after2_ be ? AddZonedDateTime(_zonedRelativeTo_.[[Nanoseconds]], _timeZoneRec_, _calendarRec_, _two_.[[Years]], _two_.[[Months]], _two_.[[Weeks]], _two_.[[Days]], _two_.[[Hours]], _two_.[[Minutes]], _two_.[[Seconds]], _two_.[[Milliseconds]], _two_.[[Microseconds]], _two_.[[Nanoseconds]], _precalculatedPlainDateTime_). + 1. Let _norm1_ be NormalizeTimeDuration(_one_.[[Hours]], _one_.[[Minutes]], _one_.[[Seconds]], _one_.[[Milliseconds]], _one_.[[Microseconds]], _one_.[[Nanoseconds]]). + 1. Let _norm2_ be NormalizeTimeDuration(_two_.[[Hours]], _two_.[[Minutes]], _two_.[[Seconds]], _two_.[[Milliseconds]], _two_.[[Microseconds]], _two_.[[Nanoseconds]]). + 1. Let _after1_ be ? AddZonedDateTime(_zonedRelativeTo_.[[Nanoseconds]], _timeZoneRec_, _calendarRec_, _one_.[[Years]], _one_.[[Months]], _one_.[[Weeks]], _one_.[[Days]], _norm1_, _precalculatedPlainDateTime_). + 1. Let _after2_ be ? AddZonedDateTime(_zonedRelativeTo_.[[Nanoseconds]], _timeZoneRec_, _calendarRec_, _two_.[[Years]], _two_.[[Months]], _two_.[[Weeks]], _two_.[[Days]], _norm2_, _precalculatedPlainDateTime_). 1. If _after1_ > _after2_, return *1*𝔽. 1. If _after1_ < _after2_, return *-1*𝔽. 1. Return *+0*𝔽. @@ -119,13 +121,11 @@

Temporal.Duration.compare ( _one_, _two_ [ , _options_ ] )

1. Else, 1. Let _days1_ be _one_.[[Days]]. 1. Let _days2_ be _two_.[[Days]]. - 1. Let _hours1_ be _one_.[[Hours]] + _days1_ × 24. - 1. Let _hours2_ be _two_.[[Hours]] + _days2_ × 24. - 1. Let _ns1_ be TotalDurationNanoseconds(_hours1_, _one_.[[Minutes]], _one_.[[Seconds]], _one_.[[Milliseconds]], _one_.[[Microseconds]], _one_.[[Nanoseconds]]). - 1. Let _ns2_ be TotalDurationNanoseconds(_hours2_, _two_.[[Minutes]], _two_.[[Seconds]], _two_.[[Milliseconds]], _two_.[[Microseconds]], _two_.[[Nanoseconds]]). - 1. If _ns1_ > _ns2_, return *1*𝔽. - 1. If _ns1_ < _ns2_, return *-1*𝔽. - 1. Return *+0*𝔽. + 1. Let _norm1_ be NormalizeTimeDuration(_one_.[[Hours]], _one_.[[Minutes]], _one_.[[Seconds]], _one_.[[Milliseconds]], _one_.[[Microseconds]], _one_.[[Nanoseconds]]). + 1. Set _norm1_ to ! Add24HourDaysToNormalizedTimeDuration(_norm1_, _days1_). + 1. Let _norm2_ be NormalizeTimeDuration(_two_.[[Hours]], _two_.[[Minutes]], _two_.[[Seconds]], _two_.[[Milliseconds]], _two_.[[Microseconds]], _two_.[[Nanoseconds]]). + 1. Set _norm2_ to ! Add24HourDaysToNormalizedTimeDuration(_norm2_, _days2_). + 1. Return 𝔽(CompareNormalizedTimeDuration(_norm1_, _norm2_)). @@ -469,13 +469,15 @@

Temporal.Duration.prototype.round ( _roundTo_ )

1. Set _plainRelativeTo_ to ! CreateTemporalDate(_precalculatedPlainDateTime_.[[ISOYear]], _precalculatedPlainDateTime_.[[ISOMonth]], _precalculatedPlainDateTime_.[[ISODay]], _zonedRelativeTo_.[[Calendar]]). 1. Let _calendarRec_ be ? CreateCalendarMethodsRecordFromRelativeTo(_plainRelativeTo_, _zonedRelativeTo_, « ~date-add~, ~date-until~ »). 1. Let _unbalanceResult_ be ? UnbalanceDateDurationRelative(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _duration_.[[Days]], _largestUnit_, _plainRelativeTo_, _calendarRec_). - 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_, _calendarRec_, _zonedRelativeTo_, _timeZoneRec_, _precalculatedPlainDateTime_). - 1. Let _roundResult_ be _roundRecord_.[[DurationRecord]]. + 1. Let _norm_ be NormalizeTimeDuration(_duration_.[[Hours]], _duration_.[[Minutes]], _duration_.[[Seconds]], _duration_.[[Milliseconds]], _duration_.[[Microseconds]], _duration_.[[Nanoseconds]]). + 1. Let _roundRecord_ be ? RoundDuration(_unbalanceResult_.[[Years]], _unbalanceResult_.[[Months]], _unbalanceResult_.[[Weeks]], _unbalanceResult_.[[Days]], _norm_, _roundingIncrement_, _smallestUnit_, _roundingMode_, _plainRelativeTo_, _calendarRec_, _zonedRelativeTo_, _timeZoneRec_, _precalculatedPlainDateTime_). + 1. Let _roundResult_ be _roundRecord_.[[NormalizedDuration]]. 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_, _calendarRec_, _timeZoneRec_, _precalculatedPlainDateTime_). - 1. Let _balanceResult_ be ? BalanceTimeDurationRelative(_roundResult_.[[Days]], _roundResult_.[[Hours]], _roundResult_.[[Minutes]], _roundResult_.[[Seconds]], _roundResult_.[[Milliseconds]], _roundResult_.[[Microseconds]], _roundResult_.[[Nanoseconds]], _largestUnit_, _zonedRelativeTo_, _timeZoneRec_, _precalculatedPlainDateTime_). + 1. Set _roundResult_ to ? AdjustRoundedDurationDays(_roundResult_.[[Years]], _roundResult_.[[Months]], _roundResult_.[[Weeks]], _roundResult_.[[Days]], _roundResult_.[[NormalizedTime]], _roundingIncrement_, _smallestUnit_, _roundingMode_, _zonedRelativeTo_, _calendarRec_, _timeZoneRec_, _precalculatedPlainDateTime_). + 1. Let _balanceResult_ be ? BalanceTimeDurationRelative(_roundResult_.[[Days]], _roundResult_.[[NormalizedTime]], _largestUnit_, _zonedRelativeTo_, _timeZoneRec_, _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 _normWithDays_ be ? Add24HourDaysToNormalizedTimeDuration(_roundResult_.[[NormalizedTime]], _roundResult_.[[Days]]). + 1. Let _balanceResult_ be BalanceTimeDuration(_normWithDays_, _largestUnit_). 1. Let _result_ be ? BalanceDateDurationRelative(_roundResult_.[[Years]], _roundResult_.[[Months]], _roundResult_.[[Weeks]], _balanceResult_.[[Days]], _largestUnit_, _smallestUnit_, _plainRelativeTo_, _calendarRec_). 1. Return ! CreateTemporalDuration(_result_.[[Years]], _result_.[[Months]], _result_.[[Weeks]], _result_.[[Days]], _balanceResult_.[[Hours]], _balanceResult_.[[Minutes]], _balanceResult_.[[Seconds]], _balanceResult_.[[Milliseconds]], _balanceResult_.[[Microseconds]], _balanceResult_.[[Nanoseconds]]). @@ -511,12 +513,33 @@

Temporal.Duration.prototype.total ( _totalOf_ )

1. Set _plainRelativeTo_ to ! CreateTemporalDate(_precalculatedPlainDateTime_.[[ISOYear]], _precalculatedPlainDateTime_.[[ISOMonth]], _precalculatedPlainDateTime_.[[ISODay]], _zonedRelativeTo_.[[Calendar]]). 1. Let _calendarRec_ be ? CreateCalendarMethodsRecordFromRelativeTo(_plainRelativeTo_, _zonedRelativeTo_, « ~date-add~, ~date-until~ »). 1. Let _unbalanceResult_ be ? UnbalanceDateDurationRelative(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _duration_.[[Days]], _unit_, _plainRelativeTo_, _calendarRec_). + 1. Let _days_ be _unbalanceResult_.[[Days]]. 1. If _zonedRelativeTo_ is not *undefined*, then 1. Let _intermediate_ be ? MoveRelativeZonedDateTime(_zonedRelativeTo_, _calendarRec_, _timeZoneRec_, _unbalanceResult_.[[Years]], _unbalanceResult_.[[Months]], _unbalanceResult_.[[Weeks]], 0, _precalculatedPlainDateTime_). - 1. Let _balanceResult_ be ? BalanceTimeDurationRelative(_unbalanceResult_.[[Days]], _duration_.[[Hours]], _duration_.[[Minutes]], _duration_.[[Seconds]], _duration_.[[Milliseconds]], _duration_.[[Microseconds]], _duration_.[[Nanoseconds]], _unit_, _intermediate_, _timeZoneRec_). + 1. Let _norm_ be NormalizeTimeDuration(_duration_.[[Hours]], _duration_.[[Minutes]], _duration_.[[Seconds]], _duration_.[[Milliseconds]], _duration_.[[Microseconds]], _duration_.[[Nanoseconds]]). + 1. Let _startNs_ be _intermediate_.[[Nanoseconds]]. + 1. Let _startInstant_ be ! CreateTemporalInstant(_startNs_). + 1. Let _startDateTime_ be *undefined*. + 1. If _days_ ≠ 0, then + 1. Set _startDateTime_ to ? GetPlainDateTimeFor(_timeZoneRec_, _startInstant_, *"iso8601"*). + 1. Let _addResult_ be ? AddDaysToZonedDateTime(_startInstant_, _startDateTime_, _timeZoneRec_, *"iso8601"*, _days_). + 1. Let _intermediateNs_ be _addResult_.[[EpochNanoseconds]]. + 1. Else, + 1. Let _intermediateNs_ be _startNs_. + 1. Let _endNs_ be ? AddInstant(_intermediateNs_, _norm_). + 1. Set _norm_ to NormalizedTimeDurationFromEpochNanosecondsDifference(_endNs_, _startNs_). + 1. If _unit_ is one of *"year"*, *"month"*, *"week"*, or *"day"*, then + 1. If NormalizedTimeDurationIsZero(_norm_) is *false* and _startDateTime_ is *undefined*, set _startDateTime_ to ? GetPlainDateTimeFor(_timeZoneRec_, _startInstant_, *"iso8601"*). + 1. Let _result_ be ? NormalizedTimeDurationToDays(_norm_, _intermediate_, _timeZoneRec_, _startDateTime_). + 1. Set _norm_ to _result_.[[Remainder]]. + 1. Set _days_ to _result_.[[Days]]. + 1. Else, + 1. Set _days_ to 0. 1. Else, - 1. Let _balanceResult_ be ! BalanceTimeDuration(_unbalanceResult_.[[Days]], _duration_.[[Hours]], _duration_.[[Minutes]], _duration_.[[Seconds]], _duration_.[[Milliseconds]], _duration_.[[Microseconds]], _duration_.[[Nanoseconds]], _unit_). - 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_, _calendarRec_, _zonedRelativeTo_, _timeZoneRec_, _precalculatedPlainDateTime_). + 1. Let _norm_ be NormalizeTimeDuration(_duration_.[[Hours]], _duration_.[[Minutes]], _duration_.[[Seconds]], _duration_.[[Milliseconds]], _duration_.[[Microseconds]], _duration_.[[Nanoseconds]]). + 1. Set _norm_ to ! Add24HourDaysToNormalizedTimeDuration(_norm_, _days_). + 1. Set _days_ to 0. + 1. Let _roundRecord_ be ? RoundDuration(_unbalanceResult_.[[Years]], _unbalanceResult_.[[Months]], _unbalanceResult_.[[Weeks]], _days_, _norm_, 1, _unit_, *"trunc"*, _plainRelativeTo_, _calendarRec_, _zonedRelativeTo_, _timeZoneRec_, _precalculatedPlainDateTime_). 1. Return 𝔽(_roundRecord_.[[Total]]). @@ -537,14 +560,16 @@

Temporal.Duration.prototype.toString ( [ _options_ ] )

1. If _smallestUnit_ is *"hour"* or *"minute"*, throw a *RangeError* exception. 1. Let _precision_ be ToSecondsStringPrecisionRecord(_smallestUnit_, _digits_). 1. If _precision_.[[Unit]] is not *"nanosecond"* or _precision_.[[Increment]] ≠ 1, then + 1. Let _norm_ be NormalizeTimeDuration(_duration_.[[Hours]], _duration_.[[Minutes]], _duration_.[[Seconds]], _duration_.[[Milliseconds]], _duration_.[[Microseconds]], _duration_.[[Nanoseconds]]). 1. Let _largestUnit_ be DefaultTemporalLargestUnit(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _duration_.[[Days]], _duration_.[[Hours]], _duration_.[[Minutes]], _duration_.[[Seconds]], _duration_.[[Milliseconds]], _duration_.[[Microseconds]]). - 1. Let _roundRecord_ be ? RoundDuration(0, 0, 0, 0, 0, 0, _duration_.[[Seconds]], _duration_.[[Milliseconds]], _duration_.[[Microseconds]], _duration_.[[Nanoseconds]], _precision_.[[Increment]], _precision_.[[Unit]], _roundingMode_). - 1. Let _roundResult_ be _roundRecord_.[[DurationRecord]]. - 1. Let _balanceResult_ be ? BalanceTimeDuration(_duration_.[[Days]], _duration_.[[Hours]], _duration_.[[Minutes]], _roundResult_.[[Seconds]], _roundResult_.[[Milliseconds]], _roundResult_.[[Microseconds]], _roundResult_.[[Nanoseconds]], _largestUnit_). - 1. Let _result_ be ! CreateDurationRecord(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _balanceResult_.[[Days]], _balanceResult_.[[Hours]], _balanceResult_.[[Minutes]], _balanceResult_.[[Seconds]], _balanceResult_.[[Milliseconds]], _balanceResult_.[[Microseconds]], _balanceResult_.[[Nanoseconds]]). + 1. Let _roundRecord_ be ? RoundDuration(0, 0, 0, 0, _norm_, _precision_.[[Increment]], _precision_.[[Unit]], _roundingMode_). + 1. Set _norm_ to _roundRecord_.[[NormalizedDuration]].[[NormalizedTime]]. + 1. Let _result_ be BalanceTimeDuration(_norm_, _largestUnit_). + 1. Set _result_ to CreateDurationRecord(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _duration_.[[Days]] + _result_.[[Days]], _result_.[[Hours]], _result_.[[Minutes]], _result_.[[Seconds]], _result_.[[Milliseconds]], _result_.[[Microseconds]], _result_.[[Nanoseconds]]). 1. Else, 1. Let _result_ be _duration_. - 1. Return ! TemporalDurationToString(_result_.[[Years]], _result_.[[Months]], _result_.[[Weeks]], _result_.[[Days]], _result_.[[Hours]], _result_.[[Minutes]], _result_.[[Seconds]], _result_.[[Milliseconds]], _result_.[[Microseconds]], _result_.[[Nanoseconds]], _precision_.[[Precision]]). + 1. Let _normSeconds_ be NormalizeTimeDuration(0, 0, _result_.[[Seconds]], _result_.[[Milliseconds]], _result_.[[Microseconds]], _result_.[[Nanoseconds]]). + 1. Return ! TemporalDurationToString(_result_.[[Years]], _result_.[[Months]], _result_.[[Weeks]], _result_.[[Days]], _result_.[[Hours]], _result_.[[Minutes]], _normSeconds_, _precision_.[[Precision]]). @@ -556,7 +581,8 @@

Temporal.Duration.prototype.toJSON ( )

1. Let _duration_ be the *this* value. 1. Perform ? RequireInternalSlot(_duration_, [[InitializedTemporalDuration]]). - 1. Return ! TemporalDurationToString(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _duration_.[[Days]], _duration_.[[Hours]], _duration_.[[Minutes]], _duration_.[[Seconds]], _duration_.[[Milliseconds]], _duration_.[[Microseconds]], _duration_.[[Nanoseconds]], *"auto"*). + 1. Let _normSeconds_ be NormalizeTimeDuration(0, 0, _duration_.[[Seconds]], _duration_.[[Milliseconds]], _duration_.[[Microseconds]], _duration_.[[Nanoseconds]]). + 1. Return ! TemporalDurationToString(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _duration_.[[Days]], _duration_.[[Hours]], _duration_.[[Minutes]], _normSeconds_, *"auto"*). @@ -572,7 +598,8 @@

Temporal.Duration.prototype.toLocaleString ( [ _locales_ [ , _options_ ] ] ) 1. Let _duration_ be the *this* value. 1. Perform ? RequireInternalSlot(_duration_, [[InitializedTemporalDuration]]). - 1. Return ! TemporalDurationToString(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _duration_.[[Days]], _duration_.[[Hours]], _duration_.[[Minutes]], _duration_.[[Seconds]], _duration_.[[Milliseconds]], _duration_.[[Microseconds]], _duration_.[[Nanoseconds]], *"auto"*). + 1. Let _normSeconds_ be NormalizeTimeDuration(0, 0, _duration_.[[Seconds]], _duration_.[[Milliseconds]], _duration_.[[Microseconds]], _duration_.[[Nanoseconds]]). + 1. Return ! TemporalDurationToString(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _duration_.[[Days]], _duration_.[[Hours]], _duration_.[[Minutes]], _normSeconds_, *"auto"*). @@ -841,6 +868,93 @@

Partial Duration Records

+ +

Normalized Time Duration Records

+

+ A Normalized Time Duration Record is a Record value used to represent the portion of a Temporal.Duration object that deals with time units, but as a combined value. + Normalized Time Duration Records are produced by the abstract operation NormalizeTimeDuration, among others. +

+

+ Normalized Time Duration Records have the fields listed in . +

+ + + + + + + + + + + + +
Field NameValueMeaning
[[TotalNanoseconds]] + an integer in the inclusive interval from -maxTimeDuration to maxTimeDuration, where + + maxTimeDuration = 253 × 109 - 1 = 9,007,199,254,740,991,999,999,999 + + + The number of nanoseconds in the duration. +
+
+
+ + +

Normalized Duration Records

+

+ A Normalized Duration Record is a Record value used to represent the combination of a Date Duration Record with a Normalized Time Duration Record. + Such Records are used by operations that deal with both date and time portions of durations, such as RoundDuration. +

+

+ Normalized Duration Records have the fields listed in . +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field NameValueMeaning
[[Years]]a float64-representable integer + The number of years in the duration. +
[[Months]]a float64-representable integer + The number of months in the duration. +
[[Weeks]]a float64-representable integer + The number of weeks in the duration. +
[[Days]]a float64-representable integer + The number of days in the duration. +
[[NormalizedTime]]a Normalized Time Duration Record + The time portion of the duration. +
+
+
+

CreateDurationRecord ( @@ -854,14 +968,14 @@

_milliseconds_: an integer, _microseconds_: an integer, _nanoseconds_: an integer, - ): either a normal completion containing a Duration Record, or a throw completion + ): a Duration Record

description
- 1. If ! IsValidDuration(_years_, _months_, _weeks_, _days_, _hours_, _minutes_, _seconds_, _milliseconds_, _microseconds_, _nanoseconds_) is *false*, throw a *RangeError* exception. + 1. Assert: ! IsValidDuration(_years_, _months_, _weeks_, _days_, _hours_, _minutes_, _seconds_, _milliseconds_, _microseconds_, _nanoseconds_) is *true*. 1. Return the Record { [[Years]]: ℝ(𝔽(_years_)), [[Months]]: ℝ(𝔽(_months_)), @@ -931,6 +1045,60 @@

+ +

+ CreateNormalizedDurationRecord ( + _years_: an integer, + _months_: an integer, + _weeks_: an integer, + _days_: an integer, + _norm_: a Normalized Time Duration Record, + ): either a normal completion containing a Normalized Duration Record, or a throw completion +

+
+
description
+
+
+ + 1. Let _dateDurationRecord_ be ? CreateDateDurationRecord(_years_, _months_, _weeks_, _days_). + 1. Let _dateSign_ be DurationSign(_dateDurationRecord_.[[Years]], _dateDurationRecord_.[[Months]], _dateDurationRecord_.[[Weeks]], _dateDurationRecord_.[[Days]], 0, 0, 0, 0, 0, 0). + 1. Let _timeSign_ be NormalizedTimeDurationSign(_norm_). + 1. If _dateSign_ ≠ 0 and _timeSign_ ≠ 0 and _dateSign_ ≠ _timeSign_, throw a *RangeError* exception. + 1. Return the Record { + [[Years]]: _dateDurationRecord_.[[Years]], + [[Months]]: _dateDurationRecord_.[[Months]], + [[Weeks]]: _dateDurationRecord_.[[Weeks]], + [[Days]]: _dateDurationRecord_.[[Days]], + [[NormalizedTime]]: _norm_ + }. + +
+ + +

+ CombineDateAndNormalizedTimeDuration ( + _dateDurationRecord_: a Date Duration Record, + _norm_: a Normalized Time Duration Record, + ): either a normal completion containing a Normalized Duration Record, or a throw completion +

+
+
description
+
+
+ + 1. Let _dateSign_ be DurationSign(_dateDurationRecord_.[[Years]], _dateDurationRecord_.[[Months]], _dateDurationRecord_.[[Weeks]], _dateDurationRecord_.[[Days]], 0, 0, 0, 0, 0, 0). + 1. Let _timeSign_ be NormalizedTimeDurationSign(_norm_). + 1. If _dateSign_ ≠ 0 and _timeSign_ ≠ 0 and _dateSign_ ≠ _timeSign_, throw a *RangeError* exception. + 1. Return the Record { + [[Years]]: _dateDurationRecord_.[[Years]], + [[Months]]: _dateDurationRecord_.[[Months]], + [[Weeks]]: _dateDurationRecord_.[[Weeks]], + [[Days]]: _dateDurationRecord_.[[Days]], + [[NormalizedTime]]: _norm_ + }. + +
+

ToTemporalDuration ( @@ -964,7 +1132,7 @@

1. If _temporalDurationLike_ is not a String, throw a *TypeError* exception. 1. Return ? ParseTemporalDurationString(_temporalDurationLike_). 1. If _temporalDurationLike_ has an [[InitializedTemporalDuration]] internal slot, then - 1. Return ! CreateDurationRecord(_temporalDurationLike_.[[Years]], _temporalDurationLike_.[[Months]], _temporalDurationLike_.[[Weeks]], _temporalDurationLike_.[[Days]], _temporalDurationLike_.[[Hours]], _temporalDurationLike_.[[Minutes]], _temporalDurationLike_.[[Seconds]], _temporalDurationLike_.[[Milliseconds]], _temporalDurationLike_.[[Microseconds]], _temporalDurationLike_.[[Nanoseconds]]). + 1. Return CreateDurationRecord(_temporalDurationLike_.[[Years]], _temporalDurationLike_.[[Months]], _temporalDurationLike_.[[Weeks]], _temporalDurationLike_.[[Days]], _temporalDurationLike_.[[Hours]], _temporalDurationLike_.[[Minutes]], _temporalDurationLike_.[[Seconds]], _temporalDurationLike_.[[Milliseconds]], _temporalDurationLike_.[[Microseconds]], _temporalDurationLike_.[[Nanoseconds]]). 1. Let _result_ be a new Duration Record with each field set to 0. 1. Let _partial_ be ? ToTemporalPartialDurationRecord(_temporalDurationLike_). 1. If _partial_.[[Years]] is not *undefined*, set _result_.[[Years]] to _partial_.[[Years]]. @@ -1167,21 +1335,23 @@

- +

- TotalDurationNanoseconds ( + NormalizeTimeDuration ( _hours_: an integer, _minutes_: an integer, _seconds_: an integer, _milliseconds_: an integer, _microseconds_: an integer, _nanoseconds_: an integer, - ): an integer + ): a Normalized Time Duration Record

description
- It computes an integer number of nanoseconds from the given units. + From the given units, it computes a normalized time duration consisting of whole seconds, and subseconds expressed in nanoseconds. + The normalized time duration can be stored losslessly in two 64-bit floating point numbers. + Alternatively, _normalizedSeconds_ × 109 + _subseconds_ can be stored as a 96-bit integer.
@@ -1189,33 +1359,267 @@

1. Set _seconds_ to _seconds_ + _minutes_ × 60. 1. Set _milliseconds_ to _milliseconds_ + _seconds_ × 1000. 1. Set _microseconds_ to _microseconds_ + _milliseconds_ × 1000. - 1. Return _nanoseconds_ + _microseconds_ × 1000. + 1. Set _nanoseconds_ to _nanoseconds_ + _microseconds_ × 1000. + 1. Assert: abs(_nanoseconds_) ≤ maxTimeDuration. + 1. Return the Record { [[TotalNanoseconds]]: _nanoseconds_ }. + + + + +

+ NormalizedTimeDurationAbs ( + _d_: a Normalized Time Duration Record, + ): a Normalized Time Duration Record +

+
+
description
+
It returns a new normalized time duration that is the absolute value of _d_.
+
+ + 1. Return the Record { [[TotalNanoseconds]]: abs(_d_.[[TotalNanoseconds]]) }. + +
+ + +

+ AddNormalizedTimeDuration ( + _one_: a Normalized Time Duration Record, + _two_: a Normalized Time Duration Record, + ): either a normal completion containing a Normalized Time Duration Record, or a throw completion +

+
+
description
+
It returns a normalized time duration that is the sum of _one_ and _two_, throwing an exception if the result is greater than the maximum normalized time duration.
+
+ + 1. Let _result_ be _one_.[[TotalNanoseconds]] + _two_.[[TotalNanoseconds]]. + 1. If abs(_result_) > maxTimeDuration, throw a *RangeError* exception. + 1. Return the Record { [[TotalNanoseconds]]: _result_ }. + +
+ + +

+ Add24HourDaysToNormalizedTimeDuration ( + _d_: a Normalized Time Duration Record, + _days_: an integer, + ): either a normal completion containing a Normalized Time Duration Record, or a throw completion +

+
+
description
+
+ It returns a normalized time duration that is the sum of _d_ and the number of 24-hour days indicated by _days_, throwing an exception if the result is greater than the maximum normalized time duration. + This operation should not be used when adding days relative to a Temporal.ZonedDateTime, since the days may not be 24 hours long. +
+
+ + 1. Let _result_ be _d_.[[TotalNanoseconds]] + _days_ × nsPerDay. + 1. If abs(_result_) > maxTimeDuration, throw a *RangeError* exception. + 1. Return the Record { [[TotalNanoseconds]]: _result_ }. + +
+ + +

+ AddNormalizedTimeDurationToEpochNanoseconds ( + _d_: a Normalized Time Duration Record, + _epochNs_: a BigInt, + ): a BigInt +

+
+
description
+
+ It adds a normalized time duration _d_ to an exact time in nanoseconds since the epoch, _epochNs_, and returns a new exact time. + The returned exact time is not required to be valid according to IsValidEpochNanoseconds. +
+
+ + 1. Return _epochNs_ + ℤ(_d_.[[TotalNanoseconds]]). + +
+ + +

+ CompareNormalizedTimeDuration ( + _one_: a Normalized Time Duration Record, + _two_: a Normalized Time Duration Record, + ): -1, 0, or 1 +

+
+
description
+
It performs a comparison of two Normalized Time Duration Records.
+
+ + 1. If _one_.[[TotalNanoseconds]] > _two_.[[TotalNanoseconds]], return 1. + 1. If _one_.[[TotalNanoseconds]] < _two_.[[TotalNanoseconds]], return -1. + 1. Return 0. + +
+ + +

+ DivideNormalizedTimeDuration ( + _d_: a Normalized Time Duration Record, + _divisor_: an integer, + ): a mathematical value +

+
+
description
+
It divides the total number of nanoseconds in the normalized time duration _d_ by _divisor_.
+
+ + 1. Assert: _divisor_ ≠ 0. + 1. NOTE: The following step cannot be implemented directly using floating-point arithmetic when 𝔽(_d_.[[TotalNanoseconds]]) is not a safe integer. The division can be implemented in C++ with the `__float128` type if the compiler supports it, or with software emulation such as in the SoftFP library. + 1. Return _d_.[[TotalNanoseconds]] / _divisor_. + +
+ + +

+ NormalizedTimeDurationFromEpochNanosecondsDifference ( + _one_: a BigInt, + _two_: a BigInt, + ): a Normalized Time Duration Record +

+
+
description
+
It creates a Normalized Time Duration Record with the difference between two exact times in nanoseconds since the epoch, which must not be greater than the maximum normalized time duration.
+
+ + 1. Let _result_ be ℝ(_one_) - ℝ(_two_). + 1. Assert: abs(_result_) ≤ maxTimeDuration. + 1. Return the Record { [[TotalNanoseconds]]: _result_ }. + +
+ + +

+ NormalizedTimeDurationIsZero ( + _d_: a Normalized Time Duration Record, + ): a Boolean +

+
+
description
+
It returns *true* if _d_ is a zero duration, *false* otherwise.
+
+ + 1. If _d_.[[TotalNanoseconds]] = 0, return *true*. + 1. Return *false*. + +
+ + +

+ RoundNormalizedTimeDurationToIncrement ( + _d_: a Normalized Time Duration Record, + _increment_: an integer, + _roundingMode_: *"ceil"*, *"floor"*, *"expand"*, *"trunc"*, *"halfCeil"*, *"halfFloor"*, *"halfExpand"*, *"halfTrunc"*, or *"halfEven"*, + ): a Normalized Time Duration Record +

+
+
description
+
It rounds the total number of nanoseconds in the normalized time duration _d_ to the nearest multiple of _increment_, up or down according to _roundingMode_.
+
+ + 1. Let _rounded_ be RoundNumberToIncrement(_d_.[[TotalNanoseconds]], _increment_, _roundingMode_). + 1. Return the Record { [[TotalNanoseconds]]: _rounded_ }. + +
+ + +

+ NormalizedTimeDurationSeconds ( + _d_: a Normalized Time Duration Record, + ): an integer in the interval from -253 (exclusive) to 253 (exclusive) +

+
+
description
+
It returns the integer number of seconds in _d_.
+
+ + 1. Return truncate(_d_.[[TotalNanoseconds]] / 109). + +
+ + +

+ NormalizedTimeDurationSign ( + _d_: a Normalized Time Duration Record, + ): -1, 0, or 1 +

+
+
description
+
It returns 0 if the duration is zero, or ±1 depending on the sign of the duration.
+
+ + 1. If _d_.[[TotalNanoseconds]] < 0, return -1. + 1. If _d_.[[TotalNanoseconds]] > 0, return 1. + 1. Return 0. + +
+ + +

+ NormalizedTimeDurationSubseconds ( + _d_: a Normalized Time Duration Record, + ): an integer in the interval from -109 (exclusive) to 109 (exclusive) +

+
+
description
+
It returns the integer number of nanoseconds in the subsecond part of _d_.
+
+ + 1. Return remainder(_d_.[[TotalNanoseconds]], 109). + +
+ + +

+ SubtractNormalizedTimeDuration ( + _one_: a Normalized Time Duration Record, + _two_: a Normalized Time Duration Record, + ): either a normal completion containing a Normalized Time Duration Record, or a throw completion +

+
+
description
+
It returns a normalized time duration that is the difference between _one_ and _two_, throwing an exception if the result is greater than the maximum normalized time duration.
+
+ + 1. Let _result_ be _one_.[[TotalNanoseconds]] - _two_.[[TotalNanoseconds]]. + 1. If abs(_result_) > maxTimeDuration, throw a *RangeError* exception. + 1. Return the Record { [[TotalNanoseconds]]: _result_ }. + +
+ + +

+ ZeroTimeDuration ( + ): a Normalized Time Duration Record +

+
+
description
+
It returns a normalized time duration of zero length.
+
+ + 1. Return the Record { [[TotalNanoseconds]]: 0 }.

BalanceTimeDuration ( - _days_: an integer, - _hours_: an integer, - _minutes_: an integer, - _seconds_: an integer, - _milliseconds_: an integer, - _microseconds_: an integer, - _nanoseconds_: an integer, + _norm_: a Normalized Time Duration Record, _largestUnit_: a String, - ): either a normal completion containing a Time Duration Record, or an abrupt completion + ): a Time Duration Record

description
-
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_. If the Number value for any unit is infinite, it returns abruptly with a *RangeError*.
+
It converts a normalized time duration into a time duration with separated units, up to _largestUnit_.
- 1. Set _hours_ to _hours_ + _days_ × 24. - 1. Set _nanoseconds_ to TotalDurationNanoseconds(_hours_, _minutes_, _seconds_, _milliseconds_, _microseconds_, _nanoseconds_). - 1. Set _days_, _hours_, _minutes_, _seconds_, _milliseconds_, and _microseconds_ to 0. - 1. If _nanoseconds_ < 0, let _sign_ be -1; else, let _sign_ be 1. - 1. Set _nanoseconds_ to abs(_nanoseconds_). + 1. Let _days_, _hours_, _minutes_, _seconds_, _milliseconds_, and _microseconds_ be 0. + 1. Let _sign_ be NormalizedTimeDurationSign(_norm_). + 1. Let _nanoseconds_ be NormalizedTimeDurationAbs(_norm_).[[TotalNanoseconds]]. 1. If _largestUnit_ is *"year"*, *"month"*, *"week"*, or *"day"*, then 1. Set _microseconds_ to floor(_nanoseconds_ / 1000). 1. Set _nanoseconds_ to _nanoseconds_ modulo 1000. @@ -1267,7 +1671,7 @@

1. Else, 1. Assert: _largestUnit_ is *"nanosecond"*. 1. NOTE: When _largestUnit_ is *"millisecond"*, *"microsecond"*, or *"nanosecond"*, _milliseconds_, _microseconds_, or _nanoseconds_ may be an unsafe integer. In this case, care must be taken when implementing the calculation using floating point arithmetic. It can be implemented in C++ using `std::fma()`. String manipulation will also give an exact result, since the multiplication is by a power of 10. - 1. Return ? CreateTimeDurationRecord(_days_ × _sign_, _hours_ × _sign_, _minutes_ × _sign_, _seconds_ × _sign_, _milliseconds_ × _sign_, _microseconds_ × _sign_, _nanoseconds_ × _sign_). + 1. Return ! CreateTimeDurationRecord(_days_ × _sign_, _hours_ × _sign_, _minutes_ × _sign_, _seconds_ × _sign_, _milliseconds_ × _sign_, _microseconds_ × _sign_, _nanoseconds_ × _sign_). @@ -1275,16 +1679,11 @@

BalanceTimeDurationRelative ( _days_: an integer, - _hours_: an integer, - _minutes_: an integer, - _seconds_: an integer, - _milliseconds_: an integer, - _microseconds_: an integer, - _nanoseconds_: an integer, + _norm_: a Normalized Time Duration Record, _largestUnit_: a String, _zonedRelativeTo_: a Temporal.ZonedDateTime, _timeZoneRec_: a Time Zone Methods Record, - optional _precalculatedPlainDateTime_: a Temporal.PlainDateTime or *undefined*, + _precalculatedPlainDateTime_: a Temporal.PlainDateTime or *undefined*, ): either a normal completion containing a Time Duration Record, or an abrupt completion

@@ -1295,24 +1694,25 @@

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. Let _startNs_ be _zonedRelativeTo_.[[Nanoseconds]]. + 1. Let _startInstant_ be ! CreateTemporalInstant(_startNs_). + 1. Let _intermediateNs_ be _startNs_. 1. If _days_ ≠ 0, then 1. If _precalculatedPlainDateTime_ is *undefined*, set _precalculatedPlainDateTime_ to ? GetPlainDateTimeFor(_timeZoneRec_, _startInstant_, *"iso8601"*). 1. Let _intermediateResult_ be ? AddDaysToZonedDateTime(_startInstant_, _precalculatedPlainDateTime_, _timeZoneRec_, *"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]]). - 1. If _nanoseconds_ = 0, return ! CreateTimeDurationRecord(0, 0, 0, 0, 0, 0, 0). + 1. Let _endNs_ be ? AddInstant(_intermediateNs_, _norm_). + 1. Set _norm_ to NormalizedTimeDurationFromEpochNanosecondsDifference(_endNs_, _startNs_). + 1. If NormalizedTimeDurationIsZero(_norm_) is *true*, return ! CreateTimeDurationRecord(0, 0, 0, 0, 0, 0, 0). 1. If _largestUnit_ is one of *"year"*, *"month"*, *"week"*, or *"day"*, then 1. If _precalculatedPlainDateTime_ is *undefined*, set _precalculatedPlainDateTime_ to ? GetPlainDateTimeFor(_timeZoneRec_, _startInstant_, *"iso8601"*). - 1. Let _result_ be ? NanosecondsToDays(_nanoseconds_, _zonedRelativeTo_, _timeZoneRec_, _precalculatedPlainDateTime_). + 1. Let _result_ be ? NormalizedTimeDurationToDays(_norm_, _zonedRelativeTo_, _timeZoneRec_, _precalculatedPlainDateTime_). 1. Set _days_ to _result_.[[Days]]. + 1. Set _norm_ to _result_.[[NormalizedTime]]. 1. Set _largestUnit_ to *"hour"*. 1. Else, 1. Set _days_ to 0. - 1. Let _balanceResult_ be ? BalanceTimeDuration(0, 0, 0, 0, 0, 0, _result_.[[Nanoseconds]], _largestUnit_). + 1. Let _balanceResult_ be BalanceTimeDuration(_norm_, _largestUnit_). 1. Return ! CreateTimeDurationRecord(_days_, _balanceResult_.[[Hours]], _balanceResult_.[[Minutes]], _balanceResult_.[[Seconds]], _balanceResult_.[[Milliseconds]], _balanceResult_.[[Microseconds]], _balanceResult_.[[Nanoseconds]]). @@ -1473,8 +1873,12 @@

1. If _zonedRelativeTo_ is *undefined* and _plainRelativeTo_ is *undefined*, then 1. If _largestUnit_ is one of *"year"*, *"month"*, or *"week"*, then 1. Throw a *RangeError* exception. - 1. Let _result_ be ? BalanceTimeDuration(_d1_ + _d2_, _h1_ + _h2_, _min1_ + _min2_, _s1_ + _s2_, _ms1_ + _ms2_, _mus1_ + _mus2_, _ns1_ + _ns2_, _largestUnit_). - 1. Return ! CreateDurationRecord(0, 0, 0, _result_.[[Days]], _result_.[[Hours]], _result_.[[Minutes]], _result_.[[Seconds]], _result_.[[Milliseconds]], _result_.[[Microseconds]], _result_.[[Nanoseconds]]). + 1. Let _norm1_ be NormalizeTimeDuration(_h1_, _min1_, _s1_, _ms1_, _mus1_, _ns1_). + 1. Let _norm2_ be NormalizeTimeDuration(_h2_, _min2_, _s2_, _ms2_, _mus2_, _ns2_). + 1. Let _normResult_ be ? AddNormalizedTimeDuration(_norm1_, _norm2_). + 1. Set _normResult_ to ? Add24HourDaysToNormalizedTimeDuration(_normResult_, _d1_ + _d2_). + 1. Let _result_ be BalanceTimeDuration(_normResult_, _largestUnit_). + 1. Return CreateDurationRecord(0, 0, 0, _result_.[[Days]], _result_.[[Hours]], _result_.[[Minutes]], _result_.[[Seconds]], _result_.[[Milliseconds]], _result_.[[Microseconds]], _result_.[[Nanoseconds]]). 1. If _plainRelativeTo_ is not *undefined*, then 1. Assert: _zonedRelativeTo_ is *undefined*. 1. Let _dateDuration1_ be ! CreateTemporalDuration(_y1_, _mon1_, _w1_, _d1_, 0, 0, 0, 0, 0, 0). @@ -1485,8 +1889,12 @@

1. Let _differenceOptions_ be OrdinaryObjectCreate(*null*). 1. Perform ! CreateDataPropertyOrThrow(_differenceOptions_, *"largestUnit"*, _dateLargestUnit_). 1. Let _dateDifference_ be ? DifferenceDate(_calendarRec_, _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. Let _norm1_ be NormalizeTimeDuration(_h1_, _min1_, _s1_, _ms1_, _mus1_, _ns1_). + 1. Let _norm2_ be NormalizeTimeDuration(_h2_, _min2_, _s2_, _ms2_, _mus2_, _ns2_). + 1. Let _norm1WithDays_ be ? Add24HourDaysToNormalizedTimeDuration(_norm1_, _dateDifference_.[[Days]]). + 1. Let _normResult_ be ? AddNormalizedTimeDuration(_norm1WithDays_, _norm2_). + 1. Let _result_ be BalanceTimeDuration(_normResult_, _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. If _largestUnit_ is *"year"*, or _largestUnit_ is *"month"*, or _largestUnit_ is *"week"*, or _largestUnit_ is *"day"*, let _startDateTimeNeeded_ be *true*; else let _startDateTimeNeeded_ be *false*. @@ -1494,12 +1902,17 @@

1. Let _startDateTime_ be ? GetPlainDateTimeFor(_timeZoneRec_, _zonedRelativeTo_.[[Nanoseconds]], _calendarRec_.[[Receiver]]). 1. Else, 1. Let _startDateTime_ be _precalculatedPlainDateTime_. - 1. Let _intermediateNs_ be ? AddZonedDateTime(_zonedRelativeTo_.[[Nanoseconds]], _timeZoneRec_, _calendarRec_, _y1_, _mon1_, _w1_, _d1_, _h1_, _min1_, _s1_, _ms1_, _mus1_, _ns1_, _startDateTime_). - 1. Let _endNs_ be ? AddZonedDateTime(_intermediateNs_, _timeZoneRec_, _calendarRec_, _y2_, _mon2_, _w2_, _d2_, _h2_, _min2_, _s2_, _ms2_, _mus2_, _ns2_). + 1. Let _norm1_ be NormalizeTimeDuration(_h1_, _min1_, _s1_, _ms1_, _mus1_, _ns1_). + 1. Let _norm2_ be NormalizeTimeDuration(_h2_, _min2_, _s2_, _ms2_, _mus2_, _ns2_). + 1. Let _intermediateNs_ be ? AddZonedDateTime(_zonedRelativeTo_.[[Nanoseconds]], _timeZoneRec_, _calendarRec_, _y1_, _mon1_, _w1_, _d1_, _norm1_, _startDateTime_). + 1. Let _endNs_ be ? AddZonedDateTime(_intermediateNs_, _timeZoneRec_, _calendarRec_, _y2_, _mon2_, _w2_, _d2_, _norm2_). 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_, _timeZoneRec_, _calendarRec_, _largestUnit_, OrdinaryObjectCreate(*null*), _startDateTime_). + 1. Let _norm_ be NormalizedTimeDurationFromEpochNanosecondsDifference(_endNs_, _zonedRelativeTo_.[[Nanoseconds]]). + 1. Let _result_ be BalanceTimeDuration(_norm_, _largestUnit_). + 1. Return CreateDurationRecord(0, 0, 0, 0, _result_.[[Hours]], _result_.[[Minutes]], _result_.[[Seconds]], _result_.[[Milliseconds]], _result_.[[Microseconds]], _result_.[[Nanoseconds]]). + 1. Let _diffResult_ be ? DifferenceZonedDateTime(_zonedRelativeTo_.[[Nanoseconds]], _endNs_, _timeZoneRec_, _calendarRec_, _largestUnit_, OrdinaryObjectCreate(*null*), _startDateTime_). + 1. Let _timeResult_ be BalanceTimeDuration(_diffResult_.[[NormalizedTime]], *"hour"*). + 1. Return CreateDurationRecord(_diffResult_.[[Years]], _diffResult_.[[Months]], _diffResult_.[[Weeks]], _diffResult_.[[Days]], _timeResult_.[[Hours]], _timeResult_.[[Minutes]], _timeResult_.[[Seconds]], _timeResult_.[[Milliseconds]], _timeResult_.[[Microseconds]], _timeResult_.[[Nanoseconds]]). @@ -1567,7 +1980,7 @@

1. Assert: TimeZoneMethodsRecordHasLookedUp(_timeZoneRec_, ~get-offset-nanoseconds-for~) is *true*. 1. Assert: TimeZoneMethodsRecordHasLookedUp(_timeZoneRec_, ~get-possible-instants-for~) is *true*. - 1. Let _intermediateNs_ be ? AddZonedDateTime(_zonedDateTime_.[[Nanoseconds]], _timeZoneRec_, _calendarRec_, _years_, _months_, _weeks_, _days_, 0, 0, 0, 0, 0, 0, _precalculatedPlainDateTime_). + 1. Let _intermediateNs_ be ? AddZonedDateTime(_zonedDateTime_.[[Nanoseconds]], _timeZoneRec_, _calendarRec_, _years_, _months_, _weeks_, _days_, ZeroTimeDuration(), _precalculatedPlainDateTime_). 1. Return ! CreateTemporalZonedDateTime(_intermediateNs_, _zonedDateTime_.[[TimeZone]], _zonedDateTime_.[[Calendar]]). @@ -1579,12 +1992,7 @@

_months_: an integer, _weeks_: an integer, _days_: an integer, - _hours_: an integer, - _minutes_: an integer, - _seconds_: an integer, - _milliseconds_: an integer, - _microseconds_: an integer, - _nanoseconds_: an integer, + _norm_: a Normalized Time Duration Record, _increment_: an integer, _unit_: a String, _roundingMode_: a String, @@ -1593,12 +2001,12 @@

optional _zonedRelativeTo_: *undefined* or a Temporal.ZonedDateTime, optional _timeZoneRec_: *undefined* or a Time Zone Methods Record, 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 + ): either a normal completion containing a Record with fields [[NormalizedDuration]] (a Normalized Duration Record) and [[Total]] (a mathematical value), or a throw completion

description
- It rounds a duration (denoted by _years_ through _nanoseconds_) according to the rounding parameters _unit_, _increment_, and _roundingMode_, and returns a Record with the Duration Record result in its [[DurationRecord]] field. + It rounds a duration (denoted by _years_ through _nanoseconds_) according to the rounding parameters _unit_, _increment_, and _roundingMode_, and returns a Record with the Normalized Duration Record result in its [[NormalizedDuration]] field. It also returns the total of the smallest unit before the rounding operation in its [[Total]] field, for use in `Temporal.Duration.prototype.total`. For rounding involving calendar units, the _relativeTo_ parameter is required.
@@ -1614,17 +2022,14 @@

1. Assert: CalendarMethodsRecordHasLookedUp(_calendarRec_, ~date-add~) is *true*. 1. Assert: CalendarMethodsRecordHasLookedUp(_calendarRec_, ~date-until~) is *true*. 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_, _calendarRec_, _timeZoneRec_, _years_, _months_, _weeks_, _days_, _precalculatedPlainDateTime_). - 1. Let _result_ be ? NanosecondsToDays(_nanoseconds_, _intermediate_, _timeZoneRec_). - 1. Let _fractionalDays_ be _days_ + _result_.[[Days]] + _result_.[[Nanoseconds]] / _result_.[[DayLength]]. + 1. Let _result_ be ? NormalizedTimeDurationToDays(_norm_, _intermediate_, _timeZoneRec_). + 1. Let _fractionalDays_ be _days_ + _result_.[[Days]] + DivideNormalizedTimeDuration(_result_.[[Remainder]], _result_.[[DayLength]]). 1. Else, - 1. Let _fractionalDays_ be _days_ + _nanoseconds_ / nsPerDay. - 1. Set _days_, _hours_, _minutes_, _seconds_, _milliseconds_, _microseconds_, and _nanoseconds_ to 0. - 1. [declared="fractionalSeconds"] Assert: _fractionalSeconds_ is not used below. + 1. Let _fractionalDays_ be _days_ + DivideNormalizedTimeDuration(_norm_, nsPerDay). + 1. Set _days_ to 0. 1. Else, - 1. Let _fractionalSeconds_ be _nanoseconds_ × 10-9 + _microseconds_ × 10-6 + _milliseconds_ × 10-3 + _seconds_. 1. Assert: _fractionalDays_ is not used below. 1. Let _total_ be ~unset~. 1. If _unit_ is *"year"*, then @@ -1656,6 +2061,7 @@

1. Set _years_ to RoundNumberToIncrement(_fractionalYears_, _increment_, _roundingMode_). 1. Set _total_ to _fractionalYears_. 1. Set _months_ and _weeks_ to 0. + 1. Set _norm_ to ZeroTimeDuration(). 1. Else if _unit_ is *"month"*, then 1. Let _yearsMonths_ be ! CreateTemporalDuration(_years_, _months_, 0, 0, 0, 0, 0, 0, 0, 0). 1. Let _yearsMonthsLater_ be ? AddDate(_calendarRec_, _plainRelativeTo_, _yearsMonths_). @@ -1685,6 +2091,7 @@

1. Set _months_ to RoundNumberToIncrement(_fractionalMonths_, _increment_, _roundingMode_). 1. Set _total_ to _fractionalMonths_. 1. Set _weeks_ to 0. + 1. Set _norm_ to ZeroTimeDuration(). 1. Else if _unit_ is *"week"*, then 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]], _calendarRec_.[[Receiver]]). @@ -1706,40 +2113,37 @@

1. Let _fractionalWeeks_ be _weeks_ + _fractionalDays_ / abs(_oneWeekDays_). 1. Set _weeks_ to RoundNumberToIncrement(_fractionalWeeks_, _increment_, _roundingMode_). 1. Set _total_ to _fractionalWeeks_. + 1. Set _norm_ to ZeroTimeDuration(). 1. Else if _unit_ is *"day"*, then 1. Set _days_ to RoundNumberToIncrement(_fractionalDays_, _increment_, _roundingMode_). 1. Set _total_ to _fractionalDays_. + 1. Set _norm_ to ZeroTimeDuration(). 1. Else if _unit_ is *"hour"*, then - 1. Let _fractionalHours_ be (_fractionalSeconds_ / 60 + _minutes_) / 60 + _hours_. - 1. Set _hours_ to RoundNumberToIncrement(_fractionalHours_, _increment_, _roundingMode_). - 1. Set _total_ to _fractionalHours_. - 1. Set _minutes_, _seconds_, _milliseconds_, _microseconds_, and _nanoseconds_ to 0. + 1. Let _divisor_ be 3.6 × 1012. + 1. Set _total_ to DivideNormalizedTimeDuration(_norm_, _divisor_). + 1. Set _norm_ to RoundNormalizedTimeDurationToIncrement(_norm_, _divisor_ × _increment_, _roundingMode_). 1. Else if _unit_ is *"minute"*, then - 1. Let _fractionalMinutes_ be _fractionalSeconds_ / 60 + _minutes_. - 1. Set _minutes_ to RoundNumberToIncrement(_fractionalMinutes_, _increment_, _roundingMode_). - 1. Set _total_ to _fractionalMinutes_. - 1. Set _seconds_, _milliseconds_, _microseconds_, and _nanoseconds_ to 0. + 1. Let _divisor_ be 6 × 1010. + 1. Set _total_ to DivideNormalizedTimeDuration(_norm_, _divisor_). + 1. Set _norm_ to RoundNormalizedTimeDurationToIncrement(_norm_, _divisor_ × _increment_, _roundingMode_). 1. Else if _unit_ is *"second"*, then - 1. Set _seconds_ to RoundNumberToIncrement(_fractionalSeconds_, _increment_, _roundingMode_). - 1. Set _total_ to _fractionalSeconds_. - 1. Set _milliseconds_, _microseconds_, and _nanoseconds_ to 0. + 1. Let _divisor_ be 109. + 1. Set _total_ to DivideNormalizedTimeDuration(_norm_, _divisor_). + 1. Set _norm_ to RoundNormalizedTimeDurationToIncrement(_norm_, _divisor_ × _increment_, _roundingMode_). 1. Else if _unit_ is *"millisecond"*, then - 1. Let _fractionalMilliseconds_ be _nanoseconds_ × 10-6 + _microseconds_ × 10-3 + _milliseconds_. - 1. Set _milliseconds_ to RoundNumberToIncrement(_fractionalMilliseconds_, _increment_, _roundingMode_). - 1. Set _total_ to _fractionalMilliseconds_. - 1. Set _microseconds_ and _nanoseconds_ to 0. + 1. Let _divisor_ be 106. + 1. Set _total_ to DivideNormalizedTimeDuration(_norm_, _divisor_). + 1. Set _norm_ to RoundNormalizedTimeDurationToIncrement(_norm_, _divisor_ × _increment_, _roundingMode_). 1. Else if _unit_ is *"microsecond"*, then - 1. Let _fractionalMicroseconds_ be _nanoseconds_ × 10-3 + _microseconds_. - 1. Set _microseconds_ to RoundNumberToIncrement(_fractionalMicroseconds_, _increment_, _roundingMode_). - 1. Set _total_ to _fractionalMicroseconds_. - 1. Set _nanoseconds_ to 0. + 1. Let _divisor_ be 103. + 1. Set _total_ to DivideNormalizedTimeDuration(_norm_, _divisor_). + 1. Set _norm_ to RoundNormalizedTimeDurationToIncrement(_norm_, _divisor_ × _increment_, _roundingMode_). 1. Else, 1. Assert: _unit_ is *"nanosecond"*. - 1. Set _total_ to _nanoseconds_. - 1. Set _nanoseconds_ to RoundNumberToIncrement(_nanoseconds_, _increment_, _roundingMode_). - 1. Let _duration_ be ? CreateDurationRecord(_years_, _months_, _weeks_, _days_, _hours_, _minutes_, _seconds_, _milliseconds_, _microseconds_, _nanoseconds_). + 1. Set _total_ to NormalizedTimeDurationSeconds(_norm_) × 109 + NormalizedTimeDurationSubseconds(_norm_). + 1. Set _norm_ to RoundNormalizedTimeDurationToIncrement(_norm_, _increment_, _roundingMode_). 1. Return the Record { - [[DurationRecord]]: _duration_, + [[NormalizedDuration]]: ? CreateNormalizedDurationRecord(_years_, _months_, _weeks_, _days_, _norm_), [[Total]]: _total_ }. @@ -1752,12 +2156,7 @@

_months_: an integer, _weeks_: an integer, _days_: an integer, - _hours_: an integer, - _minutes_: an integer, - _seconds_: an integer, - _milliseconds_: an integer, - _microseconds_: an integer, - _nanoseconds_: an integer, + _norm_: a Normalized Time Duration Record, _increment_: an integer, _unit_: a String, _roundingMode_: a String, @@ -1765,7 +2164,7 @@

_calendarRec_: a Calendar Methods Record, _timeZoneRec_: a Time Zone Methods Record, _precalculatedPlainDateTime_: a Temporal.PlainDateTime or *undefined*, - ): either a normal completion containing a Duration Record, or a throw completion + ): either a normal completion containing a Normalized Duration Record, or a throw completion

description
@@ -1777,24 +2176,20 @@

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. Return ! CreateNormalizedDurationRecord(_years_, _months_, _weeks_, _days_, _norm_). 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. - 1. Else, let _direction_ be 1. - 1. Let _dayStart_ be ? AddZonedDateTime(_zonedRelativeTo_.[[Nanoseconds]], _timeZoneRec_, _calendarRec_, _years_, _months_, _weeks_, _days_, 0, 0, 0, 0, 0, 0, _precalculatedPlainDateTime_). + 1. Let _direction_ be NormalizedTimeDurationSign(_norm_). + 1. Let _dayStart_ be ? AddZonedDateTime(_zonedRelativeTo_.[[Nanoseconds]], _timeZoneRec_, _calendarRec_, _years_, _months_, _weeks_, _days_, ZeroTimeDuration(), _precalculatedPlainDateTime_). 1. Let _dayStartInstant_ be ! CreateTemporalInstant(_dayStart_). 1. Let _dayStartDateTime_ be ? GetPlainDateTimeFor(_timeZoneRec_, _dayStartInstant_, _zonedRelativeTo_.[[Calendar]]). 1. Let _dayEnd_ be ? AddDaysToZonedDateTime(_dayStartInstant_, _dayStartDateTime_, _timeZoneRec_, _zonedRelativeTo_.[[Calendar]], _direction_).[[EpochNanoseconds]]. - 1. Let _dayLengthNs_ be ℝ(_dayEnd_ - _dayStart_). - 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 _dayLengthNs_ be NormalizedTimeDurationFromEpochNanosecondsDifference(_dayEnd_, _dayStart_). + 1. Let _oneDayLess_ be ? SubtractNormalizedTimeDuration(_norm_, _dayLengthNs_). + 1. If NormalizedTimeDurationSign(_oneDayLess_) × _direction_ < 0, then + 1. Return ! CreateNormalizedDurationRecord(_years_, _months_, _weeks_, _days_, _norm_). 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_, _calendarRec_, _timeZoneRec_, _precalculatedPlainDateTime_). - 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]]). + 1. Let _roundRecord_ be ! RoundDuration(0, 0, 0, 0, _oneDayLess_, _increment_, _unit_, _roundingMode_). + 1. Return ? CombineDateAndNormalizedTimeDuration(_adjustedDateDuration_, _roundRecord_.[[NormalizedDuration]].[[NormalizedTime]]). @@ -1807,10 +2202,7 @@

_days_: an integer, _hours_: an integer, _minutes_: an integer, - _seconds_: an integer, - _milliseconds_: an integer, - _microseconds_: an integer, - _nanoseconds_: an integer, + _normSeconds_: a Normalized Time Duration Record, _precision_: an integer between 0 and 9 inclusive, or *"auto"*, )

@@ -1819,13 +2211,7 @@

It returns a String which is the ISO 8601 representation of the duration denoted by _years_ through _nanoseconds_, with the number of decimal places in the seconds value controlled by _precision_.

- 1. Let _sign_ be ! DurationSign(_years_, _months_, _weeks_, _days_, _hours_, _minutes_, _seconds_, _milliseconds_, _microseconds_, _nanoseconds_). - 1. Set _microseconds_ to _microseconds_ + truncate(_nanoseconds_ / 1000). - 1. Set _nanoseconds_ to remainder(_nanoseconds_, 1000). - 1. Set _milliseconds_ to _milliseconds_ + truncate(_microseconds_ / 1000). - 1. Set _microseconds_ to remainder(_microseconds_, 1000). - 1. Set _seconds_ to _seconds_ + truncate(_milliseconds_ / 1000). - 1. Set _milliseconds_ to remainder(_milliseconds_, 1000). + 1. Let _sign_ be ! DurationSign(_years_, _months_, _weeks_, _days_, _hours_, _minutes_, NormalizedTimeDurationSeconds(_normSeconds_), 0, 0, NormalizedTimeDurationSubseconds(_normSeconds_)). 1. Let _datePart_ be *""*. 1. If _years_ is not 0, then 1. Set _datePart_ to the string concatenation of abs(_years_) formatted as a decimal number and the code unit 0x0059 (LATIN CAPITAL LETTER Y). @@ -1840,14 +2226,11 @@

1. Set _timePart_ to the string concatenation of abs(_hours_) formatted as a decimal number and the code unit 0x0048 (LATIN CAPITAL LETTER H). 1. If _minutes_ is not 0, then 1. Set _timePart_ to the string concatenation of _timePart_, abs(_minutes_) formatted as a decimal number, and the code unit 0x004D (LATIN CAPITAL LETTER M). - 1. Let _nonzeroSecondsAndLower_ be *false*. - 1. If _seconds_ ≠ 0, or _milliseconds_ ≠ 0, or _microseconds_ ≠ 0, or _nanoseconds_ ≠ 0, set _nonzeroSecondsAndLower_ to *true*. 1. Let _zeroMinutesAndHigher_ be *false*. 1. If _years_ = 0, and _months_ = 0, and _weeks_ = 0, and _days_ = 0, and _hours_ = 0, and _minutes_ = 0, set _zeroMinutesAndHigher_ to *true*. - 1. If _nonzeroSecondsAndLower_ is *true*, or _zeroMinutesAndHigher_ is *true*, or _precision_ is not *"auto"*, then - 1. Let _secondsPart_ be abs(_seconds_) formatted as a decimal number. - 1. Let _subSecondNanoseconds_ be abs(_milliseconds_) × 106 + abs(_microseconds_) × 103 + abs(_nanoseconds_). - 1. Let _subSecondsPart_ be FormatFractionalSeconds(_subSecondNanoseconds_, _precision_). + 1. If NormalizedTimeDurationIsZero(_normSeconds_) is *false*, or _zeroMinutesAndHigher_ is *true*, or _precision_ is not *"auto"*, then + 1. Let _secondsPart_ be abs(NormalizedTimeDurationSeconds(_normSeconds_)) formatted as a decimal number. + 1. Let _subSecondsPart_ be FormatFractionalSeconds(abs(NormalizedTimeDurationSubseconds(_normSeconds_)), _precision_). 1. Set _timePart_ to the string concatenation of _timePart_, _secondsPart_, _subSecondsPart_, and the code unit 0x0053 (LATIN CAPITAL LETTER S). 1. Let _signPart_ be the code unit 0x002D (HYPHEN-MINUS) if _sign_ < 0, and otherwise the empty String. 1. Let _result_ be the string concatenation of _signPart_, the code unit 0x0050 (LATIN CAPITAL LETTER P) and _datePart_. diff --git a/spec/instant.html b/spec/instant.html index e83f23bb96..30927fb4d7 100644 --- a/spec/instant.html +++ b/spec/instant.html @@ -552,12 +552,7 @@

AddInstant ( _epochNanoseconds_: a BigInt value, - _hours_: an integer, - _minutes_: an integer, - _seconds_: an integer, - _milliseconds_: an integer, - _microseconds_: an integer, - _nanoseconds_: an integer, + _norm_: a Normalized Time Duration Record, ): either a normal completion containing a BigInt, or a throw completion

@@ -565,13 +560,8 @@

It adds a duration in various time units to a number of nanoseconds since the epoch.

- 1. Let _result_ be _epochNanoseconds_ + ℤ(_nanoseconds_) + - ℤ(_microseconds_) × *1000* + - ℤ(_milliseconds_) × ℤ(106) + - ℤ(_seconds_) × ℤ(109) + - ℤ(_minutes_) × *60* × ℤ(109) + - ℤ(_hours_) × *3600* × ℤ(109). - 1. If IsValidEpochNanoseconds(_result_) is *false*, throw a *RangeError* exception. + 1. Let _result_ be AddNormalizedTimeDurationToEpochNanoseconds(_norm_, _epochNanoseconds_). + 1. If ! IsValidEpochNanoseconds(_result_) is *false*, throw a *RangeError* exception. 1. Return _result_. @@ -583,25 +573,19 @@

_ns2_: a BigInt, _roundingIncrement_: a positive integer, _smallestUnit_: a String, - _largestUnit_: a String, _roundingMode_: a String, - ): a Time Duration Record + ): a Normalized Time Duration Record

description
-
It computes the difference between two exact times _ns1_ and _ns2_ expressed in nanoseconds since the epoch, and rounds the result according to the parameters _roundingIncrement_, _smallestUnit_, _largestUnit_, and _roundingMode_.
+
It computes the difference between two exact times _ns1_ and _ns2_ expressed in nanoseconds since the epoch, and rounds the result according to the parameters _roundingIncrement_, _smallestUnit_, and _roundingMode_.
- 1. Let _difference_ be ℝ(_ns2_) - ℝ(_ns1_). - 1. Let _nanoseconds_ be remainder(_difference_, 1000). - 1. Let _microseconds_ be remainder(truncate(_difference_ / 1000), 1000). - 1. Let _milliseconds_ be remainder(truncate(_difference_ / 106), 1000). - 1. Let _seconds_ be truncate(_difference_ / 109). + 1. Let _difference_ be NormalizedTimeDurationFromEpochNanosecondsDifference(_ns2_, _ns1_). 1. If _smallestUnit_ is *"nanosecond"* and _roundingIncrement_ is 1, then - 1. Return ! BalanceTimeDuration(0, 0, 0, _seconds_, _milliseconds_, _microseconds_, _nanoseconds_, _largestUnit_). - 1. Let _roundResult_ be ! RoundDuration(0, 0, 0, 0, 0, 0, _seconds_, _milliseconds_, _microseconds_, _nanoseconds_, _roundingIncrement_, _smallestUnit_, _roundingMode_). - 1. Assert: _roundResult_.[[Days]] is 0. - 1. Return ! BalanceTimeDuration(0, _roundResult_.[[Hours]], _roundResult_.[[Minutes]], _roundResult_.[[Seconds]], _roundResult_.[[Milliseconds]], _roundResult_.[[Microseconds]], _roundResult_.[[Nanoseconds]], _largestUnit_). + 1. Return _difference_. + 1. Let _roundRecord_ be ! RoundDuration(0, 0, 0, 0, _difference_, _roundingIncrement_, _smallestUnit_, _roundingMode_). + 1. Return _roundRecord_.[[NormalizedDuration]].[[NormalizedTime]]. @@ -672,7 +656,8 @@

1. Set _other_ to ? ToTemporalInstant(_other_). 1. Let _resolvedOptions_ be ? SnapshotOwnProperties(? GetOptionsObject(_options_), *null*). 1. Let _settings_ be ? GetDifferenceSettings(_operation_, _resolvedOptions_, ~time~, « », *"nanosecond"*, *"second"*). - 1. Let _result_ be DifferenceInstant(_instant_.[[Nanoseconds]], _other_.[[Nanoseconds]], _settings_.[[RoundingIncrement]], _settings_.[[SmallestUnit]], _settings_.[[LargestUnit]], _settings_.[[RoundingMode]]). + 1. Let _norm_ be DifferenceInstant(_instant_.[[Nanoseconds]], _other_.[[Nanoseconds]], _settings_.[[RoundingIncrement]], _settings_.[[SmallestUnit]], _settings_.[[RoundingMode]]). + 1. Let _result_ be BalanceTimeDuration(_norm_, _settings_.[[LargestUnit]]). 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]]). @@ -695,7 +680,8 @@

1. If _duration_.[[Months]] is not 0, throw a *RangeError* exception. 1. If _duration_.[[Weeks]] is not 0, throw a *RangeError* exception. 1. If _duration_.[[Years]] is not 0, throw a *RangeError* exception. - 1. Let _ns_ be ? AddInstant(_instant_.[[Nanoseconds]], _sign_ × _duration_.[[Hours]], _sign_ × _duration_.[[Minutes]], _sign_ × _duration_.[[Seconds]], _sign_ × _duration_.[[Milliseconds]], _sign_ × _duration_.[[Microseconds]], _sign_ × _duration_.[[Nanoseconds]]). + 1. Let _norm_ be NormalizeTimeDuration(_sign_ × _duration_.[[Hours]], _sign_ × _duration_.[[Minutes]], _sign_ × _duration_.[[Seconds]], _sign_ × _duration_.[[Milliseconds]], _sign_ × _duration_.[[Microseconds]], _sign_ × _duration_.[[Nanoseconds]]). + 1. Let _ns_ be ? AddInstant(_instant_.[[Nanoseconds]], _norm_). 1. Return ! CreateTemporalInstant(_ns_). diff --git a/spec/intl.html b/spec/intl.html index 5d9c8b6e3e..9c5f55a1ba 100644 --- a/spec/intl.html +++ b/spec/intl.html @@ -2312,11 +2312,12 @@

Temporal.Calendar.prototype.dateAdd ( _date_, _duration_ [ , _options_ ] ) diff --git a/spec/plaindate.html b/spec/plaindate.html index 4ac1243fda..9b25f40316 100644 --- a/spec/plaindate.html +++ b/spec/plaindate.html @@ -1011,7 +1011,8 @@

1. If _duration_.[[Years]] ≠ 0, or _duration_.[[Months]] ≠ 0, or _duration_.[[Weeks]] ≠ 0, then 1. Return ? CalendarDateAdd(_calendarRec_, _plainDate_, _duration_, _options_). 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 _norm_ be NormalizeTimeDuration(_duration_.[[Hours]], _duration_.[[Minutes]], _duration_.[[Seconds]], _duration_.[[Milliseconds]], _duration_.[[Microseconds]], _duration_.[[Nanoseconds]]). + 1. Let _days_ be _duration_.[[Days]] + BalanceTimeDuration(_norm_, *"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]], _calendarRec_.[[Receiver]]). @@ -1056,8 +1057,8 @@

1. Let _result_ be ? DifferenceDate(_calendarRec_, _temporalDate_, _other_, _resolvedOptions_). 1. If _settings_.[[SmallestUnit]] is *"day"* and _settings_.[[RoundingIncrement]] = 1, let _roundingGranularityIsNoop_ be *true*; else let _roundingGranularityIsNoop_ be *false*. 1. If _roundingGranularityIsNoop_ is *false*, 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_, _calendarRec_). - 1. Let _roundResult_ be _roundRecord_.[[DurationRecord]]. + 1. Let _roundRecord_ be ? RoundDuration(_result_.[[Years]], _result_.[[Months]], _result_.[[Weeks]], _result_.[[Days]], ZeroTimeDuration(), _settings_.[[RoundingIncrement]], _settings_.[[SmallestUnit]], _settings_.[[RoundingMode]], _temporalDate_, _calendarRec_). + 1. Let _roundResult_ be _roundRecord_.[[NormalizedDuration]]. 1. Set _result_ to ? BalanceDateDurationRelative(_roundResult_.[[Years]], _roundResult_.[[Months]], _roundResult_.[[Weeks]], _roundResult_.[[Days]], _settings_.[[LargestUnit]], _settings_.[[SmallestUnit]], _temporalDate_, _calendarRec_). 1. Return ! CreateTemporalDuration(_sign_ × _result_.[[Years]], _sign_ × _result_.[[Months]], _sign_ × _result_.[[Weeks]], _sign_ × _result_.[[Days]], 0, 0, 0, 0, 0, 0). diff --git a/spec/plaindatetime.html b/spec/plaindatetime.html index 6ebebe3cd0..7743e5266d 100644 --- a/spec/plaindatetime.html +++ b/spec/plaindatetime.html @@ -1149,12 +1149,7 @@

_months_: an integer, _weeks_: an integer, _days_: an integer, - _hours_: an integer, - _minutes_: an integer, - _seconds_: an integer, - _milliseconds_: an integer, - _microseconds_: an integer, - _nanoseconds_: an integer, + _norm_: a Normalized Time Duration Record, _options_: an Object or *undefined*, ): either a normal completion containing an ISO Date-Time Record, or a throw completion

@@ -1166,7 +1161,7 @@

1. Assert: ISODateTimeWithinLimits(_year_, _month_, _day_, _hour_, _minute_, _second_, _millisecond_, _microsecond_, _nanosecond_) is *true*. - 1. Let _timeResult_ be AddTime(_hour_, _minute_, _second_, _millisecond_, _microsecond_, _nanosecond_, _hours_, _minutes_, _seconds_, _milliseconds_, _microseconds_, _nanoseconds_). + 1. Let _timeResult_ be AddTime(_hour_, _minute_, _second_, _millisecond_, _microsecond_, _nanosecond_, _norm_). 1. Let _datePart_ be ! CreateTemporalDate(_year_, _month_, _day_, _calendarRec_.[[Receiver]]). 1. Let _dateDuration_ be ? CreateTemporalDuration(_years_, _months_, _weeks_, _days_ + _timeResult_.[[Days]], 0, 0, 0, 0, 0, 0). 1. Let _addedDate_ be ? AddDate(_calendarRec_, _datePart_, _dateDuration_, _options_). @@ -1249,7 +1244,7 @@

_calendarRec_: a Calendar Methods Record, _largestUnit_: a String, _options_: an Object, - ): either a normal completion containing a Duration Record, or a throw completion + ): either a normal completion containing a Normalized Duration Record, or a throw completion

description
@@ -1263,21 +1258,20 @@

1. Assert: ISODateTimeWithinLimits(_y1_, _mon1_, _d1_, _h1_, _min1_, _s1_, _ms1_, _mus1_, _ns1_) is *true*. 1. Assert: ISODateTimeWithinLimits(_y2_, _mon2_, _d2_, _h2_, _min2_, _s2_, _ms2_, _mus2_, _ns2_) is *true*. 1. Assert: If _y1_ ≠ _y2_, and _mon1_ ≠ _mon2_, and _d1_ ≠ _d2_, and LargerOfTwoTemporalUnits(_largestUnit_, *"day"*) is not *"day"*, CalendarMethodsRecordHasLookedUp(_calendarRec_, ~date-until~) is *true*. - 1. Let _timeDifference_ be ! DifferenceTime(_h1_, _min1_, _s1_, _ms1_, _mus1_, _ns1_, _h2_, _min2_, _s2_, _ms2_, _mus2_, _ns2_). - 1. Let _timeSign_ be ! DurationSign(0, 0, 0, 0, _timeDifference_.[[Hours]], _timeDifference_.[[Minutes]], _timeDifference_.[[Seconds]], _timeDifference_.[[Milliseconds]], _timeDifference_.[[Microseconds]], _timeDifference_.[[Nanoseconds]]). + 1. Let _timeDuration_ be ! DifferenceTime(_h1_, _min1_, _s1_, _ms1_, _mus1_, _ns1_, _h2_, _min2_, _s2_, _ms2_, _mus2_, _ns2_). + 1. Let _timeSign_ be NormalizedTimeDurationSign(_timeDuration_). 1. Let _dateSign_ be ! CompareISODate(_y2_, _mon2_, _d2_, _y1_, _mon1_, _d1_). 1. Let _adjustedDate_ be CreateISODateRecord(_y1_, _mon1_, _d1_). 1. If _timeSign_ is -_dateSign_, then 1. Set _adjustedDate_ to BalanceISODate(_adjustedDate_.[[Year]], _adjustedDate_.[[Month]], _adjustedDate_.[[Day]] - _timeSign_). - 1. Set _timeDifference_ to ! BalanceTimeDuration(-_timeSign_, _timeDifference_.[[Hours]], _timeDifference_.[[Minutes]], _timeDifference_.[[Seconds]], _timeDifference_.[[Milliseconds]], _timeDifference_.[[Microseconds]], _timeDifference_.[[Nanoseconds]], _largestUnit_). + 1. Set _timeDuration_ to ? Add24HourDaysToNormalizedTimeDuration(_timeDuration_, -_timeSign_). 1. Let _date1_ be ! CreateTemporalDate(_adjustedDate_.[[Year]], _adjustedDate_.[[Month]], _adjustedDate_.[[Day]], _calendarRec_.[[Receiver]]). 1. Let _date2_ be ! CreateTemporalDate(_y2_, _mon2_, _d2_, _calendarRec_.[[Receiver]]). 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 ? DifferenceDate(_calendarRec_, _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]]). + 1. Return ? CreateNormalizedDurationRecord(_dateDifference_.[[Years]], _dateDifference_.[[Months]], _dateDifference_.[[Weeks]], _dateDifference_.[[Days]], _timeDuration_). @@ -1305,16 +1299,20 @@

1. If _datePartsIdentical_ is *true*, 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 _calendarRec_ be ? CreateCalendarMethodsRecord(_dateTime_.[[Calendar]], « ~date-add~, ~date-until~ »). - 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]], _calendarRec_, _settings_.[[LargestUnit]], _resolvedOptions_). + 1. Let _result_ 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]], _calendarRec_, _settings_.[[LargestUnit]], _resolvedOptions_). 1. If _settings_.[[SmallestUnit]] is *"nanosecond"* and _settings_.[[RoundingIncrement]] = 1, let _roundingGranularityIsNoop_ be *true*; else let _roundingGranularityIsNoop_ be *false*. - 1. If _roundingGranularityIsNoop_ is *true*, 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]]). - 1. Let _relativeTo_ be ! CreateTemporalDate(_dateTime_.[[ISOYear]], _dateTime_.[[ISOMonth]], _dateTime_.[[ISODay]], _dateTime_.[[Calendar]]). - 1. Let _roundRecord_ be ? RoundDuration(_diff_.[[Years]], _diff_.[[Months]], _diff_.[[Weeks]], _diff_.[[Days]], _diff_.[[Hours]], _diff_.[[Minutes]], _diff_.[[Seconds]], _diff_.[[Milliseconds]], _diff_.[[Microseconds]], _diff_.[[Nanoseconds]], _settings_.[[RoundingIncrement]], _settings_.[[SmallestUnit]], _settings_.[[RoundingMode]], _relativeTo_, _calendarRec_). - 1. Let _roundResult_ be _roundRecord_.[[DurationRecord]]. - 1. Let _result_ be ? BalanceTimeDuration(_roundResult_.[[Days]], _roundResult_.[[Hours]], _roundResult_.[[Minutes]], _roundResult_.[[Seconds]], _roundResult_.[[Milliseconds]], _roundResult_.[[Microseconds]], _roundResult_.[[Nanoseconds]], _settings_.[[LargestUnit]]). - 1. Let _balanceResult_ be ? BalanceDateDurationRelative(_roundResult_.[[Years]], _roundResult_.[[Months]], _roundResult_.[[Weeks]], _result_.[[Days]], _settings_.[[LargestUnit]], _settings_.[[SmallestUnit]], _relativeTo_, _calendarRec_). - 1. Return ! CreateTemporalDuration(_sign_ × _balanceResult_.[[Years]], _sign_ × _balanceResult_.[[Months]], _sign_ × _balanceResult_.[[Weeks]], _sign_ × _balanceResult_.[[Days]], _sign_ × _result_.[[Hours]], _sign_ × _result_.[[Minutes]], _sign_ × _result_.[[Seconds]], _sign_ × _result_.[[Milliseconds]], _sign_ × _result_.[[Microseconds]], _sign_ × _result_.[[Nanoseconds]]). + 1. If _roundingGranularityIsNoop_ is *false*, then + 1. Let _relativeTo_ be ! CreateTemporalDate(_dateTime_.[[ISOYear]], _dateTime_.[[ISOMonth]], _dateTime_.[[ISODay]], _dateTime_.[[Calendar]]). + 1. Let _roundRecord_ be ? RoundDuration(_result_.[[Years]], _result_.[[Months]], _result_.[[Weeks]], _result_.[[Days]], _result_.[[NormalizedTime]], _settings_.[[RoundingIncrement]], _settings_.[[SmallestUnit]], _settings_.[[RoundingMode]], _relativeTo_, _calendarRec_). + 1. Let _roundResult_ be _roundRecord_.[[NormalizedDuration]]. + 1. Let _normWithDays_ be ? Add24HourDaysToNormalizedTimeDuration(_roundResult_.[[NormalizedTime]], _roundResult_.[[Days]]). + 1. Let _timeResult_ be BalanceTimeDuration(_normWithDays_, _settings_.[[LargestUnit]]). + 1. Let _balanceResult_ be ? BalanceDateDurationRelative(_roundResult_.[[Years]], _roundResult_.[[Months]], _roundResult_.[[Weeks]], _timeResult_.[[Days]], _settings_.[[LargestUnit]], _settings_.[[SmallestUnit]], _relativeTo_, _calendarRec_). + 1. Else, + 1. Let _normWithDays_ be ? Add24HourDaysToNormalizedTimeDuration(_result_.[[NormalizedTime]], _result_.[[Days]]). + 1. Let _timeResult_ be BalanceTimeDuration(_normWithDays_, _settings_.[[LargestUnit]]). + 1. Let _balanceResult_ be ! CreateDateDurationRecord(_result_.[[Years]], _result_.[[Months]], _result_.[[Weeks]], _timeResult_.[[Days]]). + 1. Return ! CreateTemporalDuration(_sign_ × _balanceResult_.[[Years]], _sign_ × _balanceResult_.[[Months]], _sign_ × _balanceResult_.[[Weeks]], _sign_ × _balanceResult_.[[Days]], _sign_ × _timeResult_.[[Hours]], _sign_ × _timeResult_.[[Minutes]], _sign_ × _timeResult_.[[Seconds]], _sign_ × _timeResult_.[[Milliseconds]], _sign_ × _timeResult_.[[Microseconds]], _sign_ × _timeResult_.[[Nanoseconds]]). @@ -1335,7 +1333,8 @@

1. Let _duration_ be ? ToTemporalDurationRecord(_temporalDurationLike_). 1. Set _options_ to ? GetOptionsObject(_options_). 1. Let _calendarRec_ be ? CreateCalendarMethodsRecord(_dateTime_.[[Calendar]], « ~date-add~ »). - 1. Let _result_ be ? AddDateTime(_dateTime_.[[ISOYear]], _dateTime_.[[ISOMonth]], _dateTime_.[[ISODay]], _dateTime_.[[ISOHour]], _dateTime_.[[ISOMinute]], _dateTime_.[[ISOSecond]], _dateTime_.[[ISOMillisecond]], _dateTime_.[[ISOMicrosecond]], _dateTime_.[[ISONanosecond]], _calendarRec_, _sign_ × _duration_.[[Years]], _sign_ × _duration_.[[Months]], _sign_ × _duration_.[[Weeks]], _sign_ × _duration_.[[Days]], _sign_ × _duration_.[[Hours]], _sign_ × _duration_.[[Minutes]], _sign_ × _duration_.[[Seconds]], _sign_ × _duration_.[[Milliseconds]], _sign_ × _duration_.[[Microseconds]], _sign_ × _duration_.[[Nanoseconds]], _options_). + 1. Let _norm_ be NormalizeTimeDuration(_sign_ × _duration_.[[Hours]], _sign_ × _duration_.[[Minutes]], _sign_ × _duration_.[[Seconds]], _sign_ × _duration_.[[Milliseconds]], _sign_ × _duration_.[[Microseconds]], _sign_ × _duration_.[[Nanoseconds]]). + 1. Let _result_ be ? AddDateTime(_dateTime_.[[ISOYear]], _dateTime_.[[ISOMonth]], _dateTime_.[[ISODay]], _dateTime_.[[ISOHour]], _dateTime_.[[ISOMinute]], _dateTime_.[[ISOSecond]], _dateTime_.[[ISOMillisecond]], _dateTime_.[[ISOMicrosecond]], _dateTime_.[[ISONanosecond]], _calendarRec_, _sign_ × _duration_.[[Years]], _sign_ × _duration_.[[Months]], _sign_ × _duration_.[[Weeks]], _sign_ × _duration_.[[Days]], _norm_, _options_). 1. Assert: IsValidISODate(_result_.[[Year]], _result_.[[Month]], _result_.[[Day]]) is *true*. 1. Assert: IsValidTime(_result_.[[Hour]], _result_.[[Minute]], _result_.[[Second]], _result_.[[Millisecond]], _result_.[[Microsecond]], _result_.[[Nanosecond]]) is *true*. 1. Return ? CreateTemporalDateTime(_result_.[[Year]], _result_.[[Month]], _result_.[[Day]], _result_.[[Hour]], _result_.[[Minute]], _result_.[[Second]], _result_.[[Millisecond]], _result_.[[Microsecond]], _result_.[[Nanosecond]], _dateTime_.[[Calendar]]). diff --git a/spec/plaintime.html b/spec/plaintime.html index 2de869bf4e..f255c60d2f 100644 --- a/spec/plaintime.html +++ b/spec/plaintime.html @@ -600,7 +600,7 @@

description
-
It returns a Time Duration Record with the elapsed duration from a first wall-clock time, until a second wall-clock time.
+
It returns a Normalized Time Duration Record with the elapsed duration from a first wall-clock time, until a second wall-clock time.
1. Let _hours_ be _h2_ - _h1_. @@ -609,10 +609,9 @@

1. Let _milliseconds_ be _ms2_ - _ms1_. 1. Let _microseconds_ be _mus2_ - _mus1_. 1. Let _nanoseconds_ be _ns2_ - _ns1_. - 1. Let _sign_ be ! DurationSign(0, 0, 0, 0, _hours_, _minutes_, _seconds_, _milliseconds_, _microseconds_, _nanoseconds_). - 1. Let _bt_ be BalanceTime(_hours_ × _sign_, _minutes_ × _sign_, _seconds_ × _sign_, _milliseconds_ × _sign_, _microseconds_ × _sign_, _nanoseconds_ × _sign_). - 1. Assert: _bt_.[[Days]] is 0. - 1. Return ! CreateTimeDurationRecord(0, _bt_.[[Hour]] × _sign_, _bt_.[[Minute]] × _sign_, _bt_.[[Second]] × _sign_, _bt_.[[Millisecond]] × _sign_, _bt_.[[Microsecond]] × _sign_, _bt_.[[Nanosecond]] × _sign_). + 1. Let _norm_ be NormalizeTimeDuration(_hours_, _minutes_, _seconds_, _milliseconds_, _microseconds_, _nanoseconds_). + 1. Assert: NormalizedTimeDurationAbs(_norm_).[[TotalNanoseconds]] < nsPerDay. + 1. Return _norm_. @@ -916,12 +915,7 @@

_millisecond_: an integer in the inclusive range 0 to 999, _microsecond_: an integer in the inclusive range 0 to 999, _nanosecond_: an integer in the inclusive range 0 to 999, - _hours_: an integer, - _minutes_: an integer, - _seconds_: an integer, - _milliseconds_: an integer, - _microseconds_: an integer, - _nanoseconds_: an integer, + _norm_: a Normalized Time Duration Record, ): a Time Record

@@ -929,12 +923,8 @@

- 1. Set _hour_ to _hour_ + _hours_. - 1. Set _minute_ to _minute_ + _minutes_. - 1. Set _second_ to _second_ + _seconds_. - 1. Set _millisecond_ to _millisecond_ + _milliseconds_. - 1. Set _microsecond_ to _microsecond_ + _microseconds_. - 1. Set _nanosecond_ to _nanosecond_ + _nanoseconds_. + 1. Set _second_ to _second_ + NormalizedTimeDurationSeconds(_norm_). + 1. Set _nanosecond_ to _nanosecond_ + NormalizedTimeDurationSubseconds(_norm_). 1. Return BalanceTime(_hour_, _minute_, _second_, _millisecond_, _microsecond_, _nanosecond_).
@@ -1019,11 +1009,11 @@

1. Set _other_ to ? ToTemporalTime(_other_). 1. Let _resolvedOptions_ be ? SnapshotOwnProperties(? GetOptionsObject(_options_), *null*). 1. Let _settings_ be ? GetDifferenceSettings(_operation_, _resolvedOptions_, ~time~, « », *"nanosecond"*, *"hour"*). - 1. Let _result_ be ! DifferenceTime(_temporalTime_.[[ISOHour]], _temporalTime_.[[ISOMinute]], _temporalTime_.[[ISOSecond]], _temporalTime_.[[ISOMillisecond]], _temporalTime_.[[ISOMicrosecond]], _temporalTime_.[[ISONanosecond]], _other_.[[ISOHour]], _other_.[[ISOMinute]], _other_.[[ISOSecond]], _other_.[[ISOMillisecond]], _other_.[[ISOMicrosecond]], _other_.[[ISONanosecond]]). + 1. Let _norm_ be ! DifferenceTime(_temporalTime_.[[ISOHour]], _temporalTime_.[[ISOMinute]], _temporalTime_.[[ISOSecond]], _temporalTime_.[[ISOMillisecond]], _temporalTime_.[[ISOMicrosecond]], _temporalTime_.[[ISONanosecond]], _other_.[[ISOHour]], _other_.[[ISOMinute]], _other_.[[ISOSecond]], _other_.[[ISOMillisecond]], _other_.[[ISOMicrosecond]], _other_.[[ISONanosecond]]). 1. If _settings_.[[SmallestUnit]] is not *"nanosecond"* or _settings_.[[RoundingIncrement]] ≠ 1, then - 1. Let _roundRecord_ be ! RoundDuration(0, 0, 0, 0, _result_.[[Hours]], _result_.[[Minutes]], _result_.[[Seconds]], _result_.[[Milliseconds]], _result_.[[Microseconds]], _result_.[[Nanoseconds]], _settings_.[[RoundingIncrement]], _settings_.[[SmallestUnit]], _settings_.[[RoundingMode]]). - 1. Set _result_ to _roundRecord_.[[DurationRecord]]. - 1. Set _result_ to ! BalanceTimeDuration(0, _result_.[[Hours]], _result_.[[Minutes]], _result_.[[Seconds]], _result_.[[Milliseconds]], _result_.[[Microseconds]], _result_.[[Nanoseconds]], _settings_.[[LargestUnit]]). + 1. Let _roundRecord_ be ! RoundDuration(0, 0, 0, 0, _norm_, _settings_.[[RoundingIncrement]], _settings_.[[SmallestUnit]], _settings_.[[RoundingMode]]). + 1. Set _norm_ to _roundRecord_.[[NormalizedDuration]].[[NormalizedTime]]. + 1. Let _result_ be BalanceTimeDuration(_norm_, _settings_.[[LargestUnit]]). 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]]). @@ -1042,8 +1032,8 @@

1. If _operation_ is ~subtract~, let _sign_ be -1. Otherwise, let _sign_ be 1. 1. Let _duration_ be ? ToTemporalDurationRecord(_temporalDurationLike_). - 1. Let _result_ be AddTime(_temporalTime_.[[ISOHour]], _temporalTime_.[[ISOMinute]], _temporalTime_.[[ISOSecond]], _temporalTime_.[[ISOMillisecond]], _temporalTime_.[[ISOMicrosecond]], _temporalTime_.[[ISONanosecond]], _sign_ × _duration_.[[Hours]], _sign_ × _duration_.[[Minutes]], _sign_ × _duration_.[[Seconds]], _sign_ × _duration_.[[Milliseconds]], _sign_ × _duration_.[[Microseconds]], _sign_ × _duration_.[[Nanoseconds]]). - 1. Assert: IsValidTime(_result_.[[Hour]], _result_.[[Minute]], _result_.[[Second]], _result_.[[Millisecond]], _result_.[[Microsecond]], _result_.[[Nanosecond]]) is *true*. + 1. Let _norm_ be NormalizeTimeDuration(_sign_ × _duration_.[[Hours]], _sign_ × _duration_.[[Minutes]], _sign_ × _duration_.[[Seconds]], _sign_ × _duration_.[[Milliseconds]], _sign_ × _duration_.[[Microseconds]], _sign_ × _duration_.[[Nanoseconds]]). + 1. Let _result_ be AddTime(_temporalTime_.[[ISOHour]], _temporalTime_.[[ISOMinute]], _temporalTime_.[[ISOSecond]], _temporalTime_.[[ISOMillisecond]], _temporalTime_.[[ISOMicrosecond]], _temporalTime_.[[ISONanosecond]], _norm_). 1. Return ! CreateTemporalTime(_result_.[[Hour]], _result_.[[Minute]], _result_.[[Second]], _result_.[[Millisecond]], _result_.[[Microsecond]], _result_.[[Nanosecond]]). diff --git a/spec/plainyearmonth.html b/spec/plainyearmonth.html index 92ebaa8d2d..b5ae031691 100644 --- a/spec/plainyearmonth.html +++ b/spec/plainyearmonth.html @@ -652,8 +652,8 @@

1. Perform ! CreateDataPropertyOrThrow(_resolvedOptions_, *"largestUnit"*, _settings_.[[LargestUnit]]). 1. Let _result_ be ? CalendarDateUntil(_calendarRec_, _thisDate_, _otherDate_, _resolvedOptions_). 1. If _settings_.[[SmallestUnit]] is not *"month"* or _settings_.[[RoundingIncrement]] ≠ 1, then - 1. Let _roundRecord_ be ? RoundDuration(_result_.[[Years]], _result_.[[Months]], 0, 0, 0, 0, 0, 0, 0, 0, _settings_.[[RoundingIncrement]], _settings_.[[SmallestUnit]], _settings_.[[RoundingMode]], _thisDate_, _calendarRec_). - 1. Let _roundResult_ be _roundRecord_.[[DurationRecord]]. + 1. Let _roundRecord_ be ? RoundDuration(_result_.[[Years]], _result_.[[Months]], 0, 0, ZeroTimeDuration(), _settings_.[[RoundingIncrement]], _settings_.[[SmallestUnit]], _settings_.[[RoundingMode]], _thisDate_, _calendarRec_). + 1. Let _roundResult_ be _roundRecord_.[[NormalizedDuration]]. 1. Set _result_ to ? BalanceDateDurationRelative(_roundResult_.[[Years]], _roundResult_.[[Months]], 0, 0, _settings_.[[LargestUnit]], _settings_.[[SmallestUnit]], _thisDate_, _calendarRec_). 1. Return ! CreateTemporalDuration(_sign_ × _result_.[[Years]], _sign_ × _result_.[[Months]], 0, 0, 0, 0, 0, 0, 0, 0). @@ -675,9 +675,11 @@

1. Let _duration_ be ? ToTemporalDuration(_temporalDurationLike_). 1. If _operation_ is ~subtract~, then 1. Set _duration_ to ! CreateNegatedTemporalDuration(_duration_). - 1. Let _balanceResult_ be ? BalanceTimeDuration(_duration_.[[Days]], _duration_.[[Hours]], _duration_.[[Minutes]], _duration_.[[Seconds]], _duration_.[[Milliseconds]], _duration_.[[Microseconds]], _duration_.[[Nanoseconds]], *"day"*). 1. Set _options_ to ? GetOptionsObject(_options_). - 1. Let _sign_ be ! DurationSign(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _balanceResult_.[[Days]], 0, 0, 0, 0, 0, 0). + 1. Let _norm_ be NormalizeTimeDuration(_duration_.[[Hours]], _duration_.[[Minutes]], _duration_.[[Seconds]], _duration_.[[Milliseconds]], _duration_.[[Microseconds]], _duration_.[[Nanoseconds]]). + 1. Let _balanceResult_ be BalanceTimeDuration(_norm_, *"day"*). + 1. Let _days_ be _duration_.[[Days]] + _balanceResult_.[[Days]]. + 1. Let _sign_ be ! DurationSign(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _days_, 0, 0, 0, 0, 0, 0). 1. Let _calendarRec_ be ? CreateCalendarMethodsRecord(_yearMonth_.[[Calendar]], « ~date-add~, ~date-from-fields~, ~day~, ~fields~, ~year-month-from-fields~ »). 1. Let _fieldNames_ be ? CalendarFields(_calendarRec_, « *"monthCode"*, *"year"* »). 1. Let _fields_ be ? PrepareTemporalFields(_yearMonth_, _fieldNames_, «»). @@ -694,7 +696,7 @@

1. Let _date_ be ? CalendarDateFromFields(_calendarRec_, _fieldsCopy_). 1. Else, 1. Let _date_ be _intermediateDate_. - 1. Let _durationToAdd_ be ! CreateTemporalDuration(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _balanceResult_.[[Days]], 0, 0, 0, 0, 0, 0). + 1. Let _durationToAdd_ be ! CreateTemporalDuration(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _days_, 0, 0, 0, 0, 0, 0). 1. Let _optionsCopy_ be ? SnapshotOwnProperties(_options_, *null*). 1. Let _addedDate_ be ? AddDate(_calendarRec_, _date_, _durationToAdd_, _options_). 1. Let _addedDateFields_ be ? PrepareTemporalFields(_addedDate_, _fieldNames_, «»). diff --git a/spec/timezone.html b/spec/timezone.html index 30e2793ba0..0425c09ff4 100644 --- a/spec/timezone.html +++ b/spec/timezone.html @@ -938,14 +938,16 @@

1. Let _offsetAfter_ be ? GetOffsetNanosecondsFor(_timeZoneRec_, _dayAfter_). 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_). + 1. Let _norm_ be NormalizeTimeDuration(0, 0, 0, 0, 0, -_nanoseconds_). + 1. Let _earlierTime_ be AddTime(_dateTime_.[[ISOHour]], _dateTime_.[[ISOMinute]], _dateTime_.[[ISOSecond]], _dateTime_.[[ISOMillisecond]], _dateTime_.[[ISOMicrosecond]], _dateTime_.[[ISONanosecond]], _norm_). 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(_timeZoneRec_, _earlierDateTime_). 1. If _possibleInstants_ is empty, throw a *RangeError* exception. 1. Return _possibleInstants_[0]. 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 _norm_ be NormalizeTimeDuration(0, 0, 0, 0, 0, _nanoseconds_). + 1. Let _laterTime_ be AddTime(_dateTime_.[[ISOHour]], _dateTime_.[[ISOMinute]], _dateTime_.[[ISOSecond]], _dateTime_.[[ISOMillisecond]], _dateTime_.[[ISOMicrosecond]], _dateTime_.[[ISONanosecond]], _norm_). 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]], *"iso8601"*). 1. Set _possibleInstants_ to ? GetPossibleInstantsFor(_timeZoneRec_, _laterDateTime_). diff --git a/spec/zoneddatetime.html b/spec/zoneddatetime.html index a6e480a471..119748f61a 100644 --- a/spec/zoneddatetime.html +++ b/spec/zoneddatetime.html @@ -454,8 +454,8 @@

get Temporal.ZonedDateTime.prototype.hoursInDay

1. Let _tomorrow_ be ? CreateTemporalDateTime(_tomorrowFields_.[[Year]], _tomorrowFields_.[[Month]], _tomorrowFields_.[[Day]], 0, 0, 0, 0, 0, 0, *"iso8601"*). 1. Let _todayInstant_ be ? GetInstantFor(_timeZoneRec_, _today_, *"compatible"*). 1. Let _tomorrowInstant_ be ? GetInstantFor(_timeZoneRec_, _tomorrow_, *"compatible"*). - 1. Let _diffNs_ be _tomorrowInstant_.[[Nanoseconds]] - _todayInstant_.[[Nanoseconds]]. - 1. Return 𝔽(_diffNs_ / (3.6 × 1012)). + 1. Let _diff_ be NormalizedTimeDurationFromEpochNanosecondsDifference(_tomorrowInstant_.[[Nanoseconds]], _todayInstant_.[[Nanoseconds]]). + 1. Return 𝔽(DivideNormalizedTimeDuration(_diff_, 3.6 × 1012)).
@@ -1290,12 +1290,7 @@

_months_: an integer, _weeks_: an integer, _days_: an integer, - _hours_: an integer, - _minutes_: an integer, - _seconds_: an integer, - _milliseconds_: an integer, - _microseconds_: an integer, - _nanoseconds_: an integer, + _norm_: a Normalized Time Duration Record, optional _precalculatedPlainDateTime_: a Temporal.PlainDateTime or *undefined*, optional _options_: an Object, ): either a normal completion containing a BigInt or an abrupt completion @@ -1315,7 +1310,7 @@

1. If _options_ is not present, set _options_ to *undefined*. 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. Return ? AddInstant(_epochNanoseconds_, _norm_). 1. Let _instant_ be ! CreateTemporalInstant(_epochNanoseconds_). 1. If _precalculatedPlainDateTime_ is not *undefined*, then 1. Let _temporalDateTime_ be _precalculatedPlainDateTime_. @@ -1324,14 +1319,14 @@

1. If _years_ = 0, and _months_ = 0, and _weeks_ = 0, then 1. Let _overflow_ be ? ToTemporalOverflow(_options_). 1. Let _intermediate_ be ? AddDaysToZonedDateTime(_instant_, _temporalDateTime_, _timeZoneRec_, _calendarRec_.[[Receiver]], _days_, _overflow_).[[EpochNanoseconds]]. - 1. Return ? AddInstant(_intermediate_, _hours_, _minutes_, _seconds_, _milliseconds_, _microseconds_, _nanoseconds_). + 1. Return ? AddInstant(_intermediate_, _norm_). 1. Assert: CalendarMethodsRecordHasLookedUp(_calendarRec_, ~date-add~) is *true*. 1. Let _datePart_ be ! CreateTemporalDate(_temporalDateTime_.[[ISOYear]], _temporalDateTime_.[[ISOMonth]], _temporalDateTime_.[[ISODay]], _calendarRec_.[[Receiver]]). 1. Let _dateDuration_ be ! CreateTemporalDuration(_years_, _months_, _weeks_, _days_, 0, 0, 0, 0, 0, 0). 1. Let _addedDate_ be ? CalendarDateAdd(_calendarRec_, _datePart_, _dateDuration_, _options_). 1. Let _intermediateDateTime_ be ? CreateTemporalDateTime(_addedDate_.[[ISOYear]], _addedDate_.[[ISOMonth]], _addedDate_.[[ISODay]], _temporalDateTime_.[[ISOHour]], _temporalDateTime_.[[ISOMinute]], _temporalDateTime_.[[ISOSecond]], _temporalDateTime_.[[ISOMillisecond]], _temporalDateTime_.[[ISOMicrosecond]], _temporalDateTime_.[[ISONanosecond]], _calendarRec_.[[Receiver]]). 1. Let _intermediateInstant_ be ? GetInstantFor(_timeZoneRec_, _intermediateDateTime_, *"compatible"*). - 1. Return ? AddInstant(_intermediateInstant_.[[Nanoseconds]], _hours_, _minutes_, _seconds_, _milliseconds_, _microseconds_, _nanoseconds_). + 1. Return ? AddInstant(_intermediateInstant_.[[Nanoseconds]], _norm_). @@ -1385,7 +1380,7 @@

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

description
@@ -1396,7 +1391,7 @@

Unless _ns1_ and _ns2_ are equal, _timeZoneRec_ must have looked up both `getOffsetNanosecondsFor` and `getPossibleInstantsFor`.

1. If _ns1_ is _ns2_, then - 1. Return ! CreateDurationRecord(0, 0, 0, 0, 0, 0, 0, 0, 0, 0). + 1. Return ! CreateNormalizedDurationRecord(0, 0, 0, 0, ZeroTimeDuration()). 1. If _precalculatedPlainDateTime_ is *undefined*, then 1. Let _startInstant_ be ! CreateTemporalInstant(_ns1_). 1. Let _startDateTime_ be ? GetPlainDateTimeFor(_timeZoneRec_, _startInstant_, _calendarRec_.[[Receiver]]). @@ -1405,41 +1400,39 @@

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, 0, 0, 0, 0, 0, 0, _startDateTime_). - 1. Let _timeRemainderNs_ be _ns2_ - _intermediateNs_. + 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 ? NanosecondsToDays(ℝ(_timeRemainderNs_), _intermediate_, _timeZoneRec_). - 1. Let _timeDifference_ be ! BalanceTimeDuration(0, 0, 0, 0, 0, 0, _result_.[[Nanoseconds]], *"hour"*). - 1. Return ! CreateDurationRecord(_dateDifference_.[[Years]], _dateDifference_.[[Months]], _dateDifference_.[[Weeks]], _result_.[[Days]], _timeDifference_.[[Hours]], _timeDifference_.[[Minutes]], _timeDifference_.[[Seconds]], _timeDifference_.[[Milliseconds]], _timeDifference_.[[Microseconds]], _timeDifference_.[[Nanoseconds]]). + 1. Let _result_ be ? NormalizedTimeDurationToDays(_norm_, _intermediate_, _timeZoneRec_). + 1. Return ! CreateNormalizedDurationRecord(_dateDifference_.[[Years]], _dateDifference_.[[Months]], _dateDifference_.[[Weeks]], _result_.[[Days]], _result_.[[Remainder]]). - +

- NanosecondsToDays ( - _nanoseconds_: an integer, + NormalizedTimeDurationToDays ( + _norm_: a Normalized Time Duration Record, _zonedRelativeTo_: a Temporal.ZonedDateTime, _timeZoneRec_: a Time Zone Methods Record, 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 + ): either a normal completion containing a Record with fields [[Days]] (an integer), [[Remainder]] (a Normalized Time Duration Record), and [[DayLength]] (an integer), or an abrupt completion

description
- It converts a number of _nanoseconds_ relative to a Temporal.ZonedDateTime _zonedRelativeTo_ (if supplied), and converts it into a number of days and remainder of nanoseconds, taking into account any offset changes in the time zone of _zonedRelativeTo_. + It converts a normalized time duration _norm_ relative to a Temporal.ZonedDateTime _zonedRelativeTo_, and converts it into a number of days and a remainder normalized time duration, taking into account any offset changes in the time zone of _zonedRelativeTo_. It also returns the length of the last day in nanoseconds, for rounding purposes.

Unless _nanoseconds_ = 0, _timeZoneRec_ must have looked up both `getOffsetNanosecondsFor` and `getPossibleInstantsFor`.

- 1. If _nanoseconds_ = 0, then - 1. Return the Record { [[Days]]: 0, [[Nanoseconds]]: 0, [[DayLength]]: nsPerDay }. - 1. If _nanoseconds_ < 0, let _sign_ be -1; else, let _sign_ be 1. - 1. Let _startNs_ be ℝ(_zonedRelativeTo_.[[Nanoseconds]]). - 1. Let _startInstant_ be ! CreateTemporalInstant(ℤ(_startNs_)). - 1. Let _endNs_ be _startNs_ + _nanoseconds_. - 1. If IsValidEpochNanoseconds(ℤ(_endNs_)) is *false*, throw a *RangeError* exception. - 1. Let _endInstant_ be ! CreateTemporalInstant(ℤ(_endNs_)). + 1. Let _sign_ be NormalizedTimeDurationSign(_norm_). + 1. If _sign_ = 0, then + 1. Return the Record { [[Days]]: 0, [[Remainder]]: _norm_, [[DayLength]]: nsPerDay }. + 1. Let _startNs_ be _zonedRelativeTo_.[[Nanoseconds]]. + 1. Let _startInstant_ be ! CreateTemporalInstant(_startNs_). + 1. Let _endNs_ be ? AddInstant(_startNs_, _norm_). + 1. Let _endInstant_ be ! CreateTemporalInstant(_endNs_). 1. If _precalculatedPlainDateTime_ is present, let _startDateTime_ be _precalculatedPlainDateTime_; else let _startDateTime_ be ? GetPlainDateTimeFor(_timeZoneRec_, _startInstant_, *"iso8601"*). 1. Let _endDateTime_ be ? GetPlainDateTimeFor(_timeZoneRec_, _endInstant_, *"iso8601"*). 1. Let _date1_ be ! CreateTemporalDate(_startDateTime_.[[ISOYear]], _startDateTime_.[[ISOMonth]], _startDateTime_.[[ISODay]], *"iso8601"*). @@ -1455,28 +1448,29 @@

1. Repeat, while _days_ > 0 and ℝ(_relativeResult_.[[EpochNanoseconds]]) > _endNs_, 1. Set _days_ to _days_ - 1. 1. Set _relativeResult_ to ? AddDaysToZonedDateTime(_startInstant_, _startDateTime_, _timeZoneRec_, _zonedRelativeTo_.[[Calendar]], _days_). - 1. Set _nanoseconds_ to _endNs_ - ℝ(_relativeResult_.[[EpochNanoseconds]]). + 1. Set _norm_ to NormalizedTimeDurationFromEpochNanosecondsDifference(_endNs_, _relativeResult_.[[EpochNanoseconds]]). 1. Let _done_ be *false*. 1. Let _dayLengthNs_ be ~unset~. 1. Repeat, while _done_ is *false*, 1. Let _oneDayFarther_ be ? AddDaysToZonedDateTime(_relativeResult_.[[Instant]], _relativeResult_.[[DateTime]], _timeZoneRec_, _zonedRelativeTo_.[[Calendar]], _sign_). - 1. Set _dayLengthNs_ to ℝ(_oneDayFarther_.[[EpochNanoseconds]] - _relativeResult_.[[EpochNanoseconds]]). - 1. If (_nanoseconds_ - _dayLengthNs_) × _sign_ ≥ 0, then - 1. Set _nanoseconds_ to _nanoseconds_ - _dayLengthNs_. + 1. Set _dayLengthNs_ to NormalizedTimeDurationFromEpochNanosecondsDifference(_oneDayFarther_.[[EpochNanoseconds]], _relativeResult_.[[EpochNanoseconds]]). + 1. Let _oneDayLess_ be ! SubtractNormalizedTimeDuration(_norm_, _dayLengthNs_). + 1. If NormalizedTimeDurationSign(_oneDayLess_) × _sign_ ≥ 0, then + 1. Set _norm_ to _oneDayLess_. 1. Set _relativeResult_ to _oneDayFarther_. 1. Set _days_ to _days_ + _sign_. 1. Else, 1. Set _done_ to *true*. 1. If _days_ < 0 and _sign_ = 1, throw a *RangeError* exception. 1. If _days_ > 0 and _sign_ = -1, throw a *RangeError* exception. - 1. If _nanoseconds_ < 0, then + 1. If NormalizedTimeDurationSign(_norm_) = -1, then 1. Assert: _sign_ is -1. - 1. If _nanoseconds_ > 0 and _sign_ = -1, throw a *RangeError* exception. - 1. Assert: The inequality abs(_nanoseconds_) < abs(_dayLengthNs_) holds. + 1. If NormalizedTimeDurationSign(_norm_) = 1 and _sign_ = -1, throw a *RangeError* exception. + 1. Assert: CompareNormalizedTimeDuration(NormalizedTimeDurationAbs(_norm_), NormalizedTimeDurationAbs(_dayLengthNs_)) = -1. 1. Return the Record { [[Days]]: _days_, - [[Nanoseconds]]: _nanoseconds_, - [[DayLength]]: abs(_dayLengthNs_) + [[Remainder]]: _norm_, + [[DayLength]]: ℝ(NormalizedTimeDurationAbs(_dayLengthNs_).[[TotalNanoseconds]]) }. @@ -1501,7 +1495,8 @@

1. Let _resolvedOptions_ be ? SnapshotOwnProperties(? GetOptionsObject(_options_), *null*). 1. Let _settings_ be ? GetDifferenceSettings(_operation_, _resolvedOptions_, ~datetime~, « », *"nanosecond"*, *"hour"*). 1. If _settings_.[[LargestUnit]] is not one of *"year"*, *"month"*, *"week"*, or *"day"*, then - 1. Let _result_ be DifferenceInstant(_zonedDateTime_.[[Nanoseconds]], _other_.[[Nanoseconds]], _settings_.[[RoundingIncrement]], _settings_.[[SmallestUnit]], _settings_.[[LargestUnit]], _settings_.[[RoundingMode]]). + 1. Let _norm_ be DifferenceInstant(_zonedDateTime_.[[Nanoseconds]], _other_.[[Nanoseconds]], _settings_.[[RoundingIncrement]], _settings_.[[SmallestUnit]], _settings_.[[RoundingMode]]). + 1. Let _result_ be BalanceTimeDuration(_norm_, _settings_.[[LargestUnit]]). 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. @@ -1513,15 +1508,18 @@

1. Let _precalculatedPlainDateTime_ be ? GetPlainDateTimeFor(_timeZoneRec_, _instant_, _calendarRec_.[[Receiver]]). 1. Let _plainRelativeTo_ be ! CreateTemporalDate(_precalculatedPlainDateTime_.[[ISOYear]], _precalculatedPlainDateTime_.[[ISOMonth]], _precalculatedPlainDateTime_.[[ISODay]], _calendarRec_.[[Receiver]]). 1. Perform ! CreateDataPropertyOrThrow(_resolvedOptions_, *"largestUnit"*, _settings_.[[LargestUnit]]). - 1. Let _difference_ be ? DifferenceZonedDateTime(_zonedDateTime_.[[Nanoseconds]], _other_.[[Nanoseconds]], _timeZoneRec_, _calendarRec_, _settings_.[[LargestUnit]], _resolvedOptions_, _precalculatedPlainDateTime_). + 1. Let _result_ be ? DifferenceZonedDateTime(_zonedDateTime_.[[Nanoseconds]], _other_.[[Nanoseconds]], _timeZoneRec_, _calendarRec_, _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_, _calendarRec_, _zonedDateTime_, _timeZoneRec_, _precalculatedPlainDateTime_). - 1. Let _roundResult_ be _roundRecord_.[[DurationRecord]]. - 1. Let _adjustResult_ 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_, _calendarRec_, _timeZoneRec_, _precalculatedPlainDateTime_). - 1. Let _result_ be ? BalanceDateDurationRelative(_adjustResult_.[[Years]], _adjustResult_.[[Months]], _adjustResult_.[[Weeks]], _adjustResult_.[[Days]], _settings_.[[LargestUnit]], _settings_.[[SmallestUnit]], _plainRelativeTo_, _calendarRec_). - 1. Return ! CreateTemporalDuration(_sign_ × _result_.[[Years]], _sign_ × _result_.[[Months]], _sign_ × _result_.[[Weeks]], _sign_ × _result_.[[Days]], _sign_ × _adjustResult_.[[Hours]], _sign_ × _adjustResult_.[[Minutes]], _sign_ × _adjustResult_.[[Seconds]], _sign_ × _adjustResult_.[[Milliseconds]], _sign_ × _adjustResult_.[[Microseconds]], _sign_ × _adjustResult_.[[Nanoseconds]]). + 1. If _roundingGranularityIsNoop_ is *false*, then + 1. Let _roundRecord_ be ? RoundDuration(_result_.[[Years]], _result_.[[Months]], _result_.[[Weeks]], _result_.[[Days]], _result_.[[NormalizedTime]], _settings_.[[RoundingIncrement]], _settings_.[[SmallestUnit]], _settings_.[[RoundingMode]], _plainRelativeTo_, _calendarRec_, _zonedDateTime_, _timeZoneRec_, _precalculatedPlainDateTime_). + 1. Let _roundResult_ be _roundRecord_.[[NormalizedDuration]]. + 1. Let _daysResult_ be ! NormalizedTimeDurationToDays(_roundResult_.[[NormalizedTime]], _zonedDateTime_, _timeZoneRec_). + 1. Let _days_ be _roundResult_.[[Days]] + _daysResult_.[[Days]]. + 1. Let _adjustResult_ be ? AdjustRoundedDurationDays(_roundResult_.[[Years]], _roundResult_.[[Months]], _roundResult_.[[Weeks]], _days_, _daysResult_.[[NormalizedTime]], _settings_.[[RoundingIncrement]], _settings_.[[SmallestUnit]], _settings_.[[RoundingMode]], _zonedDateTime_, _calendarRec_, _timeZoneRec_, _precalculatedPlainDateTime_). + 1. Let _balanceResult_ be ? BalanceDateDurationRelative(_adjustResult_.[[Years]], _adjustResult_.[[Months]], _adjustResult_.[[Weeks]], _adjustResult_.[[Days]], _settings_.[[LargestUnit]], _settings_.[[SmallestUnit]], _plainRelativeTo_, _calendarRec_). + 1. Set _result_ to ? CombineDateAndNormalizedTimeDuration(_balanceResult_, _adjustResult_.[[NormalizedTime]]). + 1. Let _timeResult_ be BalanceTimeDuration(_result_.[[NormalizedTime]], *"hour"*). + 1. Return ! CreateTemporalDuration(_sign_ × _result_.[[Years]], _sign_ × _result_.[[Months]], _sign_ × _result_.[[Weeks]], _sign_ × _result_.[[Days]], _sign_ × _timeResult_.[[Hours]], _sign_ × _timeResult_.[[Minutes]], _sign_ × _timeResult_.[[Seconds]], _sign_ × _timeResult_.[[Milliseconds]], _sign_ × _timeResult_.[[Microseconds]], _sign_ × _timeResult_.[[Nanoseconds]]). @@ -1543,7 +1541,8 @@

1. Set _options_ to ? GetOptionsObject(_options_). 1. Let _timeZoneRec_ be ? CreateTimeZoneMethodsRecord(_zonedDateTime_.[[TimeZone]], « ~get-offset-nanoseconds-for~, ~get-possible-instants-for~ »). 1. Let _calendarRec_ be ? CreateCalendarMethodsRecord(_zonedDateTime_.[[Calendar]], « ~date-add~ »). - 1. Let _epochNanoseconds_ be ? AddZonedDateTime(_zonedDateTime_.[[Nanoseconds]], _timeZoneRec_, _calendarRec_, _sign_ × _duration_.[[Years]], _sign_ × _duration_.[[Months]], _sign_ × _duration_.[[Weeks]], _sign_ × _duration_.[[Days]], _sign_ × _duration_.[[Hours]], _sign_ × _duration_.[[Minutes]], _sign_ × _duration_.[[Seconds]], _sign_ × _duration_.[[Milliseconds]], _sign_ × _duration_.[[Microseconds]], _sign_ × _duration_.[[Nanoseconds]], *undefined*, _options_). + 1. Let _norm_ be NormalizeTimeDuration(_sign_ × _duration_.[[Hours]], _sign_ × _duration_.[[Minutes]], _sign_ × _duration_.[[Seconds]], _sign_ × _duration_.[[Milliseconds]], _sign_ × _duration_.[[Microseconds]], _sign_ × _duration_.[[Nanoseconds]]). + 1. Let _epochNanoseconds_ be ? AddZonedDateTime(_zonedDateTime_.[[Nanoseconds]], _timeZoneRec_, _calendarRec_, _sign_ × _duration_.[[Years]], _sign_ × _duration_.[[Months]], _sign_ × _duration_.[[Weeks]], _sign_ × _duration_.[[Days]], _norm_, *undefined*, _options_). 1. Return ! CreateTemporalZonedDateTime(_epochNanoseconds_, _timeZoneRec_.[[Receiver]], _calendarRec_.[[Receiver]]).