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

Support relative time #205

Closed
rxaviers opened this issue Jan 31, 2014 · 29 comments
Closed

Support relative time #205

rxaviers opened this issue Jan 31, 2014 · 29 comments

Comments

@rxaviers
Copy link
Member

Support relative times:

  • in the past , eg "5 days ago";
  • in the future, eg "in 5 days";

API

Globalize.formatRelativeTime( value[, options] )

Examples:

.formatRelativeTime( inTwoDays ); // in 2 days
.formatRelativeTime( twoDaysAgo ); // 2 days ago
.formatRelativeTime( twoDaysAgo, { unit: "hour" }); // 48 hours ago
.formatRelativeTime({ from: christmas2001, to: newyear2002 }, { unit: "day" }); // in 7 days

Value: Date instance, or object with from and to keys and Date instance values. When there's only one argument, it's considered to and from defaults to now (new Date()).

Options:

  • unit ("day"|"week"|"month"|...), eg. "in 30 days", "in 4 weeks", or "in 1 month", ... Defaults to automatic detection, therefore implementing a simple approximation algorithm as Jörn suggested. But, delegating any more complex things to a specific date manipulation library such as moment.js.

Returns:

  • "1 second ago" .. "59 seconds ago"; (1 .. 59 s)
  • "1 minute ago" .. "59 minutes ago"; (60 .. 60*60 s)
  • "1 hour ago" .. "59 hours ago";
  • "1 day ago" .. "59 days ago";
  • "1 week ago" .. "59 weeks ago";
  • "1 month ago" .. "59 months ago";
  • "1 year ago" .. "59 years ago";

(analogous for future)

CLDR

Relative time fields can be found on /dateFields.json.

Other

References:

@ichernev
Copy link

I have a fresh implementation with unit tests in https://github.com/ichernev/moment-cldr. I can morph it into a pull request in the future :)

@ichernev
Copy link

ichernev commented May 1, 2014

OK, so we talked with @rxaviers and sort-of decided on part of the API.

For relative duration: ... 2 days ago, yesterday, now tomorrow, in 2 days ... we can have

globalize.relativeDuration(value, unit);
globalize.relativeDuration(date, unit);
globalize.relativeDuration({from: date, to: date}, unit);

Where

  • unit is one of day, month, 'minute` etc
  • value is number
  • date is a Date object

Also absolute duration: 0 days, 1 day, 2 days, 2 weeks, 2 months ...

globalize.absoluteDuration(value, unit, [{length: length}]);
globalize.absoluteDuration(date, unit, [{length: length}]);
globalize.absoluteDruation({from: date, to: date}, unit, [{length: length}]);

Where length is long, short or narrow. We might make length a positional argument instead of an option.

@jzaefferer
Copy link
Contributor

For relative duration, is the unit argument always required? I don't think it should, so that it can pick the closest unit, like GitHub does on these comments here. "21 hours ago", "2 days ago", based on the "to" and "from" dates, without any unit.

Also, can you provide one or two examples for each of the six suggested methods? That would help a lot understanding the details here without having to ask to many questions.

@ichernev
Copy link

ichernev commented May 2, 2014

It is possible to auto pick, but then you might want to control that - cutoff points and range of units.

So if we choose that we'd better also have the low level unit interface.

@rxaviers
Copy link
Member Author

rxaviers commented May 3, 2014

Usually, hard problems can be divided into smaller and simpler problems for easier resolution. But, defining the edges or the roles of the involved entities are sometimes not an easy task.

Globalize is an i18n library. So, the way I see it is: Globalize should focus on the i18n part of the problem. It should allow user-code not to worry about the i18n difficulties. In the other hand, it should minimize implementing anything else, so we don't restrict our usage, ie. the greater the chances Globalize can be used for different use cases.

In the last meeting, @ichernev and I have discussed about this API and came up with the above, which is: let's implement here the low level interface (or the i18n interface).

On my initial conception of this API, I have also mimic'ed twitter api and rails code, which returns an approximate result. Each interface uses a different algorithm for the approximation. Talking with @ichernev, he showed me some other complex cases that moment.js experienced. So, it turned out that taking care of the approximating should be made by a separate entity/code.

@jzaefferer
Copy link
Contributor

Is it possible to implement a simple approximation, while offering the low-level methods as an alternative for anyone who needs custom cutoff points and range of units?

@ichernev
Copy link

ichernev commented May 5, 2014

All simple implementations fall short for some "obvious" cases, and there would be people in the future reporting those bugs and you having to fix them. I'm right now fixing moment's implementation because it was half an year off for dates from 30 years ago (using a very simple and straight forward approach).

Now that we know what would go wrong with the simple approach, saying in the docs "well this doesn't actually work", isn't very cool :) But if you think its helpful we can have those methods.

@jzaefferer
Copy link
Contributor

Well, my perspective is from the maintenance of this tiny jQuery plugin (for whatever reason it depends on jQuery): https://github.com/jzaefferer/jquery-prettydate/blob/master/jquery.prettydate.js#L146

This is pretty simple and I never had any complaints about the approximation. It makes sense that moment is used much more and therefore ends up with more people finding issues. Its just that I feel like the whole point of relative time is to provide the approximation, without it it seems useless to me.

If we end up with moment providing a solid implementation, while using Globalize for the low-level stuff, that's probably fine. Your call.

@ichernev
Copy link

Ok, so what about something like this:

globalize._relativeDuration(value, unit);  // 5 days -> 5 days. 1 day -> tomorrow, 365 days -> 365 days
globalize.relativeDuration(value, input_unit);  // input_unit defaults to ms, 5 days -> 5 days. 365 days -> 1 year
globalize.relativeDuration(date);  // defaults to {from: now, to date}
globalize.relativeDuration({from: from, to: to});  // auto picking of unit

@rxaviers
Copy link
Member Author

May I propose a slight change?

.formatRelativeDate( date, options ); // defaults to {from: now, to: date}
.formatRelativeDate({ from: from, to: to }, options );

Examples:

.formatRelativeDate( inTwoDays ); // in 2 days
.formatRelativeDate( twoDaysAgo ); // 2 days ago
.formatRelativeDate( twoDaysAgo, { unit: "hour" }); // 48 hours ago
.formatRelativeDate( twoDaysAgo, { unit: "hour", tense: null }); // 48 hours

fromToValue = { from: twoMonthsAgo, to: oneMonthAgo };
.formatRelativeDate( fromToValue ); // 1 month
.formatRelativeDate( fromToValue, { unit: "week" }); // 5 weeks
.formatRelativeDate( fromToValue, { unit: "day" }); // 30 days
.formatRelativeDate( fromToValue, { unit: "day", tense: "past" }); // 30 days ago

Options:

  • tense ("past"|"future"|null), eg. "2 days ago", "in 2 days", or "2 days". Defaults to automatic detection as @iskren suggested, ie. (a) past or future if argument is {from: now, to: date}, or (b) null if argument is {from: from, to: to}.
  • unit ("day"|"week"|"month"|...), eg. "30 days", "4 weeks", or "1 month", ... Defaults to automatic detection, therefore implementing a simple approximation algorithm as @jzaefferer suggested. But, delegating any more complex things to a specific date manipulation library such as moment.js.
  • form ("long"|"short"|"narrow") used when tense = null to specify the unit form, eg. ("2 seconds", "2 secs", or "2s").

What do you think?

PS:

The "yesterday" vs. "1 day ago" problem:

Can be solved by letting users hack locale data to suite their needs if they need anything different than the default. For example, if user needs 1 day ago to be displayed instead of "yesterday", he can simply remove the main.en.dates.fields.day.relative-type--1 field of his locale data.

If user needs "yesterday" on parts of his app and "1 day ago" on others, user can always load two different locales. for example, the default en and another custom one like foo (use custom names ranging from 2-3 characters). Then, en.formatRelativeTime(...) vs. foo.formatRelativeTime(...).

The "Tue" vs. "Next Tue" problem:

Again, let user tweak locale data if he needs anything different than the default. If the case, we can help them submit fixes to CLDR.

@ichernev
Copy link

My proposal was the simplest one that would have unit auto-detection and raw api. If we want to go the extra mile and put form (only for absolute time), unit and tense, then its more work / and features. I guess jquery never had a strive for minimalism, so that's fine with me too. It means moment will be a very thin wrapper.

I guess we also need (in lieu of the other two methods):

globalize.relativeDuration(value, input_unit, options);

@jzaefferer what do you think?

@jzaefferer
Copy link
Contributor

I guess jquery never had a strive for minimalism

Why would you think that?

As for the proposals, I find all of them rather poorly named. Sticking everything into a single method doesn't make a good API. How about at least two separate methods?

As for the options @rxaviers suggested: Tense and form options look like they could be combined - why do you suggest that form should only apply when tense is null?

@rxaviers
Copy link
Member Author

How about at least two separate methods?

Any name idea?

As for the options @rxaviers suggested: Tense and form options look like they could be combined - why do you suggest that form should only apply when tense is null?

Because, there is no CLDR data for different forms on present or future tense.

@ichernev
Copy link

We'll actually have 2 methods:

.formatDuration(value, [input_unit], [options]);  // relative/absolute come from options
.formatRelativeDate(date, [options]);
.formatRelativeDate({from: date, to: date}, [options]);

The raw one can be achieved by:

.formatDuration(value, input_unit, {unit: input_unit});

We might have relative and absolute methods separately:

.formatRelativeDuration(...);
.formatAbsoluteDuration(...);

Not sure if we need the absolute date format. But in general I think relative/absolute is better outside of options (separate methods) because they have different use case.

@rxaviers
Copy link
Member Author

Naming is hard. I'm also kinda confused with the function names we currently have vs. what to expect from them.

For me, duration, relative time, time distance, time span all means the same stuff. They are all synonymous. So, having two functions: duration vs. relative date together confuses. Because, it's hard to tell what the difference between them is.

I vote for .formatDuration() (preferred) or .formatRelativeDate() for the 2 secs ago or in 2 secs thing.

I vote for .formatUnit() for the 2 seconds, 2 secs, or 2s thing, which actually stick with its CLDR data nomenclature (units.json). This function could be extended to include other units, eg. length (meter, miles, etc), mass (gram, ounce, etc), power (watt), temperature (C, F), etc which CLDR supports.

@jzaefferer
Copy link
Contributor

formatDuration and formatUnit sound good to me. You could post some of your previous examples with those new names? Would help to understand which method will have what API.

Is it correct the formatDuration will make use of formatUnit?

@ichernev
Copy link

Format unit us for absolute duration (2s). Format duration is for relative duration (in 2 seconds)

@rxaviers
Copy link
Member Author

Expanding the examples... (description updated accordingly)

formatDuration

.formatDuration( value[, options] );
.formatDuration( inTwoDays ); // in 2 days
.formatDuration( twoDaysAgo ); // 2 days ago
.formatDuration( twoDaysAgo, { unit: "hour" }); // 48 hours ago
.formatDuration({ from: christmas2001, to: newyear2002 }, { unit: "day" }); // in 7 days

Value: Date instance, or object with from and to keys and Date instance values.

Options:

  • unit ("day"|"week"|"month"|...), eg. "in 30 days", "in 4 weeks", or "in 1 month", ... Defaults to automatic detection, therefore implementing a simple approximation algorithm as Jörn suggested. But, delegating any more complex things to a specific date manipulation library such as moment.js.

formatUnit (which we'll implement as a separate issue #252)

.formatUnit( value, unit[, options] ); // 1 month
.formatUnit( 1, "month" ); // 1 month
.formatUnit( 5, "week" ); // 5 weeks
.formatUnit( 30, "day" ); // 30 days
.formatUnit( 30, "day", { form: "narrow" }); // 30d

Value: Number

Unit: String, eg. "month", "day", etc.

Options:

  • form ("long"|"short"|"narrow") used when tense = null to specify the unit form, eg. ("2 seconds", "2 secs", or "2s").

@rxaviers
Copy link
Member Author

@ichernev, perhaps, we could start implementing formatDuration by requiring the unit option. In a subsequent step, we could implement the auto-guess unit thing or even rethink whether or not we should include it. What do you think?

@rxaviers
Copy link
Member Author

@jzaefferer formatDuration and formatUnit are not used among themselves. They access different CLDR portions and they have no code in common (I guess for now).

@jzaefferer
Copy link
Contributor

Isn't .formatDuration( twoDaysAgo, { unit: "hour" }) like .formatUnit( 48, "hours" ) + "ago"?

Anyway, this API looks good to me.

@ichernev
Copy link

+ago. The one thing you need to know about i18n is you can not do +
On May 20, 2014 2:24 AM, "Jörn Zaefferer" notifications@github.com wrote:

Isn't .formatDuration( twoDaysAgo, { unit: "hour" }) like .formatUnit(
48, "hours" ) + "ago"?

Anyway, this API looks good to me.


Reply to this email directly or view it on GitHubhttps://github.com//issues/205#issuecomment-43603952
.

@rxaviers
Copy link
Member Author

Exactly, inflections and other complex stuff about languages doesn't allow "+" operations in i18n.

@ichernev
Copy link

I think we need to put the low-level functionality (just nice interface on top of cldr data) in cldr.js itself.

For example formatUnit with the API described here (value + unit + form) should go in cldr.js.
Same goes for relativeDuration value + unit (the low level API I want from the begining).

The higher level API (like auto-detecting the unit, subtracting dates and such) should go in globalize.

In that light two methods formatRelativeDuration and formatAbsoluteDuration with the exact same API makes sense. The relative version might have options for reversing / setting the sign.

@rxaviers
Copy link
Member Author

For example formatUnit with the API described here (value + unit + form) should go in cldr.js.

To properly format a unit, pluralization is required. That would mean cldr.js should support pluralization too, which is quite complex.

@ichernev
Copy link

Isn't pluralization data part of cldr data? If yes I guess it should go in cldr, or at least be able to be in with a plugin. I agree its hard to implement but its also very low-level, and well defined. So I think it should go in a separate lib, that goes in hand with cldr.js.

@rxaviers
Copy link
Member Author

Isn't pluralization data part of cldr data?

Nope. It's part of the Globalize's pluralization module (borrowing the plural rule parsing from santhoshtr/CLDRPluralRuleParser), which hasn't been landed on master yet (but will)...

Cldr.js facilitates CLDR (data) access/manipulation. Globalize, i18n. Comparing them to a filesystem, I would say cldr.js would grab the bytes of an mp3 file from the disc, Globalize would decode and play it. So, as long as an operation is part of making the access of a CLDR data easier, I think it's very clear it should be in cldr.js. Although, any operation that interprets the data somehow, I think it should be in an i18n library.

Do you think it makes sense?

rxaviers added a commit that referenced this issue Jun 20, 2014
rxaviers added a commit that referenced this issue Jun 23, 2014
@jzaefferer jzaefferer added this to the 1.1.0 milestone Dec 11, 2014
@rxaviers
Copy link
Member Author

A minor update to the API: Renaming .formatDuration() to .formatRelativeTime(). Because, relative time is a name that is more commonly used elsewhere (e.g., in CLDR and in other libraries like iLib from LG).

@rxaviers
Copy link
Member Author

Closing this issue in favor of a clean #391, which contains what has been discussed in here.

@rxaviers rxaviers removed this from the 1.1.0 milestone Feb 12, 2015
rxaviers added a commit that referenced this issue Feb 16, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants