Skip to content

Commit

Permalink
Separate 'overflow' from 'disambiguation'
Browse files Browse the repository at this point in the history
"Overflow" is how to deal with out-of-range values for a particular unit
(for example, minute 61, or day 0 of the month.) "Disambiguation" is how
to deal with skipped or double wall clock times during time zone
transitions. Previously, they were both called "disambiguation".

This split is necessary, because for the zoned type being added, we will
need to provide both options to the same function call.

Closes: #607
  • Loading branch information
ptomato committed Sep 10, 2020
1 parent 7e3d5f2 commit 3d38008
Show file tree
Hide file tree
Showing 91 changed files with 1,959 additions and 1,967 deletions.
42 changes: 21 additions & 21 deletions docs/balancing.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ Take, for example, a duration of 100 seconds: `Temporal.Duration.from({ seconds:
100 seconds is equal to 1 minute and 40 seconds. Instead of clipping this to 59 seconds it's more likely that we would want to "balance" it, wrapping 60 seconds around to 0 to make 1 minute and 40 seconds.
However, it's an equally valid use case to want to track the duration of something in seconds only, and not balance the duration to 1 minute and 40 seconds.

What is done with the duration depends on the `disambiguation` option when creating the `Temporal.Duration` object.
What is done with the duration depends on the `overflow` option when creating the `Temporal.Duration` object.
Unlike with the other Temporal types, constrain mode doesn't clip the seconds value to 59, and reject mode doesn't throw.
They just leave the value as it is.
Balance mode lets you opt in to the balancing behaviour:

```javascript
d = Temporal.Duration.from({ seconds: 100 }, { disambiguation: 'balance' });
d = Temporal.Duration.from({ seconds: 100 }, { overflow: 'balance' });
d.minutes // => 1
d.seconds // => 40
```
Expand All @@ -28,7 +28,7 @@ No balancing is ever performed between years, months, and days, because such con
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.

`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.
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 overflow option.

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` or `-Number.MAX_VALUE`.
Expand All @@ -49,7 +49,7 @@ 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.
Here, we make a distinction between "necessary balancing" and "optional 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.
In order to accommodate this, the `overflow` option when performing arithmetic on `Temporal.Duration`s is different from all the other arithmetic methods' overflow 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 `reject` mode is also available, and does the same thing as `constrain` but throws on integer overflow.
Expand All @@ -58,11 +58,11 @@ The default is `constrain` mode.
The `balance` mode is only provided for convenience, since the following code snippets give the same result:

```javascript
duration3 = duration1.minus(duration2, { disambiguation: 'balance' });
duration3 = duration1.minus(duration2, { overflow: 'balance' });

tmp = duration1.minus(duration2);
// duration3 = Temporal.Duration.from(tmp, { disambiguation: 'balance' }); - FIXME: https://github.com/tc39/proposal-temporal/issues/232
duration3 = tmp.with(tmp, { disambiguation: 'balance' });
duration3 = Temporal.Duration.from(tmp, { overflow: 'balance' });
duration3 = tmp.with(tmp, { overflow: 'balance' });
```

Here are some more examples of what each mode does:
Expand All @@ -71,44 +71,44 @@ Here are some more examples of what each mode does:
// Simple, no balancing possible
one = Temporal.Duration.from({ hours: 3 });
two = Temporal.Duration.from({ hours: 1 });
one.minus(two); // => PT2H
one.minus(two, { disambiguation: 'balance' }); // => PT2H
one.minus(two); // => PT2H
one.minus(two, { overflow: 'balance' }); // => PT2H

// Balancing possible but not necessary
one = Temporal.Duration.from({ minutes: 180 });
two = Temporal.Duration.from({ minutes: 60 });
one.minus(two); // => PT120M
one.minus(two, { disambiguation: 'balance' }); // => PT2H
one.minus(two); // => PT120M
one.minus(two, { overflow: 'balance' }); // => PT2H

// Some balancing necessary, more balancing possible
one = Temporal.Duration.from({ minutes: 180 });
two = Temporal.Duration.from({ seconds: 30 });
one.minus(two); // => PT179M30S
one.minus(two, { disambiguation: 'balance' }); // => PT2H59M30S
one.minus(two); // => PT179M30S
one.minus(two, { overflow: 'balance' }); // => PT2H59M30S

// Balancing necessary, result is positive
one = Temporal.Duration.from({ hours: 4, minutes: 15 });
two = Temporal.Duration.from({ hours: 2, minutes: 30 });
one.minus(two); // => PT1H45M
one.minus(two, { disambiguation: 'balance' }); // => PT1H45M
one.minus(two); // => PT1H45M
one.minus(two, { overflow: 'balance' }); // => PT1H45M

// Result is negative
one = Temporal.Duration.from({ hours: 2, minutes: 30 });
two = Temporal.Duration.from({ hours: 3 });
one.minus(two); // -PT30M
one.minus(two, { disambiguation: 'balance' }); // -PT30M
one.minus(two); // -PT30M
one.minus(two, { overflow: 'balance' }); // -PT30M

// Unbalanceable units, but also no balancing possible
one = Temporal.Duration.from({ months: 3, days: 15 });
two = Temporal.Duration.from({ days: 10 });
one.minus(two); // => P3M5D
one.minus(two, { disambiguation: 'balance' }); // => P3M5D
one.minus(two); // => P3M5D
one.minus(two, { overflow: 'balance' }); // => P3M5D

// Result is in theory positive in the ISO calendar, but unbalanceable units
one = Temporal.Duration.from({ months: 3, days: 15 });
two = Temporal.Duration.from({ days: 30 });
one.minus(two); // throws
one.minus(two, { disambiguation: 'balance' }); // throws
one.minus(two); // throws
one.minus(two, { overflow: 'balance' }); // throws
```

## Serialization
Expand Down
12 changes: 6 additions & 6 deletions docs/calendar.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ They provide a way to construct other Temporal objects from values in the calend
- `fields` (object): An object with properties similar to what is passed to `Temporal.Date.from()`, `Temporal.YearMonth.from()`, or `Temporal.MonthDay.from()`, respectively.
- `options`: (object): An object with properties representing options for constructing the Temporal object.
The following options are recognized:
- `disambiguation` (string): How to deal with out-of-range values in `fields`.
- `overflow` (string): How to deal with out-of-range values in `fields`.
Allowed values are `constrain`, `balance`, and `reject`.
The default is `constrain`.
- `constructor` (function): The constructor function of the Temporal type to construct.
Expand All @@ -191,7 +191,7 @@ For example:
```javascript
date = Temporal.Date.from(
{ year: 5780, month: 9, day: 6, calendar: 'hebrew' },
{ disambiguation: 'reject' }
{ overflow: 'reject' }
);
date.year // => 5780
date.month // => 9
Expand All @@ -201,7 +201,7 @@ date.toString() // => 2020-05-29[c=hebrew]
// same result, but calling the method directly:
date = Temporal.Calendar.from('hebrew').dateFromFields(
{ year: 5780, month: 9, day: 6 },
{ disambiguation: 'reject' },
{ overflow: 'reject' },
Temporal.Date
);
date.year // => 5780
Expand All @@ -222,7 +222,7 @@ They provide a way to do date arithmetic in the calendar's date reckoning.
- `duration` (`Temporal.Duration`): A duration to add or subtract from `date`.
- `options` (object): An object with properties representing options for performing the addition or subtraction.
The following options are recognized:
- `disambiguation` (string): How to deal with out-of-range values in the result of the addition or subtraction.
- `overflow` (string): How to deal with out-of-range values in the result of the addition or subtraction.
Allowed values are `constrain` and `reject`.
The default is `constrain`.
- `constructor` (function): The constructor function of the Temporal type to construct.
Expand All @@ -237,7 +237,7 @@ For example:
```javascript
date = Temporal.Date.from('2020-05-29').withCalendar('islamic').plus(
Temporal.Duration.from({ months: 1 }),
{ disambiguation: 'reject' }
{ overflow: 'reject' }
);
date.year // => 1441
date.month // => 11
Expand All @@ -248,7 +248,7 @@ date.toString() // => 2020-06-28[c=islamic]
date = Temporal.Calendar.from('islamic').datePlus(
Temporal.Date.from('2020-05-29'),
Temporal.Duration.from({ months: 1 }),
{ disambiguation: 'reject' },
{ overflow: 'reject' },
Temporal.Date
);
date.year // => 1441
Expand Down
2 changes: 1 addition & 1 deletion docs/cookbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ Here are some examples of taking an existing date, and adjusting the day of the

Likewise, here are some examples of taking an existing date and adjusting the month, but keeping the day and year the same.

Depending on the behaviour you want, you will need to pick the right `disambiguation` option, but the default of `"constrain"` should be correct for most cases.
Depending on the behaviour you want, you will need to pick the right `overflow` option, but the default of `"constrain"` should be correct for most cases.

```javascript
{{cookbook/adjustMonth.mjs}}
Expand Down
2 changes: 1 addition & 1 deletion docs/cookbook/adjustMonth.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ assert.equal(feb.toString(), '2020-02-29');
// (and throw an exception if the date doesn't exist in April):

assert.throws(() => {
date.with({ month: 4 }, { disambiguation: 'reject' });
date.with({ month: 4 }, { overflow: 'reject' });
});
40 changes: 20 additions & 20 deletions docs/date.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ A `Temporal.Date` can be converted into a `Temporal.DateTime` by combining it wi
**Returns:** a new `Temporal.Date` object.

Use this constructor if you have the correct parameters for the date already as individual number values in the ISO 8601 calendar.
Otherwise, `Temporal.Date.from()`, which accepts more kinds of input, allows inputting dates in different calendar reckonings, and allows disambiguation behaviour, is probably more convenient.
Otherwise, `Temporal.Date.from()`, which accepts more kinds of input, allows inputting dates in different calendar reckonings, and allows controlling the overflow behaviour, is probably more convenient.

All values are given as reckoned in the [ISO 8601 calendar](https://en.wikipedia.org/wiki/ISO_8601#Dates).
Together, `isoYear`, `isoMonth`, and `isoDay` must represent a valid date in that calendar, even if you are passing a different calendar as the `calendar` parameter.
Expand All @@ -51,7 +51,7 @@ date = new Temporal.Date(2020, 3, 14) // => 2020-03-14
- `thing`: The value representing the desired date.
- `options` (optional object): An object with properties representing options for constructing the date.
The following options are recognized:
- `disambiguation` (string): How to deal with out-of-range values in `thing`.
- `overflow` (string): How to deal with out-of-range values in `thing`.
Allowed values are `constrain` and `reject`.
The default is `constrain`.

Expand All @@ -64,13 +64,13 @@ A `Temporal.Date` will be constructed from these properties.

Any non-object value is converted to a string, which is expected to be in ISO 8601 format.
Any time or time zone part is optional and will be ignored.
If the string isn't valid according to ISO 8601, then a `RangeError` will be thrown regardless of the value of `disambiguation`.
If the string isn't valid according to ISO 8601, then a `RangeError` will be thrown regardless of the value of `overflow`.

The `disambiguation` option works as follows:
The `overflow` option works as follows:
- In `constrain` mode (the default), any out-of-range values are clamped to the nearest in-range value.
- In `reject` mode, the presence of out-of-range values will cause the function to throw a `RangeError`.

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 function will throw a `RangeError` regardless of `disambiguation`.
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 function will throw a `RangeError` regardless of `overflow`.

> **NOTE**: The allowed values for the `thing.month` property start at 1, which is different from legacy `Date` where months are represented by zero-based indices (0 to 11).
Expand All @@ -92,14 +92,14 @@ date = Temporal.Date.from({ year: 1427, month; 8, day: 1, calendar }); // => 20
date = Temporal.Date.from({ year: 1427, month: 8, day: 1, calendar: 'islamic' });
// => same as above

// Different disambiguation modes
date = Temporal.Date.from({ year: 2001, month: 13, day: 1 }, { disambiguation: 'constrain' })
// Different overflow modes
date = Temporal.Date.from({ year: 2001, month: 13, day: 1 }, { overflow: 'constrain' })
// => 2001-12-01
date = Temporal.Date.from({ year: 2001, month: -1, day: 1 }, { disambiguation: 'constrain' })
date = Temporal.Date.from({ year: 2001, month: -1, day: 1 }, { overflow: 'constrain' })
// => 2001-01-01
date = Temporal.Date.from({ year: 2001, month: 13, day: 1 }, { disambiguation: 'reject' })
date = Temporal.Date.from({ year: 2001, month: 13, day: 1 }, { overflow: 'reject' })
// throws
date = Temporal.Date.from({ year: 2001, month: -1, day: 1 }, { disambiguation: 'reject' })
date = Temporal.Date.from({ year: 2001, month: -1, day: 1 }, { overflow: 'reject' })
// throws
```

Expand Down Expand Up @@ -274,7 +274,7 @@ date.with({year: 2100}).leapYear // => false
- `dateLike` (object): an object with some or all of the properties of a `Temporal.Date`.
- `options` (optional object): An object with properties representing options for the operation.
The following options are recognized:
- `disambiguation` (string): How to deal with out-of-range values.
- `overflow` (string): How to deal with out-of-range values.
Allowed values are `constrain` and `reject`.
The default is `constrain`.

Expand All @@ -284,7 +284,7 @@ This method creates a new `Temporal.Date` which is a copy of `date`, but any pro

Since `Temporal.Date` objects are immutable, use this method instead of modifying one.

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`.
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 `overflow`.

> **NOTE**: The allowed values for the `dateLike.month` property start at 1, which is different from legacy `Date` where months are represented by zero-based indices (0 to 11).
Expand Down Expand Up @@ -327,7 +327,7 @@ date.withCalendar('iso8601') // => 2006-08-24
- `duration` (object): A `Temporal.Duration` object or a duration-like object.
- `options` (optional object): An object with properties representing options for the addition.
The following options are recognized:
- `disambiguation` (optional string): How to deal with additions that result in out-of-range values.
- `overflow` (optional string): How to deal with additions that result in out-of-range values.
Allowed values are `constrain` and `reject`.
The default is `constrain`.

Expand All @@ -339,11 +339,11 @@ The `duration` argument is an object with properties denoting a duration, such a

Some additions may be ambiguous, because months have different lengths.
For example, adding one month to August 31 would result in September 31, which doesn't exist.
For these cases, the `disambiguation` option tells what to do:
For these cases, the `overflow` option tells what to do:
- In `constrain` mode (the default), out-of-range values are clamped to the nearest in-range value.
- In `reject` mode, an addition that would result in an out-of-range value fails, and a `RangeError` is thrown.

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`.
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 `overflow`.

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

Expand All @@ -354,7 +354,7 @@ date.plus({years: 20, months: 4}) // => 2026-12-24

date = Temporal.Date.from('2019-01-31')
date.plus({ months: 1 }) // => 2019-02-28
date.plus({ months: 1 }, { disambiguation: 'reject' }) // => throws
date.plus({ months: 1 }, { overflow: 'reject' }) // => throws
```

### date.**minus**(_duration_: object, _options_?: object) : Temporal.Date
Expand All @@ -363,7 +363,7 @@ date.plus({ months: 1 }, { disambiguation: 'reject' }) // => throws
- `duration` (object): A `Temporal.Duration` object or a duration-like object.
- `options` (optional object): An object with properties representing options for the subtraction.
The following options are recognized:
- `disambiguation` (string): How to deal with subtractions that result in out-of-range values.
- `overflow` (string): How to deal with subtractions that result in out-of-range values.
Allowed values are `constrain` and `reject`.
The default is `constrain`.

Expand All @@ -375,11 +375,11 @@ The `duration` argument is an object with properties denoting a duration, such a

Some subtractions may be ambiguous, because months have different lengths.
For example, subtracting one month from July 31 would result in June 31, which doesn't exist.
For these cases, the `disambiguation` option tells what to do:
For these cases, the `overflow` option tells what to do:
- In `constrain` mode (the default), out-of-range values are clamped to the nearest in-range value.
- In `reject` mode, an addition that would result in an out-of-range value fails, and a `RangeError` is thrown.

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`.
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 `overflow`.

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

Expand All @@ -390,7 +390,7 @@ date.minus({years: 20, months: 4}) // => 1986-04-24

date = Temporal.Date.from('2019-03-31')
date.minus({ months: 1 }) // => 2019-02-28
date.minus({ months: 1 }, { disambiguation: 'reject' }) // => throws
date.minus({ months: 1 }, { overflow: 'reject' }) // => throws
```

### date.**difference**(_other_: Temporal.Date, _options_?: object) : Temporal.Duration
Expand Down
Loading

0 comments on commit 3d38008

Please sign in to comment.