tc39 / ecma402

Status, process, and documents for ECMA 402

Home Page:https://tc39.es/ecma402/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Relative time/dates

zbraniecki opened this issue · comments

There are two major areas where relative time/date formatting could use standardization:

  1. Relative time representation

Good example are timers - representing time like "31 hours, 15 minutes and 23 seconds" as "31:15:23".

  1. pretty relative date/time.
    A lot of web apps humanize relative date and time with messages like:
  • a few seconds ago
  • less than a minute ago
  • one hour ago
  • in one hour
  • yesterday
  • last week
  • in 2 days

All of those patterns are available in CLDR and it would enable web authors to provide better user interfaces if we could incorporate this kind of formatting either into DateTimeFormat or into a separate RelativeDateTimeFormat.

@zbraniecki, now that we are ready to start working on the 3rd edition (using this repo), I plan to champion the relative time feature based on the initial work that we did on https://github.com/yahoo/intl-relativeformat. Do you want to help?

absolutely. We're currently using a very nasty hack for that[1] and I'd love to see some better, possibly CLDR based, API for that.
I'll be definitely interested in shimming whatever we come up with for Firefox and Firefox OS which should give us some ability to test how it works early on.

[1] https://github.com/mozilla-b2g/gaia/blob/19696d5db8e7e078204b15597b9d01c788e5f04f/shared/js/l10n_date.js#L132

I could not find "a few minutes ago" data in CLDR, nor the "one", "less than", etc...

Here is the initial spec: https://github.com/caridy/intl-relative-time-spec, and here is the HTML version https://rawgit.com/caridy/intl-relative-time-spec/master/index.html

That looks awesome! Thanks @caridy

I'll play with this against our use cases more, but first thing I miss here is pure duration. You have "X ago" and "in X", but you don't have "X". Examples:

  • "1 month" "1m"
  • "26 hours" / "26h"
  • "5 seconds", "5 sec", "5s"
    etc.

Hmm, the more I think about this the more I feel like there's a direct overlap area between three proposals:

  • this one
  • #37 - abbreviate numbers
  • #32 - Unit formatting
  • NumberFormat

First of all, there are three modes - future (in 5 minutes), past (5 minutes ago) and "neutral" (5 minutes).

Second, there may be specific maximum unit to show - "50000" meters is "50km", and "5000000" meters is "5000km". Same for "12000000" minutes being "50000h".
Third, on top of maximum number, there should be an option what to do with the modulo - 125 minutes may be "2 hours" or "2 hours and 5 minutes".
The maximum number may actually be a float - "160" minutes may be "2.5 hours" or "2 hours and 30 minutes", same for "2.5 miles", "2.5 GB" etc.
And lastly, all of that should be formatter by the NumberFormat.

I think that however incremental we want to take it, we should design the API to be forward compatible to allow ourselves to add the next bits later.

  1. I haven't see the data for the neutral form of time in cldr, did you?
  2. best fit vs specific unit should provide the flexibility to select the right form. If we have the concrete data in cldr I can look into expanding this proposal to cover other scales.
  3. if you look into the draft, I did use %NumberFormat% to format the numeric value before replacing the cldr {0} value in the pluralization forms.

"neutral" (5 minutes).
I haven't see the data for the neutral form of time in cldr, did you?

@caridy @zbraniecki the "neutral form" (or sometimes referred to as absolute time) you are talking about is called "unit" - #37 :) as you may already figured out.

@rxaviers - So #37 is about abbreviation, but #32 is about units. Did you mean #32? Should we keep it separate from this API? I feel like they overlap a lot and I feel tempted to try to address future/past/absolute in one API, but I don't have much experience designing APIs like that so I can imagine that there may be an obvious reason not to.

@caridy - in CLDR the "absolute time" is part of Duration Patterns - http://unicode.org/repos/cldr/trunk/specs/ldml/tr35-general.html#durationUnit

I'm talking about things like:
2000s -> "00:33:20.00" (0 hours, 33 minutes and 20 sec)

Is it part of your plan for this API?

@zbraniecki that's a good question, we need some bikeshedding on this topic I guess. Obviously that fits into the relative time paradigm since you have to use %Date.now% to compute the lapse. The other part of the equation is to figure whether or not %Date.now% is a default value of a configuration option, allowing users to provide the pivot, just like we do in intl-relativeformat, that probably makes more sense for the absolute, but it is proven to be useful for past and future as well.

Yeah, I mocked it today for our code in Clock/Timer/Stopwatch app and I used something like that:

var formatter = mozIntl.DurationFormat(navigator.languages, {
  hour: true,
  minute: true,
  second: true
  //millisecond: true
});
formatter.format(milliseconds);

I can see it becoming a separate formatter or becoming part of RelativeFormat.

@rxaviers - So #37 is about abbreviation, but #32 is about units. Did you mean #32?

Whoops, yeap I meant #32 you're correct.

Should we keep it separate from this API? I feel like they overlap a lot and I feel tempted to try to address future/past/absolute in one API, but I don't have much experience designing APIs like that so I can imagine that there may be an obvious reason not to.

I agree with you that these three forms overlap. But, I'm not too convinced RelativeTimeFormat should include an absolute form. Because, FormatUnit can be used for that purpose.

  • 2000s could be formatted as "00:33:20.00" (0 hours, 33 minutes and 20 sec) as you have mentioned. Similarly, other units too, e.g.:
  • 65in or 5.5 feet could be formatted as 5'6", or 5 feet and 6 inches.
  • 168oz or 10.5lb could be formatted as 10 pounds and 8 ounces.
  • 182cm could be formatted as 1m and 82 cm.

I see the above as FormatUnit plus an abbreviation modifier. It just happens that it's easier to think of abbreviating 1000 into 1K, or $1,000,000 into $1M because they are powers of 10. The above unit abbreviations require that the abbreviation logic know how to convert units that are not powers of 10, including time.

PS: Eventually, there might be an API that gracefully unifies all the three (relative time formatting, unit formatting, and abbreviation). So far, I think FormatUnit is a foundation similar to what FormatPlural is for MessageFormat (or possible a different more sophisticated API for formatting messages).

sounds great!

@rxaviers that's the same conclusion we arrived yesterday when discussing this issue. RelativeTimeFormat should focus on the timeline, producing past and future, relying on %Date.now% (or a provided time frame), and we can get absolute time as part of the unit api.

I separated discussion about DurationFormat to #47 since I'm not sure how it may fit into this API.

update: we are cooking the proposal for the next meeting in two weeks.

Heads up, this has been advanced to Stage 0 as of today.

This proposal has been advanced to Stage 1 as of today.

Today I submitted the patch proposal for an internal Gecko API mozIntl.RelativeTimeFormat which is based on the ECMA402 - https://bugzilla.mozilla.org/show_bug.cgi?id=1270140

I also submitted suggestions for the spec changes based on my implementation experience.
They're filed as issues at https://github.com/caridy/intl-relative-time-spec

We plan to request stage 2 at the next TC39 meeting.

This proposal has not reached the approval to promote to stage 2 just yet.

The particular requests are:

  • describe clearer relationship between UnitFormat and RelativeTimeFormat
  • identify the future compatibility between Duration type and RelativeTimeFormat

@maggiepint may help us get feedback from moment.js crew on this proposal in particular.

moment crew here trying to help: so as far as I read the discussion you already figured that RelativeTimeFormat should be separate from UnitFormat, because one has the future/past and the other has absolute everything. I could argue that other units (like distance) also have from/to forms, but the data is not present in CLDR so it would be wishful thinking to combine the two interfaces, but I'd try to make them very similar.

About Duration vs RelativeTimeFormat, can you provide more info on the definitions of the two (I bet its not moment's duration). In general, with moment development we came to realize there are 2 kind of durations.

  1. Duration defined in terms of milliseconds (no fuzzy units as months or years)
  2. Durations defined in terms of months (or years) -- may be encountered when you diff two instants (moments :))

It might seem stupid but: 1 can be approximated by 2 and vice versa, but you can not do it twice (1 - 2 - 1) and expect consistent results.

So one extreme we considered is having two classes to represent those, and remember "where you came from", so 1 - 2 - 1 will result in the first 1.

Back to your thread: #35 (comment) pointed out a few issues, and these get magnified when you start dealing with months/years.

A great API that will sort most of it, is to provide anchor points for durations so they can be accurate, for example -- if you want to know how many months are 30 days (in an API like duration.display({months: true})), you need to know where are you know, because 30 days could be 1 month, and it could be less than a month or more than a month. So at least if you care enough, you should have an anchor, and then most questions of how many days are in a month are sorted out.

So the API should be able to accept duration.display({months: true, now: new Date()}). What to do if the user doesn't pass now? You can anchor in unix 0 or year 0. If you anchor on something like now or this year jan 1, then the code becomes hard to reproduce.

The point is correct duration handling can't work unless you have an anchor. So functions that deal with durations should be able to accept it. Or (maybe) even better -- durations should store the anchor themselves, when possible.

I have no idea if I solved some of your issues or created new ones :)

@ichernev thanks for chiming in!

The way we thought of DurationFormat is actually a pretty specific formatter intended to solve the specific UI problem of displaying durection in a codified form, the way music/video and timer/stopwatch do: "02:03.201" kind of thing or "-03:12" (see #47).

So, the way we see the difference between those three is:

let uf = new Intl.UnitFormat('en', {category: 'duration'});
uf.format(5, 'hour'); // "5 hours"
uf.format(-5, 'hour'); // "-5 hours"
uf.format(1, 'day'); // "1 day"


let rtf = new Intl.RelativeTimeFormat('en', {
  type: 'text'
});
rtf.format(5, 'hour'); // "in 5 hours"
rtf.format(-5, 'hour'); // "5 hours ago"
rtf.format(1, 'day'); // "tomorrow"

let df = new Intl.DurationFormat('en', {
  hour: 'numeric',
  minute: '2-digit',
  second: '2-digit'
});
df.format(5 * 60 * 1000); // "5:00:00"
df.format(-5 * 60 * 1000); // "-5:00:00"
df.format(1 * 24 * 60 * 1000); // "24:00:00"

Each one of them has different options and constrains. In particular, if you're interested in rtf and our conversation about how to handle discreet time vs absolute time, see tc39/proposal-intl-relative-time#14

Our conclusion is that we'll only handle the text type for integers, not floats (so, 0.8 day will not be turned into "yesterday").

you need to know where are you know, because 30 days could be 1 month, and it could be less than a month or more than a month. So at least if you care enough, you should have an anchor, and then most questions of how many days are in a month are sorted out.

Our current thinking about ReltiveTimeFormat is that we will not handle the anchor - but expect the client side lib (moment.js or others) to handle the anchor, and use RTF just to format the data after it does all the calculations).
This should make moment.js simpler, and carry less data, but at the same time leave it up to the lib to decide how exactly to implement the "bestFit" algorithm.

Does it work for you?

Well, you didn't specify how to configure months showing but I guess if its the library responsibility it will be ok, in terms of correctness.

let uf = new Intl.UnitFormat('en', {category: 'duration'});
uf.format(5, 'month'); // "5 months"
uf.format(-5, 'months'); // "-5 months"
uf.format(1, 'month'); // "1 month"


let rtf = new Intl.RelativeTimeFormat('en', {
  type: 'text'
});
rtf.format(5, 'month'); // "in 5 months"
rtf.format(-5, 'month'); // "5 months ago"
rtf.format(1, 'month'); // "next month"

// DurationFormat makes no sense for months. I believe hours is the upper unit.

I want to help this proposal to move forward. In looking into it, I had a couple questions:

  • Am I right in understanding that the interface is to print out in the unit provided, and it will never round to a different unit--that's a job for the library on top, right? I had heard some descriptions to the contrary, so I just wanted to confirm.
  • Given this unit basis, it's hard for me to understand what we'd do with an eventual integration with a duration type. Seems like you'd still have to pass in the unit, in addition to taking a new direction argument, and then it could extract the whole number from that. Is this the direction we'd want to go? It's hard for me to see how it's useful--this would just be a convenience overloading implemented on top.
  • At the March 2017 TC39 meeting, there was some discussion about layering, whether we should somehow expose something lower level. Here, the natural thing to expose would be to expose the pattern strings directly that the specification uses. Per spec, the pattern strings here are simple--there's just the portion that comes before the number and the portion that comes after (or whether it omits the number). From this more minimal interface, it seems like it'd be around 30loc to implement the currently presented API. What would you think of that?
  • It seems like ICU is missing the equivalent formatToParts method, and it's correspondingly missing from the Mozilla implementation linked above. I understand that the idea is to add formatToParts to all formatters, for consistency, and this seems reasonable. I'm wondering, in addition to that, does anyone have any particular use cases in mind for formatToParts here? This could help motivate getting such a method in upstream ICU, which will be useful in some implementations. Or, if we go with the
  • Editorial: The spec text seems to be based on a RegExp parsing of a pattern string. Is this really necessary? It seems pretty complex. Why not have the pattern be an already-parsed record (especially when, here, the pattern is very simple).
  • It looks like this interface leaves out "absolute" forms like "last Tuesday". Is this intentional? (It also leaves out last2 and next2, but a comment in a header file says it's not supported in all locales--maybe better to leave out).

Am I right in understanding that the interface is to print out in the unit provided, and it will never round to a different unit--that's a job for the library on top, right? I had heard some descriptions to the contrary, so I just wanted to confirm.

Correct. The low level approach was the preferred choice so far.

Given this unit basis, it's hard for me to understand what we'd do with an eventual integration with a duration type. Seems like you'd still have to pass in the unit, in addition to taking a new direction argument, and then it could extract the whole number from that. Is this the direction we'd want to go? It's hard for me to see how it's useful--this would just be a convenience overloading implemented on top.

Please, can you give examples? UnitFormat and a possible DurationFormat would be separate proposals.

At the March 2017 TC39 meeting, there was some discussion about layering, whether we should somehow expose something lower level. Here, the natural thing to expose would be to expose the pattern strings directly that the specification uses. Per spec, the pattern strings here are simple--there's just the portion that comes before the number and the portion that comes after (or whether it omits the number). From this more minimal interface, it seems like it'd be around 30loc to implement the currently presented API. What would you think of that?

IMOO, the current proposal is pretty low level already. Nevertheless, let's see what @caridy or @zbraniecki says here...

It looks like this interface leaves out "absolute" forms like "last Tuesday". Is this intentional? (It also leaves out last2 and next2, but a comment in a header file says it's not supported in all locales--maybe better to leave out).

Good point. Given the low level nature of the current API, it would be pretty straightforward to add that to the spec [1] and have rtf.format(-1, 'tue') === 'last Tuesday'. 1: By allowing [[Unit]] be those week day values too.

Please, can you give examples? UnitFormat and a possible DurationFormat would be separate proposals.

I don't mean to get into those territories, I just mean as an addition frontend to this capability. TC39 asked about integration with a possible Duration type at the March 2017 meeting, so I want to explain fully either how we'd do that in the future, or if we'd If we're talking about time, why not use time units? For example, we could have an overloading like:

let fmt = Intl.RelativeTimeFormatter('en');
let dur = new Duration(100000);
console.log(fmt.format(dur, "minutes", "after"));

Anyway, we'd need these two extra arguments--we still need the units (because of that first question), and we'd have to indicate the direction (since durations don't have a direction). Then, all the API would do with this is calculate the appropriate number from the duration--not a big benefit. We can just expect the user to calculate this number, instead.

By allowing [[Unit]] be those week day values too.

Are we sure that the names will never overlap in the future? I guess it seems pretty unlikely, but probably best to check with the ICU/CLDR folks that they are comfortable reusing this single field for something that's represented in separate places elsewhere.

About the overlap--never mind, in CLDR the units and the days of the week are held in the same place, see LDML.

Another minor spec issue: I'm not sure if keying off of the result of PluralRules is enough. For example, in German, there's no special "dual" PluralRules form; however, there is a special word for the day before yesterday and the day after tomorrow, as in this example. Should we put an extra Get for the exact number, before "falling back" to getting the plural category? I think this would match the structure of CLDR.

(And, this is so minor that I'm putting it in parentheses: editorially, was there a particular reason for modeling the locale database as JS objects, rather than Records?)

Intl.RelativeTimeFormat proposal has reached Stage 3 at today's TC39 meeting!

I don't remember that happening... I thought we just go to Stage 2, and we had to go back and make the changes that were suggested in the previous meeting.

My bad. The notes were confusing. I corrected the stages2 - RTF is in Stage 2.

In other news, RTF landed behind the flag in SpiderMonkey - https://bugzilla.mozilla.org/show_bug.cgi?id=1270140

Another minor spec issue: I'm not sure if keying off of the result of PluralRules is enough. For example, in German, there's no special "dual" PluralRules form; however, there is a special word for the day before yesterday and the day after tomorrow, as in this example. Should we put an extra Get for the exact number, before "falling back" to getting the plural category? I think this would match the structure of CLDR.

I guess there are more languages like German. Korean also has special words for the day after tomorrow and the day before yesterday. Actually, it has special words for 3 days ago and 3 days from today.

As with unit format, I wonder what the plan is for 'more than one units'.

3 days and 5 hours ago
5 hours and 25 minutes ago
In 3 minutes and 40 seconds

3 days and 5 hours ago
5 hours and 25 minutes ago
In 3 minutes and 40 seconds

we can support those in the future by providing an option for more accuracy.

Just dropping by to say that I implemented a polyfill for the current Intl.RelativeTimeFormat spec in case anyone finds it useful.
https://github.com/catamphetamine/relative-time-format

Intl.RelativeTimeFormat achieved Stage 3 as of today!

Perform ! CreateDataPropertyOrThrow(nfOptions, "minimumIntegerDigits", 2

The spec has the above provision. I wonder why minIntDigits is set to 2. That leads to zero-padding likes '04 days ago', 'In 05 hours' instead of '4 days ago' / 'In 5 hours'.

Should we just closed this issue now since we already have Intl.RelativeTimeFormat in Stage 3?

We can close this when the corresponding proposal reaches Stage 4 and is merged. This issue has the tag "active proposal".

Intl.RelativeTimeFormat has been merged.

Thanks @anba; this proposal is now Stage 4 and merged. Closing as Fixed