Skip to content

Commit

Permalink
Negative durations
Browse files Browse the repository at this point in the history
Changes Temporal.Duration to allow negative values in the fields, as
long as all the fields have the same sign. Adds negated() and abs()
methods to Temporal.Duration, and a sign property.

On all types with arithmetic, subtracting a negative duration is
equivalent to adding the absolute value of that duration, and adding a
negative duration is equivalent to subtracting the absolute value of
that duration.

Closes: #782
  • Loading branch information
ptomato committed Aug 27, 2020
1 parent 8b354c8 commit bb75caf
Show file tree
Hide file tree
Showing 38 changed files with 1,174 additions and 467 deletions.
4 changes: 4 additions & 0 deletions docs/absolute.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,8 @@ If you need to do this, convert the `Temporal.Absolute` to a `Temporal.DateTime`

If the result is earlier or later than the range that `Temporal.Absolute` can represent (approximately half a million years centered on the [Unix epoch](https://en.wikipedia.org/wiki/Unix_time)), a `RangeError` will be thrown.

Adding a negative duration is equivalent to subtracting the absolute value of that duration.

Example usage:
```js
// Temporal.Absolute representing five hours from now
Expand All @@ -279,6 +281,8 @@ If you need to do this, convert the `Temporal.Absolute` to a `Temporal.DateTime`

If the result is earlier or later than the range that `Temporal.Absolute` can represent (approximately half a million years centered on the [Unix epoch](https://en.wikipedia.org/wiki/Unix_time)), a `RangeError` will be thrown.

Subtracting a negative duration is equivalent to adding the absolute value of that duration.

Example usage:
```js
// Temporal.Absolute representing this time yesterday
Expand Down
26 changes: 13 additions & 13 deletions docs/balancing.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,34 +27,34 @@ Therefore, any `Duration` object with nonzero years or months can refer to a dif
No balancing is ever performed between years, months, and days, because such conversion would be ambiguous.
If you need such a conversion, you must implement it yourself, since the rules can depend on the start date and the calendar in use.

Negative values are never allowed as `Temporal.Duration` fields, so passing one as an argument to `new Temporal.Duration()` or as a property in the object passed to `Temporal.Duration.from()` will always throw an exception, regardless of the disambiguation mode.
`Temporal.Duration` fields are not allowed to have mixed signs.
For example, passing one positive and one negative argument to `new Temporal.Duration()` or as properties in the object passed to `Temporal.Duration.from()` will always throw an exception, regardless of the disambiguation mode.

Therefore, the only case where constrain and reject mode have any effect when creating a duration, is integer overflow.
If one of the values overflows, constrain mode will cap it to `Number.MAX_VALUE`.
If one of the values overflows, constrain mode will cap it to `Number.MAX_VALUE` or `-Number.MAX_VALUE`.

## Duration arithmetic

When adding two `Temporal.Duration`s, the situation is much the same as for constructing one.
It's not possible to end up with a negative value when adding two valid durations, nor is any balancing needed, so the disambiguation mode only determines what to do in the case of integer overflow.
When adding two `Temporal.Duration`s of opposite sign, or subtracting two durations of the same sign, the situation is different.
As well as the same possibility of integer overflow, the fields may also need to be balanced.

The situation is very different for subtraction.
Consider a duration of 90 minutes, from which is subtracted 30 seconds.
Subtracting the fields directly would result in 90 minutes and −30 seconds, which is invalid.
Therefore, it's necessary to balance the seconds with the minutes, resulting in 89 minutes and 30 seconds.

By default, the fields of the resulting duration are only converted between each other if one unit is negative but a larger unit is positive, in which case the smaller is balanced with the larger to avoid having negative-valued fields.
By default, the fields of the resulting duration are only converted between each other if one unit is negative but a larger unit is positive, or vice versa, in which case the smaller is balanced with the larger to avoid having mixed-sign fields.

That's not all the balancing that _could_ be done on the resulting value.
It could further be balanced into 1 hour, 29 minutes, and 30 seconds.
However, that would likely conflict with the intention of having a duration of 90 minutes in the first place, so this should be behaviour that the Temporal user opts in to.
With subtraction, we make a distinction between "necessary balancing" and "optional balancing".
Here, we make a distinction between "necessary balancing" and "optional balancing".

In order to accommodate this, the `disambiguation` option when subtracting `Temporal.Duration`s is different from all the other arithmetic methods' disambiguation options.
Necessary balancing is called `balanceConstrain` mode, because values are constrained to be non-negative through balancing.
In order to accommodate this, the `disambiguation` option when performing arithmetic on `Temporal.Duration`s is different from all the other arithmetic methods' disambiguation options.
Necessary balancing is called `constrain` mode, because values are constrained to be non-negative through balancing.
Optional balancing is called `balance` mode.
The usual `constrain` and `reject` modes are not available.
The usual `reject` mode is also available, and does the same thing as `constrain` but throws on integer overflow.

The default is `balanceConstrain` mode.
The default is `constrain` mode.
The `balance` mode is only provided for convenience, since the following code snippets give the same result:

```javascript
Expand Down Expand Up @@ -95,8 +95,8 @@ one.minus(two, { disambiguation: 'balance' }); // => PT1H45M
// Result is negative
one = Temporal.Duration.from({ hours: 2, minutes: 30 });
two = Temporal.Duration.from({ hours: 3 });
one.minus(two); // throws
one.minus(two, { disambiguation: 'balance' }); // throws
one.minus(two); // -PT30M
one.minus(two, { disambiguation: 'balance' }); // -PT30M

// Unbalanceable units, but also no balancing possible
one = Temporal.Duration.from({ months: 3, days: 15 });
Expand Down
4 changes: 4 additions & 0 deletions docs/date.md
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,8 @@ For these cases, the `disambiguation` option tells what to do:

Additionally, if the result is earlier or later than the range of dates that `Temporal.Date` can represent (approximately half a million years centered on the [Unix epoch](https://en.wikipedia.org/wiki/Unix_time)), then this method will throw a `RangeError` regardless of `disambiguation`.

Adding a negative duration is equivalent to subtracting the absolute value of that duration.

Usage example:
```javascript
date = Temporal.Date.from('2006-08-24');
Expand Down Expand Up @@ -357,6 +359,8 @@ For these cases, the `disambiguation` option tells what to do:

Additionally, if the result is earlier or later than the range of dates that `Temporal.Date` can represent (approximately half a million years centered on the [Unix epoch](https://en.wikipedia.org/wiki/Unix_time)), then this method will throw a `RangeError` regardless of `disambiguation`.

Subtracting a negative duration is equivalent to adding the absolute value of that duration.

Usage example:
```javascript
date = Temporal.Date.from('2006-08-24');
Expand Down
4 changes: 4 additions & 0 deletions docs/datetime.md
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,8 @@ For these cases, the `disambiguation` option tells what to do:

Additionally, if the result is earlier or later than the range of dates that `Temporal.DateTime` can represent (approximately half a million years centered on the [Unix epoch](https://en.wikipedia.org/wiki/Unix_time)), then this method will throw a `RangeError` regardless of `disambiguation`.

Adding a negative duration is equivalent to subtracting the absolute value of that duration.

Usage example:
```javascript
dt = new Temporal.DateTime(1995, 12, 7, 3, 24, 30, 0, 3, 500);
Expand Down Expand Up @@ -421,6 +423,8 @@ For these cases, the `disambiguation` option tells what to do:

Additionally, if the result is earlier or later than the range of dates that `Temporal.DateTime` can represent (approximately half a million years centered on the [Unix epoch](https://en.wikipedia.org/wiki/Unix_time)), then this method will throw a `RangeError` regardless of `disambiguation`.

Subtracting a negative duration is equivalent to adding the absolute value of that duration.

Usage example:
```javascript
dt = new Temporal.DateTime(1995, 12, 7, 3, 24, 30, 0, 3, 500);
Expand Down
Loading

0 comments on commit bb75caf

Please sign in to comment.