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

Accurate JSON representation for duration #3095

Open
dsnet opened this issue Feb 21, 2025 · 6 comments
Open

Accurate JSON representation for duration #3095

dsnet opened this issue Feb 21, 2025 · 6 comments
Labels
behavior Relating to behavior defined in the proposal meeting-agenda normative Would be a normative change to the proposal

Comments

@dsnet
Copy link

dsnet commented Feb 21, 2025

Hello, thank you for all your hard work improving time support in ECMAScript.

I'm one of the maintainers of the "encoding/json" package in Go, where the community is currently pursuing a prospective "encoding/json/v2" package (golang/go#71497). There's an unsettled debate (golang/go#71631) regarding what the right JSON representation for a Go time.Duration should be, where the community is split between a custom Go representation (e.g., 123h4m56.789s) versus a subset of ISO 8601 (e.g., PT123H4M56.789S). Since JSON is often used as a format for interacting with systems written in other languages, there's value in being consistent with what other implementations support. Furthermore, JSON finds its heritage in JavaScript, so the precedence set by JavaScript does impact the rest of the industry.

While ISO 8601 specifies a set of grammars for a duration, it is unfortunately ill-suited for Go since it supports both nominal units (e.g., year, month, week, day) and accurate units (hour, minute, second). For implementations with duration data structures that represent both nominal and accurate units individually (e.g., Temporal.Duration), this is perfectly fine. However, for languages that can only represent accurate durations, this is impossible to fully support (e.g., Go's time.Duration, java.time.Duration, google.protobuf.Duration, etc.).

Given the existence of many implementations that can only represent accurate durations, I suspect this has significantly impeded the adoption of the ISO 8601 duration format since interoperability is poor if nominal units are ever used. In an attempt remedy this problem, we authored an RFC Internet-Draft that proposes a strict subset of ISO 8601 that prioritizes interoperability. This RFC I-D is merely a proposal intended to kickstart discussion and the grammar is subject to change based on feedback.

The goal of this issue is to raise the concern of interopability and see if we can cooperatively move towards greater interopability. The Internet is comprised of many systems written by many different languages and would benefit from this endeavor.

Overall, it seems the TC39's design is almost entirely compatible with the proposed RFC I-D, but a minor thought we can consider:

  • Have the Temporal.Duration.toJSON method call round({largestUnit: "hour"}) before formatting as ISO 8601 if all the nominal units are zero. This does mean that formatting and parsing from JSON does not round-trip identically, but the Temporal.Duration.compare method does correctly treat PT90S and PT1M30S as equal. One advantage of always rounding is that it avoids leaking details about the fact that Temporal.Duration is able to handle each unit independently (which other languages cannot do). I believe always rounding by the largest hour would make the output compliant with the proposed RFC I-D.

  • The above suggestion is however impossible if nominal units are non-zero. In such a case, the toJSON method would have to output an ISO 8601 duration that may not be parsable by other implementations, but this is the best that can be done when lacking any "reference point". Fortunately, the arguably most common way to obtain a duration is by measuring the passage of time through the use of zonedDateTime.until (or since) methods. The until method defaults to measuring time in terms of accurate units (e.g., up to hours if no options are specified). Thus, I suspect that most common usages of the Temporal types will only have accurate units unless the programmer went out of their way to use nominal units.

  • The above suggestions are targeted toward toJSON, while toString remains unchanged. The assumption is that toJSON is targeted towards machine consumption where accuracy and interopability is a priority, while toString is targeted towards human consumption where readability is a priority.

  • It is far more important that the formatted durations be interoperable (especially in JSON). If Temporal.Duration is able to parse the durations according to the full grammar of ISO 8601, then more power to ECMAScript!

Thank you for considering our thoughts.

\cc @timbray @mvdan @johanbrandhorst

@dsnet
Copy link
Author

dsnet commented Feb 22, 2025

In case the Temporal proposal is too far along to change, it's probably fine if no behavior is made to the Temporal proposal.

Fortunately, I believe the following statements are true:

  • The output of Temporal.Duration.toJSON is exactly identical to the I-D if the duration has already been balanced to the largest unit of hours.
  • By default, the typical production of a Temporal.Duration value is already balanced to the largest unit of hours (e.g., zonedDateTime.until or zonedDateTime.since).
  • The I-D grammar is always a valid input to Temporal.Duration.from except for overly large or overly precise durations, where I-D says that implementations are free to choose practical limits. Thus, TC39 is free to choose to only support nanosecond precision.

@gibson042
Copy link
Collaborator

gibson042 commented Feb 23, 2025

In case the Temporal proposal is too far along to change, it's probably fine if no behavior is made to the Temporal proposal.

That is probably the case, although I will note that the proposal is still at Stage 3, whose documented purpose is "Gaining implementation experience and discovering any web compatibility or integration issues". So changes like this might still be possible.

EDIT: Removed text representing a misunderstanding that this suggestion would include mapping P1D to P24H.

@gibson042 gibson042 added behavior Relating to behavior defined in the proposal meeting-agenda normative Would be a normative change to the proposal labels Feb 23, 2025
@dsnet
Copy link
Author

dsnet commented Feb 24, 2025

Playing around with the Temporal package, I found the following behavior surprising:

t0 = Temporal.Now.zonedDateTimeISO()
// sleep 45 seconds
t1 = Temporal.Now.zonedDateTimeISO()
// sleep 45 seconds
t2 = Temporal.Now.zonedDateTimeISO()

d1 = t2.since(t0)                   // produces P1M30S; makes sense
d2 = t2.since(t1).add(t1.since(t0)) // produces PT90S; huh?

I was surprised that d1 and d2 produce different results when I would semantically expect them to be the same thing.

I understand why this occurs since the balancing to largestUnits: "hours" occurs within the context of the Temporal.ZonedDateTime.since method call, but the semantic intent to balance according to "hours" does not seem to be encoded in the Temporal.Duration object itself. Thus, when Temporal.Duration.add occurs, it doesn't doesn't balance upwards and maintains values in the current largest units (which is seconds).

I wonder if the intended "largestUnits" should be a stateful property in the Temporal.Duration such that arithmetic with a duration respect that intent (or the largest unit between two durations).

@arshaw
Copy link
Contributor

arshaw commented Mar 6, 2025

I had similar concerns about a data-inferred largestUnit that I expressed in an older ticket, the "LargestUnit for time parts? Balancing up?" section

@gibson042
Copy link
Collaborator

This was discussed in the Temporal champions meeting, and there was no appetite for it at this time. While changing the output of toJSON() wouldn't be harmful, and it would certainly be good to align with the external ecosystem, a lot could still change with https://datatracker.ietf.org/doc/draft-tsai-duration/ and it would be bad to guess wrong. Please post an update here if/when that draft advances, at which point it would be more enthusiastically accepted (if this proposal has not yet been merged into ECMA-262).

The interoperability arguments are completely valid; it's just unfortunate that there's not yet a mature specification to support them.

@timbray
Copy link

timbray commented Mar 6, 2025

FWIW my best guess as to what will happen with draft-tsai-duration is: Nothing.

Because nothing happens in the IETF unless there are people who want to get behind a piece of work and put energy into pushing it through the process, which is quite heavyweight. I've done this myself a couple of times but don't have room in my life to do it for this one.

If Joe wants to put a bunch of energy into the task it might advance and it might not. The IETF meets later this month and unless there's a surprise burst of energy there, I think this community could safely consider the spec stable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
behavior Relating to behavior defined in the proposal meeting-agenda normative Would be a normative change to the proposal
Projects
None yet
Development

No branches or pull requests

4 participants