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

Negative durations in Intl.DurationFormat #29

Closed
sffc opened this issue Jul 17, 2020 · 27 comments
Closed

Negative durations in Intl.DurationFormat #29

sffc opened this issue Jul 17, 2020 · 27 comments
Assignees

Comments

@sffc
Copy link
Collaborator

sffc commented Jul 17, 2020

Temporal.Duration is going to support negative durations (tc39/proposal-temporal#782). What does this mean for Intl.DurationFormat when we get a negative duration?

  1. Output the string as if the duration were positive
  2. Throw an exception
  3. Other?

Is there a standard localized format for negative durations? In the other thread, I asked:

I'm trying to think of use cases. People might try formatting strings like "the game starts 1 hour after the train arrives" for a positive duration, or "the train arrives 1 hour before the game starts" for a negative duration. In English, the sign doesn't seem to matter, but you need to handle it properly to choose whether to say "before" or "after".

@ryzokuken
Copy link
Member

I'm not sure if the relevant CLDR data exists, but returning an extra string containing what could be called the "adjacency modifier" based on the sign could be useful. This could be "before"/"after" for English and "antes"/"después" for Spanish, for example.

@justingrant
Copy link

Playing devil's advocate: Intl.RelativeTimeFormat today is already formatting durations, albeit only one-unit durations.

Instead of creating a new Intl.DurationFormat type, could (and if so, should?) Intl.RelativeTimeFormat be extended to support multi-unit durations? Presumably it could also be extended with options to control what kind of format is requested, e.g. the three formats noted in tc39/proposal-temporal#782:

8.2 Note that there are (at least) three possible i18n-ized formats possible for durations:

  • 8.2.1 Non-relative / absolute value - e.g. "1 day and 12 hours". It's not obvious whether a negative duration should throw or should be shown using the absolute value.
  • 8.2.2 Relative to now - e.g. "1 day and 12 hours ago" or "in 1 day and 12 hours". This is the format currently used by Intl.RelativeTimeFormat.
  • 8.2.3 Relative-to-something - e.g. "1 day and 12 hours before". This format is not provided by Intl.RelativeTimeFormat today.

I'm not sure if the relevant CLDR data exists, but returning an extra string containing what could be called the "adjacency modifier" based on the sign could be useful. This could be "before"/"after" for English and "antes"/"después" for Spanish, for example.

Seems useful. Some languages may put the modifier before the duration text, while other languages might put it after. Some languages and modifiers could do both positions, e.g. "after 2 hours" vs. "2 hours after". How would the caller know where to put the modifier in the final text?

@sffc
Copy link
Collaborator Author

sffc commented Jul 20, 2020

I was thinking about that, too. I would rather have the class named Intl.DurationFormat and have the style field with the three options. However, since we already have Intl.RelativeTimeFormat, maybe we should consider overloading that class.

@sffc
Copy link
Collaborator Author

sffc commented Jul 20, 2020

One advantage of combining these into one class (regardless of the name): the option would be available to Temporal.Duration.prototype.toLocaleString. For example:

const duration = Temporal.Duration.from("-PT3H5M");
duration.toLocaleString("pt-BR", { style: "relative" });

@justingrant
Copy link

I was thinking about that, too. I would rather have the class named Intl.DurationFormat and have the style field with the three options. However, since we already have Intl.RelativeTimeFormat, maybe we should consider overloading that class.

Yep, agreed. If starting from scratch, then Intl.DurationFormat would be a better name. But it'd seem strange to build a new API that essentially does almost the same thing as an existing API, but has a different name. Using the same name would probably also help to accelerate adoption because users could discover the new functionality via docs and/or IDE autocomplete for code that the developer is already using.

If Intl.RelativeTimeFormat is used, then presumably the default format should match the default style of Intl.RelativeTimeFormat today, which is "in 2 days"/"2 days ago" ? Using this default would also sidestep the concern about how to handle negative durations-- no type of duration would need to throw or coerce-to-positive by default.

Some ideas for style names:

  • 'relativeToNow' - "in 2 days"/"2 days ago" (default)
  • 'relativeToEvent' OR 'relative' - "2 days before"/"2 days after" OR "2 days earlier"/"2 days later" OR "2 days earlier than"/"2 days later than"
  • 'unsigned' OR 'absolute' OR 'plain' - "2 days" (is the word "absolute" problematic in this context because of potential confusion with the Temporal.Absolute type?)

@sffc
Copy link
Collaborator Author

sffc commented Jul 21, 2020

@pedberg-icu also pointed out one more style: "yesterday", "day before yesterday" (as opposed to "1 day ago", "2 days ago")

@sffc
Copy link
Collaborator Author

sffc commented Aug 17, 2020

Related feature request for mixed units in relative time format: tc39/ecma402#498

@justingrant
Copy link

Related feature request for mixed units in relative time format: tc39/ecma402#498

By "mixed units" I'm assuming that you mean multiple units in one string, not units with different signs from each other. Correct?

@sffc
Copy link
Collaborator Author

sffc commented Aug 18, 2020

Related feature request for mixed units in relative time format: tc39/ecma402#498

By "mixed units" I'm assuming that you mean multiple units in one string, not units with different signs from each other. Correct?

Right.

@younies
Copy link
Member

younies commented Aug 24, 2020

From my perspective, there are two options:

  1. Combine relative time format with duration format

  2. Throw and error

@ryzokuken suggested to have something in between.

@younies
Copy link
Member

younies commented Aug 27, 2020

@justingrant could you state the three options here ?

@justingrant
Copy link

justingrant commented Aug 27, 2020

Summary from 2020-08-27 meeting:

  1. We agreed that Intl.RelativeTimeFormat should accept a Temporal.Duration parameter. A "duration-like" object property bag (with same properties as Duration) should also be accepted.

  2. We agreed that there should be some way to format Durations in at least two formats:

  • 2.1 "relative to now" - same format as RelativeTimeFormat, but including multiple units e.g. "in 2 days" / "2 days ago"
  • 2.2 "unsigned" format, e.g. "2 days"
  • 2.3 there may be other formats we'd want to support, e.g. "relative to event" (not now), e.g. "2 days before" / "2 days after"
  1. We listed 4 ways we could support multiple formats per decision (2) above:
  • 3.1 Add an option (e.g. style: 'relativeToNow' | 'unsigned' | 'relativeToEvent') to RelativeTimeFormat, with the "relative to now" option being the default to match current behavior. FWIW, this would align with the style: 'decimal' | 'currency' | 'percent' | 'unit' option that's currently accepted by Intl.NumberFormat.
  • 3.2 Revive the Intl.UnitFormat proposal and use it to display unsigned durations. (This proposal was previously abandoned in favor of the unit option in Intl.NumberFormat.)
  • 3.3 Create an Intl.DurationFormat API which only emits the unsigned format, while RelativeTimeFormat emits the "relative to now" format (and could optionally offer other relative formats like "relative to event"?)
  • 3.4 Do (3.1), but also rename Intl.RelativeTimeFormat to Intl.DurationFormat. I'm not sure in practice how "renaming" would work. Would the Intl.RelativeTimeFormat be an alias for the new object, or would Intl.RelativeTimeFormat functionality remain as-is but deprecated?

Per consensus at the meeting that we should be proposing opinions along with options ;-), my preference is for 3.1 because:

  • It's probably the least surprising and most discoverable option because Intl.RelativeTimeFormat is already where users have learned to go for this use case today, even though it's limited to directed, 1-unit durations.
  • If the default format isn't what's desired, developers can use IDE autocomplete to discover what formatting options are available. If we use separate APIs, then users must read the docs to discover how to show the other format(s).
  • It seems to best follow the conventions of other Intl APIs, which are organized based on the type of the input and use options to control the output format. For example there's no separate Intl.CurrencyFormat, just Intl.NumberFormat that handles both plain numbers and currencies.
  • Aliasing and/or deprecation (3.4) adds confusion to the ecosystem. I'm not sure outweighs the value of the clearer new name.
  • My general preference is to have fewer types that developers have to learn, that MDN must document, etc.
  • There's no one working on Intl.UnitFormat right now and no guarantee when/if it'd ship.

@sffc
Copy link
Collaborator Author

sffc commented Aug 28, 2020

On point 1, I do want to point out that CLDR does not currently support relative time formatting with more than one unit. However, we've gotten some requests for this (see tc39/ecma402#498). I am tracking this in CLDR-14144.

On point 2, I suggested a fourth style in #29 (comment).

I don't want to express an opinion on point 3 at this time. I have this on the agenda to discuss in the next 402 meeting.

@sffc
Copy link
Collaborator Author

sffc commented Sep 10, 2020

@FrankYFTang
Copy link
Collaborator

Intl.DurationFormat is chartered to format the "amount" of time, not a "particular time in the timeline"
A duration in Temporal is a data type, and a data type (such as Integer) could have different operation (for example, in the case of Integer, +, -, / , sqrt, exp). In the same way a duration could have different operation and the formatting result of those operation should be considered as different from the formation result of the duration itself.

A duration itself is a "amount of time" , without any reference point of start or end. 5 days mean a duration of 5 days of any 5 days in the timeline. It could be 5 days 2000 years ago, it could be the 5 days two months ago, it could be the 5 days starting 2 days ago and we are still in, or a 5 days 3 days from now. The formatted output of the duration of 5 days should not need to have a reference point and the result should not imply there is one.

The duration could be given to a RelativeTimeFormat as input argument if we change the Intl.RelativeTimeFormat to accept it as an acceptable data type, in that case, since the current semantic of the Intl.RelativeTimeFormat is the output should means a particular time in the timeline relative to now, the formatted result should represent a particular time in the timeline relative to NOW after the operation between the time NOW and the passed in duration. So if the duration passed is -1 days, then the output could be "Yesterday", -2 hours, could be "two hours ago", 3 days could be "the day after tomorrow".

If we extend the RelativeTimeFormat to give a different reference point, say "Jan 20, 2020", then format(100, "days") could be "100 days after Jan 20, 2020" and a duration of -5 days would be "five days before Jan 20, 2020". In either case, it still represent a particular time in the timeline, just change the reference point, but there are still a reference point. And it is not expression a "duration", but the result of that after we apply an operation of that "duration" to a reference point in the timeline. It should not be used to express an "amount" but only a "particular time".

The name of expression the "duration" (amount) should not use ambiguous term "absolute", because the term "absolute" mean two different thing:

  1. Mathematically, it mean abs() => which mean the result domain is always non-negative
  2. It could also mean "unreferenced" to a particular time in the timeline
    I believe what you try to express is 2) above but the word "absolute" is very easy to be interpreted as abs() in mathematical sense.

@FrankYFTang
Copy link
Collaborator

FrankYFTang commented Jan 14, 2021

I believe the toLocaleString of the duration should only format the output as the duration itself, not a result of applying it to an operation. For example, the format output of 3 should only represent 3, not sqrt(3) or -3 by applying 3 to a different operation. In the same way, a duration of 3 day should format the output represent itself, but not the meaning of applying that to a operation, such as comparing to Now, or comparing /reference to another date in the timeline. Therefore, the toLocaleString of duration should always result to mean an "amount of time" not a "particular time in the timeline".

@justingrant
Copy link

justingrant commented Jan 14, 2021

My understanding of consensus in the meeting was that the default toLocaleString output for a negative duration should be:
a) different from the default toLocaleString output for the same duration with a positive sign
b) a format that's widely used in software ("-2h 30m" or "-3:23:45" would meet this criteria. "T minus 2 days" would not.)
c) should look to end users and developers like the inverse of the positive format. "-2h 30m" vs. "2h 30m" meets this criteria, while "2 days and 30 minutes" vs. "2 days and 30 minutes remaining" does not because the inverse of "remaining" is probably "complete" not an empty string.

@FrankYFTang are you OK with those three criteria?

@FrankYFTang
Copy link
Collaborator

I agree with a)
As of b) and c) I believe that is a question for CLDR to figure out for each human language and is OUT OF THE SCOPE and expertise of this group to decide. I won't know what should be used in Thai, Hebrew, Korean, or Greek, and I believe most other members in this group neither. So what need to be used in English, as yet another language, do not need to be discuss here.

@ryzokuken
Copy link
Member

My understanding is that this is beyond the scope of DurationFormat v1 and therefore I am closing this. Please feel free to reopen.

@sffc
Copy link
Collaborator Author

sffc commented Apr 13, 2021

This is not obsolete. DurationFormat needs to format negative durations since Temporal.Duration supports negatives. I think we agreed to go with the format Justin proposed above in #29 (comment)

@sffc sffc reopened this Apr 13, 2021
@ryzokuken
Copy link
Member

@sffc is this already handled on the CLDR side?

@sffc
Copy link
Collaborator Author

sffc commented Apr 23, 2021

I think the scope of this issue for 402 is just to make sure that CLDR is aware of the sign of the duration when generating its implementation-defined output. It would also be good to have Test262 tests to verify that the output of a positive duration and the equivalent negative duration are not the same.

@KJTsanaktsidis
Copy link

Maybe this is an implementation question that doesn't belong here, but what exactly do you mean by "make sure that CLDR is aware of the sign of the duration when generating its implementation-defined output"?

Would that involve changes in the ICU API to make UListFormatter/icu::ListFormatter` aware of the overall sign of the whole duration when stitching together its components, and then some new type of data in CLDR which is "how to format a negative list of units"?

Or do you mean something else by that?

@sffc
Copy link
Collaborator Author

sffc commented Apr 30, 2022

The exact localized format is implementation- and locale-defined (ILD). However, on our side, we should:

  1. Make sure that the sign of the duration is an input into the ILD algorithm
  2. Add a Test262 test that the localized string output is different depending on the sigh

@ryzokuken
Copy link
Member

This is solved by #101, which disallows mixed-sign durations like Temporal and implementations can handle the rest, right?

@ryzokuken ryzokuken added the done label Jun 30, 2022
@justingrant
Copy link

This is solved by #101, which disallows mixed-sign durations like Temporal and implementations can handle the rest, right?

Is this really done? Per @sffc above, aren't there Test262 changes needed? Are there spec changes needed too?

@ryzokuken
Copy link
Member

@justingrant tc39/test262#3593 should be merged soon.

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

Successfully merging a pull request may close this issue.

7 participants