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

Ergonomic issues with Duration::add() balancing #2818

Closed
arshaw opened this issue Apr 13, 2024 · 2 comments
Closed

Ergonomic issues with Duration::add() balancing #2818

arshaw opened this issue Apr 13, 2024 · 2 comments

Comments

@arshaw
Copy link
Contributor

arshaw commented Apr 13, 2024

Sounds like we're thinking about scoping-down the functionality of Duration::add(). Here are some additional ergonomic issues with the current one. Maybe this will shape discussion. The two issues I bring up are concerned with the lack of smallestUnit/largestUnit.

Week Preservation

Weeks can be preserved during Duration::round if smallestUnit is set to 'week':

Temporal.Duration.from({
  months: 1,
  weeks: 2
}).round({
  largestUnit: 'year',
  smallestUnit: 'week',
  relativeTo: '1970-01-01',
})
// P1M2W

However, if you're adding two durations that and either has a week AND either has year/month, there's no way to preserve weeks. This is because there's no notion of "smallestUnit" for Duration::add().

Temporal.Duration.from({ months: 1, weeks: 1 })
  .add(Temporal.Duration.from({ weeks: 1 }), {
    relativeTo: '1970-01-01'
  })
// P1M14D

LargestUnit for time parts? Balancing up?

The above problem will go away if y/m/w/ units are disallowed, but here's a problem with time units:

Say you do two diff of two pairs of instants:

// you typically receive hours from your diffs:
{
  inst0 = Temporal.Instant.from('2024-04-12T00:00:00Z')
  inst1 = Temporal.Instant.from('2024-04-12T02:00:00Z')
  dur0 = inst0.until(inst1, { largestUnit: 'hours' }) // PT2H

  inst2 = Temporal.Instant.from('2024-04-12T00:00:00Z')
  inst3 = Temporal.Instant.from('2024-04-12T02:00:00Z')
  dur1 = inst2.until(inst3, { largestUnit: 'hours' }) // PT2H

  sum = dur0.add(dur1) // PT4H
}

// but on the occasion where neither diff is larger than an hour,
// but their SUM is greater than an hour, you suprisingly receive minutes
{
  inst0 = Temporal.Instant.from('2024-04-12T00:00:00Z')
  inst1 = Temporal.Instant.from('2024-04-12T00:45:00Z')
  dur0 = inst0.until(inst1, { largestUnit: 'hours' }) // PT45M

  inst2 = Temporal.Instant.from('2024-04-12T00:00:00Z')
  inst3 = Temporal.Instant.from('2024-04-12T00:45:00Z')
  dur1 = inst2.until(inst3, { largestUnit: 'hours' }) // PT45M

  sum = dur0.add(dur1) // PT90M
}

You'd need to know to use Duration::round() first before displaying to users if you want to maintain hour precision.

This happens because Duration::add() implicitly decides "largestUnit" by detecting the largest non-zero duration field.

This is the only place in the API where something like "largestUnit" is data-dependent. I'd recommend always balancing-up smaller time units up to higher time units.

I'm still not sure whether hours should balance-up up to days or not.

@arshaw
Copy link
Contributor Author

arshaw commented May 2, 2024

The first part ("Week Preservation") is no longer relevant because we're limiting the Duration adding to days. The second part ("LargestUnit for time parts? Balancing up?") is still relevant and I did some digging as to why there's no largestUnit parameter and why it is instead inferred from the data:

#645
#856
#857
#980
https://github.com/tc39/proposal-temporal/issues?q=is%3Aissue+Duration+plus+largestUnit+is%3Aclosed

I still need to identify precise individual comments.

@arshaw
Copy link
Contributor Author

arshaw commented May 13, 2024

I can't figure out when Duration.p.add/subtract (formerly plus/minus) had its largestUnit parameter removed. Might be besides the point though: even if it still existed, I'd be questioning the ergonomics of the default 'auto' value, which would no doubt be the largest non-zero duration unit just like Duration.p.round.

I put in some more thought about how to make PT45M + PT45M yield a PT1H30M result, which I find more ergonomic than a PT90M result. Some options:

  1. Always balance-up to DAYS. But I don't like this because you'd loose precision in the conversion of hours -> days because there might be a 25 hour day.
  2. Always balance-up to HOURS. This doesn't work however, because P1D + P1D would yield PT48H, which is even worse.
  3. Balance up to DAYS if days is non-zero, balance up to HOURS otherwise. This is an okay solution, but it diverges from the way Duration.p.round's largestUnit:'auto' behaves. The improved ergonomics is not worth the API inconsistency.

So, I'm okay closing this issue, leaving things as-is. The usecase I was originally concerned about was large lists of durations being summed (like total duration of events on a calendar), but people will likely do this more efficiently using integers of either days or nanoseconds. In other cases, users will just need to remember to calls Duration.p.round before displaying a duration that resulted from a Duration.p.round call.

@arshaw arshaw closed this as completed May 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant