Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: rounding and balancing for Duration type (replaces #789) #856

Closed
justingrant opened this issue Aug 28, 2020 · 27 comments · Fixed by #1163
Closed

Proposal: rounding and balancing for Duration type (replaces #789) #856

justingrant opened this issue Aug 28, 2020 · 27 comments · Fixed by #1163
Assignees
Labels
documentation Additions to documentation non-prod-polyfill THIS POLYFILL IS NOT FOR PRODUCTION USE! spec-text Specification text involved

Comments

@justingrant
Copy link
Collaborator

justingrant commented Aug 28, 2020

This issue is based on what remained in #789 after extracting rounding of non-Duration types into #827. The proposal below focuses only on changes to the Duration type itself. Please review #827 before this one.

Proposal Summary

  • Extend the Temporal.Duration type with a round() method to perform balancing and/or rounding. Most rounding behavior is already defined in Proposal: Rounding method (and rounding for difference and toString) for non-Duration types #827, so this proposal focuses on what's different when applied to the Duration type.
  • Add rounding options to plus and minus to ensure that duration arithmetic can be done safely even with units that have variable lengths like months, days (if there's DST), and years (for some non-ISO calendars).
  • Add a compare method which takes a subset of the options above.

Use Cases

Common Use Cases (should have ergonomic solutions for these)

  • Normalization of existing durations, e.g. PT130M => PT2H10M
  • Rounding to nearest, e.g. PT10M52S => PT11M
  • Rounding up, e.g. PT10M52S => PT11M
  • Truncation (rounding towards zero), e.g. PT10M52S => PT10M
  • Format a duration in a normalized text format
  • Calculating totals (e.g. how many total seconds in PT2H34M18S)
  • Normalization that takes DST into account, e.g. convert PT1756H into days, assuming starting at midnight on a day that DST ends.
  • Normalization that ignores DST and treats every day as 24 hours.
  • Normalize days into months/years, e.g. P190D -> P6M12D (requires knowing the starting point and, optionally, the calendar)
  • Rounding to multiples of a unit, e.g. round to nearest 15 minutes or truncate to integer number of 3-month quarters
  • Non-ISO calendar-aware normalization of days into months, or months into years

Less Common Use Cases (mostly not supported)

  • (not supported, but could add later via option?) Fractional remainders, e.g. "How many days are in 36 hours?" => 1.5 days.
  • (not supported) "Get Remainder" / Top-end truncation, e.g. get time-only portion of a Duration, or fetch fractional seconds units.
  • (partially supported) Get a date-only or time-only portion of a duration. The former is easy today via {largestUnit: 'days'}. The latter is really just a special case of the "Get Remainder" case above.
  • (not supported) "Disjoint units" - some platforms store date/time values as a tuple of a "large" and "small" unit, where "large" is typically a day and "small" is usually the smallest unit of supported precision. For example, SQL Server's legacy date/time types:

(not supported) Values with the datetime data type are stored internally by the SQL Server 2005 Database Engine as two 4-byte integers. The first 4 bytes store the number of days before or after the base date: January 1, 1900. The base date is the system reference date. The other 4 bytes store the time of day represented as the number of 1/300-second units after midnight.

The smalldatetime data type stores dates and times of day with less precision than datetime. The Database Engine stores smalldatetime values as two 2-byte integers. The first 2 bytes store the number of days after January 1, 1900. The other 2 bytes store the number of minutes since midnight.

Open Issues

  • How to handle the case where a duration is currently balanced, and then it's rounded away from zero? For example, PT2H59M55S rounded to the nearest minute without balancing would be PT2H60M. This seems unexpected. Should the default be to balance, and users can opt out of balancing instead of opting in? Or should it always keep balanced durations balanced? EDIT 2020-10-08 Answer: Yes. Output durations will always be balanced except for the largest unit and except weeks will usually be zero. See below for more details.
  • Should Duration's plus and minus methods accept a largestUnit of 'months' or 'years' if relativeTo is omitted? If yes, then there won't be any way to know at development time whether it will fail at runtime. If no, then failures will be found at development time, with the downside of disallowing cases which require no balancing like P1Y2M + P2Y3M? My inclination is to disallow these units in this case, to avoid unpredictable runtime behavior. See (4.4) below. EDIT 2020-10-08 Answer: we will require a relativeTo for these cases.

Proposal

1. The Duration type will add a round() method that generally matches the behavior of round() on other types.

  • 1.1 The round method will have only one optional parameter: an options bag.
  • 1.2 Options are a superset of options used in the difference methods of Absolute, DateTime, etc. See Proposal: Rounding method (and rounding for difference and toString) for non-Duration types #827 for more info on options behavior and naming.
    • 1.2.1 largestUnit and smallestUnit will match the behavior defined in Proposal: Rounding method (and rounding for difference and toString) for non-Duration types #827 for the difference method of DateTime, including behavior affecting weeks as defined in Proposal: Rounding method (and rounding for difference and toString) for non-Duration types #827 section (7.7) EXCEPT...
      • 1.2.1.1 EDIT 2020-10-06 ...the default for smallestUnit / largestUnit options depends on the relativeTo option.
      • 1.2.1.2 EDIT 2020-10-06 If largestUnit is undefined or 'auto' then the default largestUnit will be the largest nonzero unit in the duration for round or the largest unit in either input for plus and minus. In other words, the default behavior is to limit durations to the largest unit that they started out with, with balancing happening underneath that unit. If the caller wants the equivalent of the now-removed overflow: 'balance' option to expand the duration beyond its current largest unit, then the caller should explicitly set largestUnit (and relativeTo if needed).
    • 1.2.1.3 EDIT 2020-10-08 If weeks is nonzero in the input of round (or either input of plus/minus) then weeks will be filled in the output if needed. If weeks is zero in the input(s) then behavior matches behavior of non-Duration types which is that weeks will only be filled in the output if the user opts in via setting largestUnit: 'weeks' or smallestUnit: 'weeks'.
    • 1.2.2 roundingMode and roundingIncrement will match the behavior defined in Proposal: Rounding method (and rounding for difference and toString) for non-Duration types #827, EXCEPT...
      • 1.2.2.1 The 'floor' and 'trunc rounding modes will behave differently in Duration, but they behave the same in non-Duration types where zero and negative infinity are considered the same.
    • 1.2.3 As discussed in Proposal: Rounding method (and rounding for difference and toString) for non-Duration types #827, the behavior of these options will align with Intl.NumberFormatV3 for decisions around option naming, which rounding modes are supported, etc.
  • 1.3 Duration's round will add EDIT 2020-10-08 a new option: relativeTo. two additional options: relativeTo and overflow.

2. No overflow option EDIT 2020-10-06: removed this option after discussion with @ptomato

  • 2.1 The overflow: 'balance' | 'constrain' option behaves like the same option on from.
  • 2.2 If 'balance' then the resulting duration will be balanced.
  • 2.3 If 'constrain' (the default) will do no balancing.
  • 2.4 Duration's overflow: 'constrain' option in plus and minus is currently used to control whether the output is fully balanced or minimally balanced. This behavior has a few problems, especially after rounding is in the mix:
    • 2.4.1 Rounding forces at least some balancing anyways (e.g. 23.6 hours=>1 day) and it will be hard for users to understand why balancing due to rounding is performed but balancing down from larger units is not performed.
    • 2.4.2 It's not clear that constrain matches the most common use case for unbalanced durations which is where only the largest unit is unbalanced, e.g. 45 days, 3 hours, and 10 minutes, or @jasonwilliams's case where the BBC measures all program lengths in seconds. It's not clear that "unbalanced in the middle" durations like "45 days and 180 minutes" are a real use case that we need to support in the results of plus, minus, and round.
    • 2.4.3 Even before adding rounding, the "balance constrain" algorithm was one of the hardest concepts in all of Temporal to understand and explain.
  • 2.5 For these reasons, there will be no overflow options on plus, minus, or round. Instead, we'll optimize for the "unbalanced largest unit" use case by automatically setting the largestUnit default to prevent durations from growing into larger units by default. See (3.5.1) below for more details.
    • 2.5.1 Note that the overflow option was already in plus and minus before this proposal. It will be removed from those methods.
  • 2.6 This change implies simplification in the docs for the duration balancing page. A PR is coming soon for that page.

3. relativeTo option

  • 3.1 This option defines the reference point used when balancing across a boundary where the number of smaller units per next-larger unit can vary. These boundaries include hours/days (because DST), days/months (month lengths vary), and months/years (non-ISO-calendar years may have 12 or 13 months).
    • 3.1.1 Example 2: P40D balances to P1M9D if relativeTo is 2020-01-01.
    • 3.1.2 Example 1: P40D balances to P1M11D if relativeTo is 2020-02-01
  • 3.2 In non-Duration types, the starting point is this so relativeTo isn't needed. That's why it was omitted from Proposal: Rounding method (and rounding for difference and toString) for non-Duration types #827.
  • 3.3 This option can be:
    • 3.3.1 A Temporal instance of DateTime or LocalDateTime type.
      • 3.3.1.1 If it's a DateTime all days will be assumed to be 24 hours long.
      • 3.3.1.2 However, if it's a LocalDateTime, then the time zone will be used to adjust for DST or other offset changes.
    • 3.3.2 A property bag object or string that can be that can be parsed into a DateTime or LocalDateTime
    • 3.3.3 undefined, which allows rounding durations with days or smaller units without worrying about DST or starting points. This is the default behavior if no options bag is provided.
      • 3.3.3.1 If undefined is used, then all days will be assumed to be 24 hours long.
      • 3.3.3.2 If undefined is used, then a RangeError will be thrown if the duration has a non-zero months or years. TS typings should enforce this restriction.
      • 3.3.3.3 EDIT 2020-10-06 If undefined is used, then a RangeError will be thrown if largestUnit in the input is 'weeks' or larger.
    • 3.3.4 Any other types will result in a RangeError
  • 3.4 If the value is an object property bag or string, then the value will be handled as follows:
    • 3.4.1 If the value is valid for use with LocalDateTime.from, then construct a new LocalDateTime instance from this value.
    • 3.4.2 Otherwise, if the value is valid for use with DateTime.from, then construct a new DateTime instance from this value.
    • 3.4.4 Otherwise throw a RangeError
  • 3.5 If relativeTo IS NOT undefined, then the defaults for for largestUnit and smallestUnit will be the full range of units: {largestUnit: 'years', smallestUnit: 'nanoseconds'}.
    • 3.5.1 However, if relativeTo IS undefined then defaults will be {largestUnit: 'hours', smallestUnit: 'nanoseconds'}. These defaults avoid variable-length units like months or days in a DST time zone. EDIT 2020-10-06: defaults are no longer controlled by relativeTo. See (1.2.1.2).

4. Usage in plus and minus

  • 4.1 Currently, duration arithmetic is limited to weeks or smaller units, and days are always assumed to be 24 hours which won't be accurate in a DST context. To resolve these limitations, one option would be to remove Duration arithmetic methods. Developers could use other types instead. For example, dur1.minus(dur2) could be replaced by ldt.plus(dur1).minus(dur2).difference(ldt).
  • 4.2 Another option, which is proposed below, is to add the same options available on round (including relativeTo) to plus and minus too. This would solve the DST issue and would also allow duration arithmetic to include all units including months and years.
  • 4.3 The relativeTo option is applied to this, not to other. This allows chaining multiple operations with the same relativeTo, e.g. dur1.plus(dur2, {relativeTo: ldt}).plus(dur3, {relativeTo: ldt}).
  • 4.4 If relativeTo is omitted, then largestUnit must be 'weeks' or smaller, or a RangeError will be thrown
  • 4.5 EDIT 2020-10-06 If relativeTo is undefined, then:
    • 4.5.1 The default largestUnit will be the largest non-zero unit in either input. (see 3.5.1 for more details)
    • 4.5.2 Throw a RangeError if either input has a nonzero value in 'weeks' or larger units OR if largestUnit is weeks or larger. This matches behavior of round (see 3.3.3.2 and 3.3.3.3) and compare (see 5.2).

5. Usage with compare

  • 5.1 The compare algorithm is simple: round down nanoseconds and compare the results. In order to compare across unbalanced durations and/or to adjust for DST, compare will accept a relativeTo option which will behave identically to relativeTo in other methods above.
  • 5.2 If relativeTo is undefined, then compare will throw if either duration has a non-zero unit in months or years

6. No equals

  • 6.1 We decided not to offer equals because equality is ambiguous for unbalanced durations (e.g. PT3600S vs PT60M) so we opted to simply support compare which will force users to think a little bit about what the comparison will mean.

7. We'll add an 'auto' value for largestUnit in Duration and non-Duration difference methods where it's used. 'auto', like undefined, will select the default unit. EDIT 2020-10-06: added this section

  • 7.1 On Duration methods, 'auto' means "the largest nonzero unit in the input(s)"
  • 7.2 On difference of non-Duration types 'auto' means to pick the default unit of the type:
    • 7.2.1 Instant: 'seconds'
    • 7.2.2 Date / DateTime - 'days'
    • 7.2.3 ZonedDateTime - 'hours'
    • 7.2.4 Time - 'hours'
    • 7.2.5 YearMonth - 'years'
  • 7.3 If the default chosen above is smaller than the user-provided smallestUnit then set largestUnit = smallestUnit.

8. EDIT 2020-10-06: added this section round will require either smallestUnit or largestUnit to be explicitly provided and not undefined. This is because without one of these two options, the rounding operation would be a no-op. This is analogous to how we require smallestUnit on the round methods of non-duration types. A RangeError should be thrown if neither is provided.

@justingrant
Copy link
Collaborator Author

justingrant commented Sep 13, 2020

Decisions in meeting 2020-09-11:

  • Add compare method to this proposal - see new section 5 above
  • Don't offer an equals method - see new section 6 above
  • Accept a string for relativeTo- see updated sections 3.3-3.5 above.
  • Treat Date same as DateTime for relativeTo

@ptomato ptomato self-assigned this Sep 16, 2020
@ptomato
Copy link
Collaborator

ptomato commented Sep 16, 2020

It occurred to me that we don't have to explicitly say that Temporal.Date is accepted as relativeTo. Passing a Temporal.Date is already covered by the fallback rule of "call Temporal.DateTime.from() on the value". (It results in a DateTime at midnight on that day.)

I suggest changing it to:

  • 3.3.1 A Temporal instance of DateTime or LocalDateTime type.

    • 3.3.1.1 If it's a DateTime, all days will be assumed to be 24 hours long.
    • 3.3.1.2 However, if it's a LocalDateTime, then the time zone will be used to adjust for DST or other offset changes.
  • 3.3.2 A property bag object or string that can be that can be parsed into a DateTime or LocalDateTime

  • 3.4 If the value is an object property bag or string, then the value will be handled as follows:

    • 3.4.1 If the value is valid for use with LocalDateTime.from, then construct a new LocalDateTime instance from this value.

    • 3.4.2 Otherwise, if the value is valid for use with DateTime.from, then construct a new DateTime instance from this value.

    • 3.4.4 Otherwise throw a RangeError

@justingrant
Copy link
Collaborator Author

Agreed. Updated.

@ptomato
Copy link
Collaborator

ptomato commented Sep 17, 2020

I've been trying to figure out what the correct balancing behaviour is for the years-months-weeks-days combination, for each value of largestUnit and overflow. For overflow: 'balance' I have something that seems consistent and makes sense, but for overflow: 'constrain' there is a surprising case, because unlike in difference(), you have to deal with the case where months and weeks are both given in the original duration.

It's best expressed in this table:

largestUnit ➡

overflow ⬇

years months weeks days or lower
balance

weeks down to days

months up to years

days up to years

days up to months

weeks down to days

years down to months

days up to months

years down to days

months down to days

days up to weeks

years down to days

months down to days

weeks down to days

constrain no-op years down to months

years down to days

months down to days

(this one is strange)

same as above

The strange case is something like:

d = Temporal.Duration.from({ years: 1, months; 1, weeks: 1, days: 1 });
d = d.round({ largestUnit: 'weeks', overflow: 'constrain', relativeTo: '2020-01-01' });

You get "P1W398D", i.e. one week and 398 days (366 days in 2020 + 31 days in January 2021 + 1 day in the original duration). What's surprising is that you asked for weeks to be the largest unit, i.e. higher units are balanced down into weeks, but the weeks are left untouched and you get a bunch of days.

The other option is "P57W6D", which is maybe less surprising, but then on the other hand violates the principle of overflow: 'constrain' not doing any upward balancing (2.3).

I think the former is correct, despite being surprising.

@justingrant
Copy link
Collaborator Author

I admit I haven't fully understood the problem above (will take a closer reading than I have time for tonight) but does this section help?

The referenced info in #827 section (7.7) is:

7.7 Weeks overlap with days and months in a duration. While there may be some cases where users might want weeks together with months and/or days (e.g. "3 months, 2 weeks, and 4 days") that's an uncommon case relative to the "3 months and 18 days" result that is likely expected. We'll handle these cases by setting weeks to zero when there's potential ambiguity between weeks vs. days/months. Specifically: if largestUnit is 'month' or 'year' and smallestUnit is 'day' or smaller, then weeks will always be zero and only days and/or months will be included in the result. Edge cases that are NOT ambiguous are noted below.

  • 7.7.1 If smallestUnit is 'week', then there is no ambiguity and any remainder will go into week.
  • 7.7.2 If largestUnit is 'day', then there is no ambiguity because weeks are excluded and week will be zero.
  • 7.7.3 In the degenerate case of { largestUnit: 'week', smallestUnit: 'day' } then there is also no ambiguity so both fields will be populated.

Does this help resolve the problem you found?

@ptomato
Copy link
Collaborator

ptomato commented Sep 17, 2020

It doesn't, unfortunately - the difference is that in #827 we are using the options to decide how to express a duration that wasn't previously expressed by the user, so we can simply set weeks to 0 and move on. In Duration.round(), with overflow: 'balance' we can also set weeks to 0, but with overflow: 'constrain' we've set the user's expectation that no upwards balancing takes place.

@ptomato ptomato added documentation Additions to documentation spec-text Specification text involved non-prod-polyfill THIS POLYFILL IS NOT FOR PRODUCTION USE! labels Sep 18, 2020
@ptomato
Copy link
Collaborator

ptomato commented Sep 24, 2020

3.5 If relativeTo is NOT undefined, then the defaults for for largestUnit and smallestUnit will be the full range of units: {largestUnit: 'years', smallestUnit: 'nanoseconds'}.

  • 3.5.1 However, if relativeTo is undefinedthen defaults will be{largestUnit: 'hours', smallestUnit: 'nanoseconds'}`. These defaults avoid variable-length units like months or days in a DST time zone.

I think we should reconsider this. If we implement this as described, then the method will attempt to balance any existing days, weeks, months, and years, down to hours, and fail because there is no relativeTo.

I'd propose making the default largestUnit always years, but just skip the balancing between hours, days, weeks, months, and years if relativeTo is not given. That would make the option simpler to explain to users, as well.

@justingrant
Copy link
Collaborator Author

justingrant commented Sep 24, 2020 via email

@ptomato
Copy link
Collaborator

ptomato commented Sep 24, 2020

Happy anniversary! No rush, I am partially off today as well so I definitely won't finish this.

@ptomato
Copy link
Collaborator

ptomato commented Sep 25, 2020

I'd propose making the default largestUnit always years, but just skip the balancing between hours, days, weeks, months, and years if relativeTo is not given. That would make the option simpler to explain to users, as well.

What I suggested above isn't a good solution either, because it brings back the problem where you sometimes get an exception and sometimes don't.

The problem is this:

d = Temporal.Duration.from({ years: 5, minutes: 5 })
d.round({ smallestUnit: 'hours' })
d.round({ largestUnit: 'hours', smallestUnit: 'hours' })

As a user, what I would expect to get out of line 2 is "5 years". What I would expect to get out of line 3 is an exception, because I asked for the largest unit in the result to be hours and that's not possible without a reference point. But according to what we have currently decided on, the result of both lines will be the same (and it's not clear to me which one it will be.)

@ptomato
Copy link
Collaborator

ptomato commented Oct 2, 2020

Unless anyone objects soon, here's what I'll proceed with:

This is what will be the result of the examples that I gave in previous comments:

d = Temporal.Duration.from({ years: 1, months; 1, weeks: 1, days: 1 });
d.round({ largestUnit: 'weeks', relativeTo: '2020-01-01' });  // => P1W398D

d = Temporal.Duration.from({ years: 5, minutes: 5 })
d.round({ smallestUnit: 'hours' })  // => P5Y
d.round({ largestUnit: 'hours', smallestUnit: 'hours' })  // => exception, relativeTo missing

@ptomato
Copy link
Collaborator

ptomato commented Oct 6, 2020

Quick poll, what do you expect the answer of this to be?

d = Temporal.Duration.from({ months: 1, weeks: 2 })
d.round({ largestUnit: 'days', smallestUnit: 'days', relativeTo: '2020-01-01' }) // => 45 days
d.round({ largestUnit: 'weeks', smallestUnit: 'days', relativeTo: '2020-01-01' }) // => ???

(And do you expect the answer to be different if overflow is balance or constrain?)

@justingrant
Copy link
Collaborator Author

justingrant commented Oct 6, 2020

Sorry I forgot to follow up on your questions!

For the case above, my expectation was that you'd get P6W3D in that case, because there's no ambiguity about what the user is asking for.

But I admit that I haven't thought through the cases for constrain to the depth that you have. So it might be easiest to resolve via a quick real-time meeting. I'd be happy to jump on a quick Zoom call with you and we could figure it out. Interested?

@ptomato
Copy link
Collaborator

ptomato commented Oct 6, 2020

Yes, let's do that! I'm about to close up shop for the day, but I'll email you about tomorrow.

@justingrant
Copy link
Collaborator Author

justingrant commented Oct 6, 2020

@ptomato and I chatted about the problems he found above. The core issue is how to deal with the case where there needs to be some balancing due to rounding but where the user has the default 'constrain' option.

In that case, there are three possible bad outcomes we could choose from:

  • a. Duration that was a balanced duration before rounding is unbalanced afterwards, e.g. PT2H59M35S => PT2H60M. I think this is bad because it's unexpected to end up with an unbalanced duration, because no other means of creating durations in Temporal generates unbalanced durations. So many users might not even know that unbalanced durations are possible.
  • b. relativeTo is required in all cases. I think this is bad because most durations are likely to be time-only, so this would be adding an ergonomics tax in those cases. Also, often there simply is no relativeTo-- for example when adding together a bunch of stopwatch readings without any associated context.
  • c. Runtime exception is thrown if relativeTo would have been required (if largestUnit >= weeks or if duration has a nonzero field >= weeks). IMHO, this is the least bad because it would only happen with very long durations which are already unusual to be doing math with. Also, AFAIK it's already similar to limits that we're already imposing on plus/minus and compare.

Here's some specific proposed changes:

  1. Remove the overflow option from all duration methods except from and with. Always balance, because when you're manipulating a duration (as opposed to just setting fields) then it's unintuitive to start with a balanced duration and end up with an unbalanced one. Also, rounding forces at least some balancing anyways (e.g. 23.6 hours=>1 day) and it will be hard for users to understand why some balancing is performed but other balancing is not.

  2. If largestUnit is undefined then the default largestUnit should be the largest nonzero unit in the duration (or the largest in either duration for plus/minus). In other words, the default behavior is to limit durations to the "height" that they started out with. If the caller wants the equivalent of overflow: 'balance' to "grow" the duration beyond its current largest unit, then the caller should explicitly set largestUnit (and relativeTo if needed).

  • 2.1 Like the non-Duration implementation, if smallestUnit is larger than the default largestUnit, then set largestUnit to smallestUnit.
  1. If relativeTo is undefined, then throw a RangeError if largestUnit is 'weeks' or larger. This is the same as what's already specified for plus/minus in (4.4) above.

  2. If relativeTo is undefined, then throw a RangeError if the input duration (either input for plus/minus) has a nonzero value in 'weeks' or larger. This is the same behavior as what's already specified for compare in (5.2) above.

  3. This proposal implies a simplification in the docs for duration balancing. I'd be happy to help with these docs changes! EDIT: see Update balancing docs page #978.

  4. We'll add an 'auto' value for largestUnit in Duration and non-Duration difference methods where it's used. 'auto' means the same as undefined.

  • 6.1 On Duration methods, 'auto' means "the largest nonzero unit in the input(s)"
  • 6.2 On difference of non-Duration types 'auto' means the default unit:
    • 6.2.1 Instant: max('seconds', smallestUnit)
    • 6.2.2 Date / DateTime - max('days', smallestUnit)
    • 6.2.3 ZDT - max('hours', smallestUnit)
    • 6.2.4 Time - 'hours'
    • 6.2.5 YearMonth - 'years'
  1. round will require either smallestUnit or largestUnit to be explicitly provided (not undefined). This is because without one of these two options, the rounding operation would be a no-op. This is analogous to how we require smallestUnit on the round methods of non-duration types.

@justingrant
Copy link
Collaborator Author

justingrant commented Oct 7, 2020

I updated the OP proposal with the changes noted above. Search for "2020-10-06" to find the specific sections that were changed/added.

@justingrant
Copy link
Collaborator Author

I've had to make one more adjustment: the default for largestUnit is the larger of the largest non-zero unit in the input duration, compared to smallestUnit. (otherwise this throws, unexpectedly: Temporal.Duration.from({ days: 365 }).round({ smallestUnit: 'years', relativeTo: '2020-01-01' }))

@ptomato - how is this different from (7) ?

7. We'll add an 'auto' value for largestUnit in Duration and non-Duration difference methods where it's used. 'auto', like undefined, will select the default unit. EDIT 2020-10-06: added this section

  • 7.1 On Duration methods, 'auto' means "the largest nonzero unit in the input(s)"

  • 7.2 On difference of non-Duration types 'auto' means to pick the default unit of the type:

    • 7.2.1 Instant: 'seconds'
    • 7.2.2 Date / DateTime - 'days'
    • 7.2.3 ZonedDateTime - 'hours'
    • 7.2.4 Time - 'hours'
    • 7.2.5 YearMonth - 'years'
  • 7.3 If the default chosen above is smaller than the user-provided smallestUnit then set largestUnit = smallestUnit.

@ptomato
Copy link
Collaborator

ptomato commented Oct 14, 2020

Somehow I had missed 7.3. Never mind.

ptomato added a commit that referenced this issue Oct 28, 2020
Removes the 'overflow' option from Duration.add() and Duration.subtract().
This also removes the concept of "balance-constrain." Durations are
balanced up to the largest nonzero unit in the input duration, and a
different largest unit can be selected with the largestUnit option.

The options object in Duration.add() and Duration.subtract() remains for
the time being, even though it accepts no valid options, because rounding
options will be added in #856.

See: #856
Closes: #857
Ms2ger pushed a commit that referenced this issue Oct 29, 2020
Removes the 'overflow' option from Duration.add() and Duration.subtract().
This also removes the concept of "balance-constrain." Durations are
balanced up to the largest nonzero unit in the input duration, and a
different largest unit can be selected with the largestUnit option.

The options object in Duration.add() and Duration.subtract() remains for
the time being, even though it accepts no valid options, because rounding
options will be added in #856.

See: #856
Closes: #857
Ms2ger pushed a commit that referenced this issue Oct 29, 2020
Removes the 'overflow' option from Duration.add() and Duration.subtract().
This also removes the concept of "balance-constrain." Durations are
balanced up to the largest nonzero unit in the input duration, and a
different largest unit can be selected with the largestUnit option.

The options object in Duration.add() and Duration.subtract() remains for
the time being, even though it accepts no valid options, because rounding
options will be added in #856.

See: #856
Closes: #857
@ptomato
Copy link
Collaborator

ptomato commented Nov 6, 2020

Here's a checklist of what's left to do on this issue:

ptomato added a commit that referenced this issue Nov 9, 2020
Implements a Duration.compare() static method with a relativeTo option
for comparing durations with date units. Allowing ZonedDateTime as
relativeTo does not work yet.

See: #856
ptomato added a commit that referenced this issue Nov 9, 2020
Removes old behaviour of never converting between years, months, weeks,
and days, and replaces it with using relativeTo to do the conversion. If
either operand has nonzero years, months, or weeks, then relativeTo is
required.

See: #856
ptomato added a commit that referenced this issue Nov 10, 2020
Implements a Duration.compare() static method with a relativeTo option
for comparing durations with date units. Allowing ZonedDateTime as
relativeTo does not work yet.

See: #856
ptomato added a commit that referenced this issue Nov 10, 2020
Implements a Duration.compare() static method with a relativeTo option
for comparing durations with date units. Allowing ZonedDateTime as
relativeTo does not work yet.

See: #856
Ms2ger pushed a commit that referenced this issue Nov 10, 2020
Removes old behaviour of never converting between years, months, weeks,
and days, and replaces it with using relativeTo to do the conversion. If
either operand has nonzero years, months, or weeks, then relativeTo is
required.

See: #856
ptomato added a commit that referenced this issue Nov 10, 2020
This operation gives the total number of nanoseconds in an exact
duration as a bigint, not losing precision. This will be reused in
Duration.compare().

This operation takes an "offset shift" parameter, used if there are days
that need to be balanced down to hours. If it's 0 (as is the case
everywhere in this commit) then days are always 24 hours. We will start
using nonzero values for this in Duration.compare().

See: #856
ptomato added a commit that referenced this issue Nov 10, 2020
Implements a Duration.compare() static method with a relativeTo option
for comparing durations with date units.

See: #856
@ptomato
Copy link
Collaborator

ptomato commented Nov 10, 2020

Opened #1171 for the last item on the checklist as it is the same as the last item on #569's checklist as well. This can close as soon as #1163 lands.

Ms2ger pushed a commit that referenced this issue Nov 11, 2020
This operation gives the total number of nanoseconds in an exact
duration as a bigint, not losing precision. This will be reused in
Duration.compare().

This operation takes an "offset shift" parameter, used if there are days
that need to be balanced down to hours. If it's 0 (as is the case
everywhere in this commit) then days are always 24 hours. We will start
using nonzero values for this in Duration.compare().

See: #856
Ms2ger pushed a commit that referenced this issue Nov 11, 2020
Implements a Duration.compare() static method with a relativeTo option
for comparing durations with date units.

See: #856
Ms2ger pushed a commit that referenced this issue Nov 11, 2020
This operation gives the total number of nanoseconds in an exact
duration as a bigint, not losing precision. This will be reused in
Duration.compare().

This operation takes an "offset shift" parameter, used if there are days
that need to be balanced down to hours. If it's 0 (as is the case
everywhere in this commit) then days are always 24 hours. We will start
using nonzero values for this in Duration.compare().

See: #856
Ms2ger pushed a commit that referenced this issue Nov 11, 2020
Implements a Duration.compare() static method with a relativeTo option
for comparing durations with date units.

See: #856
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Additions to documentation non-prod-polyfill THIS POLYFILL IS NOT FOR PRODUCTION USE! spec-text Specification text involved
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants