diff --git a/docs/duration.md b/docs/duration.md index 4464d97b06..de4b394b40 100644 --- a/docs/duration.md +++ b/docs/duration.md @@ -256,14 +256,11 @@ duration = duration.with({ years, months }); ``` -### duration.**add**(_other_: Temporal.Duration | object | string, _options_?: object) : Temporal.Duration +### duration.**add**(_other_: Temporal.Duration | object | string) : Temporal.Duration **Parameters:** - `other` (`Temporal.Duration` or value convertible to one): The duration to add. -- `options` (optional object): An object with properties representing options for the addition. - The following option is recognized: - - `relativeTo` (`Temporal.PlainDate`, `Temporal.ZonedDateTime`, or value convertible to one of those): The starting point to use when adding years, months, weeks, and days. **Returns:** a new `Temporal.Duration` object which represents the sum of the durations of `duration` and `other`. @@ -275,13 +272,9 @@ If `other` is not a `Temporal.Duration` object, then it will be converted to one In order to be valid, the resulting duration must not have fields with mixed signs, and so the result is balanced. For usage examples and a more complete explanation of how balancing works and why it is necessary, see [Duration balancing](./balancing.md). -By default, you cannot add durations with years, months, or weeks, as that could be ambiguous depending on the start date. -To do this, you must provide a start date using the `relativeTo` option. - -The `relativeTo` option may be a `Temporal.ZonedDateTime` in which case time zone offset changes will be taken into account when converting between days and hours. If `relativeTo` is omitted or is a `Temporal.PlainDate`, then days are always considered equal to 24 hours. - -If `relativeTo` is neither a `Temporal.PlainDate` nor a `Temporal.ZonedDateTime`, then it will be converted to one of the two, as if it were first attempted with `Temporal.ZonedDateTime.from()` and then with `Temporal.PlainDate.from()`. -This means that an ISO 8601 string with a time zone name annotation in it, or a property bag with a `timeZone` property, will be converted to a `Temporal.ZonedDateTime`, and an ISO 8601 string without a time zone name or a property bag without a `timeZone` property will be converted to a `Temporal.PlainDate`. +You cannot convert between years, months, or weeks when adding durations, as that could be ambiguous depending on the start date. +If `duration` or `other` have nonzero years, months, or weeks, this function will throw an exception. +If you need to add durations with years, months, or weeks, add the two durations to a start date, and then figure the difference between the resulting date and the start date. Adding a negative duration is equivalent to subtracting the absolute value of that duration. @@ -298,27 +291,27 @@ one = Temporal.Duration.from({ hours: 1, minutes: 30 }); two = Temporal.Duration.from({ hours: 2, minutes: 45 }); result = one.add(two); // => PT4H15M -fifty = Temporal.Duration.from('P50Y50M50DT50H50M50.500500500S'); -/* WRONG */ result = fifty.add(fifty); // => throws, need relativeTo -result = fifty.add(fifty, { relativeTo: '1900-01-01' }); // => P108Y7M12DT5H41M41.001001S +// Example of adding calendar units +oneAndAHalfMonth = Temporal.Duration.from({ months: 1, days: 16 }); +/* WRONG */ oneAndAHalfMonth.add(oneAndAHalfMonth); // => not allowed, throws -// Example of converting ambiguous units relative to a start date -oneAndAHalfMonth = Temporal.Duration.from({ months: 1, days: 15 }); -/* WRONG */ oneAndAHalfMonth.add(oneAndAHalfMonth); // => throws -oneAndAHalfMonth.add(oneAndAHalfMonth, { relativeTo: '2000-02-01' }); // => P3M -oneAndAHalfMonth.add(oneAndAHalfMonth, { relativeTo: '2000-03-01' }); // => P2M30D +// To convert units, use arithmetic relative to a start date: +startDate1 = Temporal.PlainDate.from('2000-12-01'); +startDate1.add(oneAndAHalfMonth).add(oneAndAHalfMonth) + .since(startDate1, { largestUnit: 'months' }); // => P3M4D + +startDate2 = Temporal.PlainDate.from('2001-01-01'); +startDate2.add(oneAndAHalfMonth).add(oneAndAHalfMonth) + .since(startDate2, { largestUnit: 'months' }); // => P3M1D ``` -### duration.**subtract**(_other_: Temporal.Duration | object | string, _options_?: object) : Temporal.Duration +### duration.**subtract**(_other_: Temporal.Duration | object | string) : Temporal.Duration **Parameters:** - `other` (`Temporal.Duration` or value convertible to one): The duration to subtract. -- `options` (optional object): An object with properties representing options for the subtraction. - The following option is recognized: - - `relativeTo` (`Temporal.PlainDate`, `Temporal.ZonedDateTime`, or value convertible to one of those): The starting point to use when adding years, months, weeks, and days. **Returns:** a new `Temporal.Duration` object which represents the duration of `duration` less the duration of `other`. @@ -330,13 +323,9 @@ If `duration` is not a `Temporal.Duration` object, then it will be converted to In order to be valid, the resulting duration must not have fields with mixed signs, and so the result is balanced. For usage examples and a more complete explanation of how balancing works and why it is necessary, see [Duration balancing](./balancing.md#duration-arithmetic). -By default, you cannot subtract durations with years, months, or weeks, as that could be ambiguous depending on the start date. -To do this, you must provide a start date using the `relativeTo` option. - -The `relativeTo` option may be a `Temporal.ZonedDateTime` in which case time zone offset changes will be taken into account when converting between days and hours. If `relativeTo` is omitted or is a `Temporal.PlainDate`, then days are always considered equal to 24 hours. - -If `relativeTo` is neither a `Temporal.PlainDate` nor a `Temporal.ZonedDateTime`, then it will be converted to one of the two, as if it were first attempted with `Temporal.ZonedDateTime.from()` and then with `Temporal.PlainDate.from()`. -This means that an ISO 8601 string with a time zone name annotation in it, or a property bag with a `timeZone` property, will be converted to a `Temporal.ZonedDateTime`, and an ISO 8601 string without a time zone name or a property bag without a `timeZone` property will be converted to a `Temporal.PlainDate`. +You cannot convert between years, months, or weeks when adding durations, as that could be ambiguous depending on the start date. +If `duration` or `other` have nonzero years, months, or weeks, this function will throw an exception. +If you need to add durations with years, months, or weeks, add the two durations to a start date, and then figure the difference between the resulting date and the start date. Subtracting a negative duration is equivalent to adding the absolute value of that duration. @@ -351,12 +340,20 @@ two = Temporal.Duration.from({ seconds: 30 }); one.subtract(two); // => PT179M30S one.subtract(two).round({ largestUnit: 'hour' }); // => PT2H59M30S -// Example of converting ambiguous units relative to a start date +// Example of subtracting calendar units; cannot be subtracted using +// subtract() because units need to be converted threeMonths = Temporal.Duration.from({ months: 3 }); oneAndAHalfMonth = Temporal.Duration.from({ months: 1, days: 15 }); -/* WRONG */ threeMonths.subtract(oneAndAHalfMonth); // => throws -threeMonths.subtract(oneAndAHalfMonth, { relativeTo: '2000-02-01' }); // => P1M16D -threeMonths.subtract(oneAndAHalfMonth, { relativeTo: '2000-03-01' }); // => P1M15D +/* WRONG */ threeMonths.subtract(oneAndAHalfMonth); // => not allowed, throws + +// To convert units, use arithmetic relative to a start date: +startDate1 = Temporal.PlainDate.from('2001-01-01'); +startDate1.add(threeMonths).subtract(oneAndAHalfMonth) + .since(startDate1, { largestUnit: 'months' }); // => P1M13D + +startDate2 = Temporal.PlainDate.from('2001-02-01'); +startDate2.add(threeMonths).subtract(oneAndAHalfMonth) + .since(startDate2, { largestUnit: 'months' }); // => P1M16D ``` ### duration.**negated**() : Temporal.Duration diff --git a/polyfill/index.d.ts b/polyfill/index.d.ts index e54392e358..e01e0ceddb 100644 --- a/polyfill/index.d.ts +++ b/polyfill/index.d.ts @@ -463,8 +463,7 @@ export namespace Temporal { }; /** - * Options to control behavior of `Duration.compare()`, `Duration.add()`, and - * `Duration.subtract()` + * Options to control behavior of `Duration.compare()` */ export interface DurationArithmeticOptions { /** @@ -544,8 +543,8 @@ export namespace Temporal { negated(): Temporal.Duration; abs(): Temporal.Duration; with(durationLike: DurationLike): Temporal.Duration; - add(other: Temporal.Duration | DurationLike | string, options?: DurationArithmeticOptions): Temporal.Duration; - subtract(other: Temporal.Duration | DurationLike | string, options?: DurationArithmeticOptions): Temporal.Duration; + add(other: Temporal.Duration | DurationLike | string): Temporal.Duration; + subtract(other: Temporal.Duration | DurationLike | string): Temporal.Duration; round(roundTo: DurationRoundTo): Temporal.Duration; total(totalOf: DurationTotalOf): number; toLocaleString(locales?: string | string[], options?: Intl.DateTimeFormatOptions): string; diff --git a/polyfill/lib/duration.mjs b/polyfill/lib/duration.mjs index 9957e1b638..33eeb7cfc4 100644 --- a/polyfill/lib/duration.mjs +++ b/polyfill/lib/duration.mjs @@ -209,13 +209,13 @@ export class Duration { Math.abs(GetSlot(this, NANOSECONDS)) ); } - add(other, options = undefined) { + add(other) { if (!ES.IsTemporalDuration(this)) throw new TypeError('invalid receiver'); - return ES.AddDurations('add', this, other, options); + return ES.AddDurations('add', this, other); } - subtract(other, options = undefined) { + subtract(other) { if (!ES.IsTemporalDuration(this)) throw new TypeError('invalid receiver'); - return ES.AddDurations('subtract', this, other, options); + return ES.AddDurations('subtract', this, other); } round(roundTo) { if (!ES.IsTemporalDuration(this)) throw new TypeError('invalid receiver'); diff --git a/polyfill/lib/ecmascript.mjs b/polyfill/lib/ecmascript.mjs index 5278b72b16..4f6d5a420e 100644 --- a/polyfill/lib/ecmascript.mjs +++ b/polyfill/lib/ecmascript.mjs @@ -5091,16 +5091,9 @@ export function AddDaysToZonedDateTime(instant, dateTime, timeZoneRec, calendar, }; } -export function AddDurations(operation, duration, other, options) { +export function AddDurations(operation, duration, other) { const sign = operation === 'subtract' ? -1 : 1; other = ToTemporalDurationRecord(other); - options = GetOptionsObject(options); - const { plainRelativeTo, zonedRelativeTo, timeZoneRec } = GetTemporalRelativeToOption(options); - - const calendarRec = CalendarMethodRecord.CreateFromRelativeTo(plainRelativeTo, zonedRelativeTo, [ - 'dateAdd', - 'dateUntil' - ]); const y1 = GetSlot(duration, YEARS); const mon1 = GetSlot(duration, MONTHS); @@ -5131,87 +5124,14 @@ export function AddDurations(operation, duration, other, options) { const norm2 = TimeDuration.normalize(h2, min2, s2, ms2, µs2, ns2); const Duration = GetIntrinsic('%Temporal.Duration%'); - if (!zonedRelativeTo && !plainRelativeTo) { - if (IsCalendarUnit(largestUnit)) { - throw new RangeError('relativeTo is required for years, months, or weeks arithmetic'); - } - const { days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = BalanceTimeDuration( - norm1.add(norm2).add24HourDays(d1 + d2), - largestUnit - ); - return new Duration(0, 0, 0, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds); - } - - if (plainRelativeTo) { - const dateDuration1 = new Duration(y1, mon1, w1, d1, 0, 0, 0, 0, 0, 0); - const dateDuration2 = new Duration(y2, mon2, w2, d2, 0, 0, 0, 0, 0, 0); - const intermediate = AddDate(calendarRec, plainRelativeTo, dateDuration1); - const end = AddDate(calendarRec, intermediate, dateDuration2); - - const dateLargestUnit = LargerOfTwoTemporalUnits('day', largestUnit); - const differenceOptions = ObjectCreate(null); - differenceOptions.largestUnit = dateLargestUnit; - const untilResult = DifferenceDate(calendarRec, plainRelativeTo, end, differenceOptions); - 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 - let hours, minutes, seconds, milliseconds, microseconds, nanoseconds; - ({ days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = BalanceTimeDuration( - norm1.add(norm2).add24HourDays(days), - largestUnit - )); - return new Duration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds); - } - - // zonedRelativeTo is defined - const TemporalInstant = GetIntrinsic('%Temporal.Instant%'); - const calendar = GetSlot(zonedRelativeTo, CALENDAR); - const startInstant = GetSlot(zonedRelativeTo, INSTANT); - let startDateTime; - if (IsCalendarUnit(largestUnit) || largestUnit === 'day') { - startDateTime = GetPlainDateTimeFor(timeZoneRec, startInstant, calendar); + if (IsCalendarUnit(largestUnit)) { + throw new RangeError('For years, months, or weeks arithmetic, use date arithmetic relative to a starting point'); } - const intermediateNs = AddZonedDateTime( - startInstant, - timeZoneRec, - calendarRec, - y1, - mon1, - w1, - d1, - norm1, - startDateTime - ); - const endNs = AddZonedDateTime( - new TemporalInstant(intermediateNs), - timeZoneRec, - calendarRec, - y2, - mon2, - w2, - d2, - norm2 - ); - if (largestUnit !== 'year' && largestUnit !== 'month' && largestUnit !== 'week' && largestUnit !== 'day') { - // The user is only asking for a time difference, so return difference of instants. - const norm = TimeDuration.fromEpochNsDiff(endNs, GetSlot(zonedRelativeTo, EPOCHNANOSECONDS)); - const { hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = BalanceTimeDuration(norm, largestUnit); - return new Duration(0, 0, 0, 0, hours, minutes, seconds, milliseconds, microseconds, nanoseconds); - } - - const { years, months, weeks, days, norm } = DifferenceZonedDateTime( - GetSlot(zonedRelativeTo, EPOCHNANOSECONDS), - endNs, - timeZoneRec, - calendarRec, - largestUnit, - ObjectCreate(null), - startDateTime + const { days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = BalanceTimeDuration( + norm1.add(norm2).add24HourDays(d1 + d2), + largestUnit ); - const { hours, minutes, seconds, milliseconds, microseconds, nanoseconds } = BalanceTimeDuration(norm, 'hour'); - return new Duration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds); + return new Duration(0, 0, 0, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds); } export function AddDurationToOrSubtractDurationFromInstant(operation, instant, durationLike) { diff --git a/spec/duration.html b/spec/duration.html index e442f0b98e..0a5bb6412d 100644 --- a/spec/duration.html +++ b/spec/duration.html @@ -386,26 +386,26 @@
The `Temporal.Duration.prototype.add` method performs the following steps when called:
The `Temporal.Duration.prototype.subtract` method performs the following steps when called: