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_ ] )
1. Set _duration_ to ? ToTemporalDuration(_duration_).
1. Set _options_ to ? GetOptionsObject(_options_).
1. Let _overflow_ be ? ToTemporalOverflow(_options_).
- 1. Let _balanceResult_ be ? BalanceTimeDuration(_duration_.[[Days]], _duration_.[[Hours]], _duration_.[[Minutes]], _duration_.[[Seconds]], _duration_.[[Milliseconds]], _duration_.[[Microseconds]], _duration_.[[Nanoseconds]], *"day"*).
- 1. Let _result_ be ? AddISODate(_date_.[[ISOYear]], _date_.[[ISOMonth]], _date_.[[ISODay]], _duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _balanceResult_.[[Days]], _overflow_).
+ 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 _result_ be ? AddISODate(_date_.[[ISOYear]], _date_.[[ISOMonth]], _date_.[[ISODay]], _duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _duration_.[[Days]] + _balanceResult_.[[Days]], _overflow_).
1. Return ? CreateTemporalDate(_result_.[[Year]], _result_.[[Month]], _result_.[[Day]], *"iso8601"*).
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 Name |
+ Value |
+ Meaning |
+
+
+ [[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 Name |
+ Value |
+ Meaning |
+
+
+ [[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
- 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
+
+
+
+ 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
+
+
+
+ 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
@@ -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
+
+
+
+ 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
+
+
+
+ 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
+
+
+
+ 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
+
+
+
+ 1. Return _epochNs_ + ℤ(_d_.[[TotalNanoseconds]]).
+
+
+
+
+
+ CompareNormalizedTimeDuration (
+ _one_: a Normalized Time Duration Record,
+ _two_: a Normalized Time Duration Record,
+ ): -1, 0, or 1
+
+
+
+ 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
+
+
+
+ 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
+
+
+
+ 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
+
+
+
+ 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
+
+
+
+ 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)
+
+
+
+ 1. Return truncate(_d_.[[TotalNanoseconds]] / 109).
+
+
+
+
+
+ NormalizedTimeDurationSign (
+ _d_: a Normalized Time Duration Record,
+ ): -1, 0, or 1
+
+
+
+ 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)
+
+
+
+ 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
+
+
+
+ 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
+
+
+
+ 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
- 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
@@ -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
- 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
- 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
- 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_ ] )
1. Set _duration_ to ? ToTemporalDuration(_duration_).
1. Set _options_ to ? GetOptionsObject(_options_).
1. Let _overflow_ be ? ToTemporalOverflow(_options_).
- 1. Let _balanceResult_ be ? BalanceTimeDuration(_duration_.[[Days]], _duration_.[[Hours]], _duration_.[[Minutes]], _duration_.[[Seconds]], _duration_.[[Milliseconds]], _duration_.[[Microseconds]], _duration_.[[Nanoseconds]], *"day"*).
+ 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. If _calendar_.[[Identifier]] is *"iso8601"*, then
- 1. Let _result_ be ? AddISODate(_date_.[[ISOYear]], _date_.[[ISOMonth]], _date_.[[ISODay]], _duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _balanceResult_.[[Days]], _overflow_).
+ 1. Let _result_ be ? AddISODate(_date_.[[ISOYear]], _date_.[[ISOMonth]], _date_.[[ISODay]], _duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _duration_.[[Days]] + _balanceResult_.[[Days]], _overflow_).
1. Else,
- 1. Let _balancedDuration_ be ! CreateDateDurationRecord(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _balanceResult_.[[Days]]).
+ 1. Let _balancedDuration_ be ! CreateDateDurationRecord(_duration_.[[Years]], _duration_.[[Months]], _duration_.[[Weeks]], _duration_.[[Days]] + _balanceResult_.[[Days]]).
1. Let _result_ be ? CalendarDateAddition(_calendar_.[[Identifier]], _date_, _balancedDuration_, _overflow_).
1. Return ? CreateTemporalDate(_result_.[[Year]], _result_.[[Month]], _result_.[[Day]], _calendar_.[[Identifier]]).
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
@@ -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 @@
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
- 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