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

background on why there is only one Duration type? #2915

Closed
BurntSushi opened this issue Jul 15, 2024 · 6 comments
Closed

background on why there is only one Duration type? #2915

BurntSushi opened this issue Jul 15, 2024 · 6 comments
Labels

Comments

@BurntSushi
Copy link

Hiya!

I've non-exhaustively searched the issue tracker and the meeting minutes, but I haven't been able to find something that explains why there is only one Duration type. I suppose it might also explain why alternative designs were not chosen, with the leading contender perhaps being two duration types, where one is a "calendar" duration (containing potentially non-uniform units) and the other is an "absolute" duration (containing only uniform units). java.time, for example, has Period and Duration types for these purposes, respectively. I believe NodaTime has a similar design.

I've copied Temporal's design (one duration type) in my yet-to-be-released Rust datetime library that is heavily inspired by Temporal, and it's getting some push back among folks for which I've asked to review the API. To me, the design makes a lot of sense. It seems simpler. Being able to go back-and-forth between calendar and clock/absolute units is great. And I think it all works because Temporal gets the edge cases right. Like, when you're using a ZonedDateTime, there is no way to abuse the API such that 1 day isn't interpreted correctly. (The only way I can think of is if the user themselves writes "24 hours" thinking it will be 1 day, but I don't see how to prevent that from happening in any design.)

So I guess, does there exist something written down that goes through the trade offs? What kinds of things are worse in a world with one duration type? What kinds of things are better? And why did y'all ultimately land on one type? Is it because of a conceptual calculation in terms of what would be best for users? Or was there more of a "we can't have a bigger API because of environment constraints" decision going on? (Like the in-progress removals of custom time zones and calendars.)

Thanks!

@justingrant
Copy link
Collaborator

IIRC the single duration type decision was made before I started working on Temporal in 2020, so I can't speak to the specifics at the time. @ptomato may remember more. But "we can't have a bigger API because of environment constraints" was NOT the reason; those concerns only cropped up in 2024.

That said, there are a few significant advantages of having a single duration type:

  • A duration can be intuitively transformed into multiple forms depending on the requirements of the use case. For example, if you have a count of seconds or milliseconds or nanoseconds and you need to display that count in a human-friendly way (e.g. "1 year, 3 months, 18 days, and 7 hours"), then having a single duration type makes that much easier. You can also transform it in the opposite direction, e.g. "how many hours are left in this month?" These kinds of calculations are much harder with a split type.
  • For end-user-facing formatting, "36 hours" is distinct from "1 day and 12 hours" because some use cases require the former format and some the latter. This is not unique to durations larger than a day, e.g. "90 minutes" vs. "1 hour and 30 minutes". Temporal's Duration objects are intentionally designed to be the input shape to a localized duration formatting API like DurationFormat that allows the formatting API to be "dumb" meaning it simply prints what you give it, instead of requiring the formatting API to perform complex DST calculations. If we split duration types, then DurationFormat would need to be responsible for combining them, which we thought was a bad idea.
  • Real-world durations are often composed of partial days (e.g. "how long was your car rental?"). Forcing developers to decompose each calculation into multiple values makes code harder to write, harder to understand, and therefore more bug-prone.
  • It'd make arithmetic with ZonedDateTime and PlainDateTime vastly more complex for the caller, because instead of .add(d) you'd need separate methods for adding dates and times.
  • since/until on ZonedDateTime and PlainDateTime would be even more complicated: every result would be a tuple or we'd need to decompose it into multiple API calls.
  • Finally, as you correctly note there are many edge cases caused by DST. Some of those edge cases have no perfect answer, only less-bad or arbitrary tossup solutions. We believed that having the platform resolve those edge cases consistently in all JS code was preferable to forcing the user to puzzle out these complex edge cases on their own. We did the work of resolving edge cases so JS developers won't have to, and so date/time code will behave the same across all JS programs. This approach means that sometimes developers may get results that are unexpected. But we think that having every developer and every end user get the same unexpected results is better than the alternative of every developer rolling their own different and unexpected results.

The Temporal Duration type does have significant tradeoffs:

  • Java's Period and Duration types can each be implemented with a single 64-bit integer, making them much more efficient to store in memory. Temporal's memory requirements are much larger, not just because date and time are combined but also because in Temporal.Duration, {seconds: 3600} is distinct from {hours: 1}. Storing Temporal durations efficiently requires a lot more work from the implementer, e.g. a variable-length storage model with a "fast path" for the large % of durations that have only one unit and a "slow path" for multi-unit durations.
  • It's much harder to implement, and therefore makes it more likely that there will be implementation-specific bugs. I suspect that we have over 1000 Test262 tests focused on duration-related operations. Temporal cannot be implemented reliably without very comprehensive test coverage.
  • The same complexity means that undoubtedly there's some bug(s) lurking in the spec that, once we ship Temporal Stage 4, we'll have to live with forever.
  • Some developers may disagree with how we've implemented some edge cases. (Although we'd argue that allowing developers to vary behavior in confusing edge cases probably does more harm than good.)

There may be more pros and cons, but I think I covered the main ones above. Other Temporal champions may have more to add.

If you do get a lot of pushback for a single Duration type, one compromise position you may want to consider is to also (or instead of Duration?) offer separate methods, e.g. addSeconds, addDays, etc. Ditto for daysSince/daysUntil, minutesSince/minutesUntil. IIRC, this is how most other libraries (that didn't have multiple people working out edge cases for 5 years!) do it. Callers who care about maximum perf could use the one-unit methods. Callers who want ease of use could use the big Duration type.

@BurntSushi
Copy link
Author

Thank you for that answer!

As a follow-up question, is there prior art on Temporal's Duration type? I'm aware of PostgreSQL's interval type and, obviously, the split duration types in other datetime libraries, but I'm not aware of another single duration type like Temporal's.

@justingrant
Copy link
Collaborator

is there prior art on Temporal's Duration type?

I don't think so, I suspect for exactly the reason we discussed above: no project previously had the people time, calendar time, and inclination to find and solve the edge cases and other complexity involved in a Temporal-like Duration type.

@ptomato
Copy link
Collaborator

ptomato commented Jul 24, 2024

You can see some investigation that I did into prior art here.

@BurntSushi
Copy link
Author

You can see some investigation that I did into prior art here.

Thank you, I had missed that. That's very helpful.

@ptomato
Copy link
Collaborator

ptomato commented Aug 22, 2024

Looks like this discussion has concluded, so I'll close the issue.

For posterity, I'll also link to what I wrote in #2768 (comment):

Having only one Duration type is one of the design decisions I'm most conflicted about in hindsight, FWIW; but given the constraints on Temporal's size, we certainly wouldn't add a second one now even if the proposal was still at a stage where that'd be possible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants