javascript-time-ago
Intelligent, international, higly customizable relative date/time formatter (both for past and future dates).
Automatically chooses the right units (seconds, minutes, etc) to format a time interval.
Examples:
- just now
- 45s
- 5m
- 15 minutes ago
- 3 hours ago
- in 2 months
- in 5 years
- …
For React users, there's a React component.
This is a readme for version 2.x
. For older versions, see version 1.x
readme. For migrating from version 1.x
to version 2.x
, see a migration guide.
Install
npm install javascript-time-ago --save
Use
import TimeAgo from 'javascript-time-ago'
import en from 'javascript-time-ago/locale/en'
TimeAgo.addLocale(en)
const timeAgo = new TimeAgo('en-US')
timeAgo.format(new Date(), 'round')
timeAgo.format(Date.now() - 15 * 1000, 'round')
timeAgo.format(Date.now() - 60 * 1000, 'round')
timeAgo.format(Date.now() - 2 * 60 * 60 * 1000, 'round')
timeAgo.format(Date.now() - 24 * 60 * 60 * 1000, 'round')
Locales
This library includes date/time formatting rules and labels for any language.
No languages are loaded default: a developer must manually choose which languages should be loaded. The languages should be imported from javascript-time-ago/locale
and then added via TimeAgo.addLocale(...)
. An example of using Russian language:
import TimeAgo from 'javascript-time-ago'
import ru from 'javascript-time-ago/locale/ru'
TimeAgo.addLocale(ru)
const timeAgo = new TimeAgo('ru-RU')
timeAgo.format(new Date(), 'round')
timeAgo.format(Date.now() - 15 * 1000, 'round')
timeAgo.format(Date.now() - 60 * 1000, 'round')
timeAgo.format(Date.now() - 2 * 60 * 60 * 1000, 'round')
timeAgo.format(Date.now() - 24 * 60 * 60 * 1000, 'round')
Styles
This library allows for any custom logic for formatting time intervals:
-
What scale should be used for measuring time intervals: should it be precise down to the second, or should it only measure it up to a minute, or should it start from being more precise when time intervals are small and then gradually decrease its precision as time intervals get longer.
-
What labels should be used: should it use the standard built-in labels for the languages ("... minutes ago"
, "... min. ago"
, "...m"
), or should it use custom ones, or should it skip using relative time labels in some cases and instead output something like "Dec 11, 2015"
.
Such configuration comes under the name of "style".
While a completely custom "style" could be supplied, this library comes with several built-in "styles" that some people might find useful.
Following is the list of built-in "styles".
Round
Rounds the time up to the closest time measurement unit (second, minute, hour, etc).
timeAgo.format(Date.now(), 'round')
timeAgo.format(Date.now() - 15 * 1000, 'round')
timeAgo.format(Date.now() - 1.5 * 60 * 1000, 'round')
- just now
- 1 second ago
- 2 seconds ago
- …
- 59 seconds ago
- 1 minute ago
- 2 minutes ago
- …
- 59 minutes ago
- 1 hour ago
- 2 hours ago
- …
- 59 hours ago
- 1 day ago
- 2 days ago
- …
- 6 days ago
- 1 week ago
- 2 weeks ago
- 3 weeks ago
- 1 month ago
- 2 months ago
- …
- 11 months ago
- 1 year ago
- 2 years ago
- …
Round (minute)
Same as "round"
but without seconds.
timeAgo.format(Date.now(), 'round-minute')
timeAgo.format(Date.now() - 15 * 1000, 'round-minute')
timeAgo.format(Date.now() - 15 * 1000, 'round-minute')
timeAgo.format(Date.now() - 1.5 * 60 * 1000, 'round-minute')
- just now
- 1 minute ago
- 2 minutes ago
- …
- 59 minutes ago
- 1 hour ago
- 2 hours ago
- …
- 59 hours ago
- 1 day ago
- 2 days ago
- …
- 6 days ago
- 1 week ago
- 2 weeks ago
- 3 weeks ago
- 1 month ago
- 2 months ago
- …
- 11 months ago
- 1 year ago
- 2 years ago
- …
Approximate
"approximate"
style is a legacy one that has been introduced in the early versions of this library. It's basically the same as "round-minute"
with the difference that it rounds time in some cases:
just now
→ just now
40 seconds ago
→ just now
45 seconds ago
→ 1 minute ago
5 minutes ago
→ 5 minutes ago
6 minutes ago
→ 5 minutes ago
7 minutes ago
→ 5 minutes ago
8 minutes ago
→ 10 minutes ago
9 minutes ago
→ 10 minutes ago
10 minutes ago
→ 10 minutes ago
- …
timeAgo.format(Date.now(), 'approximate')
timeAgo.format(Date.now() - 15 * 1000, 'approximate')
timeAgo.format(Date.now() - 1.5 * 60 * 1000, 'approximate')
timeAgo.format(Date.now() - 3 * 60 * 1000, 'approximate')
- just now
- 1 minute ago
- 2 minutes ago
- 5 minutes ago
- 10 minutes ago
- 15 minutes ago
- 20 minutes ago
- …
- 50 minutes ago
- 1 hour ago
- 2 hours ago
- …
- 20 hours ago
- 1 day ago
- 2 days ago
- …
- 5 days ago
- 1 week ago
- 2 weeks ago
- 3 weeks ago
- 1 month ago
- 2 months ago
- …
- 10 months ago
- 1 year ago
- 2 years ago
- …
For historical reasons, "approximate"
style is the one that's used when no style
argument is passed (this will be changed in the next major version: round
or round-minute
will be the default one).
Approximate (time)
"approximate-time"
style is a legacy one that has been introduced in the early versions of this library. It's the same as the "approximate"
style but without the "ago" part.
timeAgo.format(Date.now(), 'approximate-time')
timeAgo.format(Date.now() - 15 * 1000, 'approximate-time')
timeAgo.format(Date.now() - 1.5 * 60 * 1000, 'approximate-time')
timeAgo.format(Date.now() - 3 * 60 * 1000, 'approximate-time')
- just now
- 1 minute
- 2 minutes
- 5 minutes
- 10 minutes
- 15 minutes
- 20 minutes
- …
- 50 minutes
- 1 hour
- 2 hours
- …
- 20 hours
- 1 day
- 2 days
- …
- 5 days
- 1 week
- 2 weeks
- 3 weeks
- 1 month
- 2 months
- …
- 10 months
- 1 year
- 2 years
- …
Not all locales support this style: only those having long-time.json
.
Mimics Twitter style of "time ago" labels ("1s"
, "2m"
, "3h"
, "Mar 4"
, "Apr 5, 2012"
)
timeAgo.format(new Date(), 'twitter')
timeAgo.format(new Date() - 1, 'twitter')
timeAgo.format(Date.now() - 1.5 * 60 * 1000, 'twitter')
timeAgo.format(Date.now() - 3.5 * 60 * 60 * 1000, 'twitter')
timeAgo.format(Date.now() - 4 * 24 * 60 * 60 * 1000, 'twitter')
timeAgo.format(Date.now() - 364 * 24 * 60 * 60 * 1000, 'twitter')
"twitter"
style uses Intl
for formatting day/month/year
labels. If Intl
is not available (for example, in Internet Explorer), it falls back to day/month/year labels: "1d"
, "1mo"
, "1yr"
.
For best compatibility, mini-time.json
labels should be defined for a locale. Send pull requests for the missing ones.
Same as "twitter"
style but doesn't output anything before the first minute.
timeAgo.format(new Date(), 'twitter-first-minute')
timeAgo.format(new Date() - 1, 'twitter-first-minute')
timeAgo.format(new Date() - 1, 'twitter-first-minute')
"twitter-first-minute"
style uses Intl
for formatting day/month/year
labels. If Intl
is not available (for example, in Internet Explorer), it falls back to day/month/year labels: "1d"
, "1mo"
, "1yr"
.
For best compatibility, mini-time.json
labels should be defined for a locale. Send pull requests for the missing ones.
Custom
A custom "style" object may be passed as a second parameter to .format(date, style)
, a style
being an object defining labels
and steps
.
Labels
labels
should be the name of the time labels variant that will be used for generating output. When not defined, is set to "long"
by default.
labels
can also be an array of such time label variant names, in which case each one of them is tried until a supported one is found. For example, for labels: ["mini-time", "short"]
it will search for "mini-time"
labels first and then fall back to "short"
labels if "mini-time"
labels aren't defined for the language.
"long"
, "short"
and "narrow"
time labels are always present for each language, because they're provided by CLDR. long
is the normal one, short
is an abbreviated version of long
, narrow
is supposed to be shorter than short
but ends up just being weird: it's either equal to short
or is, for example, "-1 d."
for "1 day ago"
in Russian.
Other time labels like "now"
and "mini-time"
are only defined for a small subset of languages. Send your pull requests for the missing ones.
New labels can be added by calling TimeAgo.addLabels()
function.
import TimeAgo from 'javascript-time-ago'
import en from 'javascript-time-ago/locale/en'
import { round } from 'javascript-time-ago/locale/steps'
TimeAgo.addLocale(en)
const customLabels = {
second: {
past: {
one: "{0} second earlier",
other: "{0} seconds earlier"
},
future: {
one: "{0} second later",
other: "{0} seconds later"
}
},
...
}
TimeAgo.addLabels('en', 'custom', customLabels)
const timeAgo = new TimeAgo('en-US')
const customStyle = {
steps: round,
labels: 'custom'
}
timeAgo.format(Date.now() - 10 * 1000, customStyle)
Steps
Time interval measurement "steps".
Each "step" is tried until the last matching one is found, and that "step" is then used to generate the output. If no matching step
was found, then an empty string is returned.
An example of "round"
style steps
:
[
{
formatAs: 'second'
},
{
minTime: 59.5,
formatAs: 'minute'
},
{
minTime: 59.5 * 60,
formatAs: 'hour'
},
…
]
A basic step is described by:
minTime: number
— A minimum time interval (in seconds) required for this step, meaning that minTime
controls the progression from one step to another. The first step's minTime
is 0
by default.
formatAs: string
— A time measurement unit, the labels for which are used to generate the output of this step. If the time unit isn't supported by the language, then the step is ignored. The time units supported in all languages are: second
, minute
, hour
, day
, week
, quarter
, month
, year
. For some languages, this library also defines now
unit ("just now"
).
Alternatively to minTime
, a step may specify a test()
function:
test(
date: number,
{
now: number,
future: boolean
}
): boolean
Alternatively to formatAs
, a step may specify a format()
function:
format(
date: (Date|number),
locale: string,
{
formatAs(unit: string, value: number): string,
now: number,
future: boolean
}
): string?
Built-in steps
/steps
export provides a couple of built-in "steps" that can be used when creating custom styles.
import { round, approximate } from 'javascript-time-ago/steps'
Time unit constants
/steps
export provides some utility time unit constants that could be used to calculate minTime
values when defining custom steps
:
import { minute, hour, day, week, month, year } from 'javascript-time-ago/steps'
minute === 60
hour === 60 * 60
day === 24 * 60 * 60
...
const customSteps = [{
minTime: 5 * minute,
...
}]
Future
When given future dates, .format()
produces the corresponding output.
timeAgo.format(Date.now() + 5 * 60 * 1000, 'round')
Zero time interval is a special case: by default, it's formatted in past time. To format zero time interval in future time, pass future: true
option to .format()
.
timeAgo.format(Date.now(), 'round')
timeAgo.format(Date.now() + 5 * 60 * 1000, 'round')
timeAgo.format(Date.now(), 'round', { future: true })
timeAgo.format(Date.now() + 5 * 60 * 1000, 'round', { future: true })
Now
The .format()
function accepts an optional now: number
option: it can be used in tests to specify the exact "base" timestamp relative to which the time interval will be calculated.
timeAgo.format(60 * 1000, 'round', { now: 0 })
Update Interval
When speaking of good User Experience ("UX"), a formatted relative date, once rendered, should be constantly refreshed. And for that, the application should know how often should it refresh the formatted date. For that, each step
should provide an update interval.
When a step
has formatAs
configured, then getTimeToNextUpdate()
function is created automatically for it. Otherwise, a developer should supply their own getTimeToNextUpdate()
function for a step
.
getTimeToNextUpdate(
date: (Date|number),
{
getTimeToNextUpdateForUnit(unit: string): number,
now: number,
future: boolean
}
): number?
The application can then pass getTimeToNextUpdate: true
option to .format()
to get the best time to update the relative date label.
const timeAgo = new TimeAgo('en-US')
let output
let updateTimer
function render() {
const [formattedDate, timeToNextUpdate] = timeAgo.format(date, 'round', {
getTimeToNextUpdate: true
})
output = formattedDate
updateTimer = setTimeout(render, timeToNextUpdate || 60 * 1000)
}
Default Locale
The "default locale" is the locale used when none of the locales passed to new TimeAgo()
constructor are supported. By default, the "default locale" is "en"
.
TimeAgo.setDefaultLocale('ru')
Localization internals
This library uses an Intl.RelativeTimeFormat
polyfill under the hood. The polyfill is only 3.5 kB
in size so it won't affect the total bundle size.
Some people have
requested the ability to use native Intl.RelativeTimeFormat
and Intl.PluralRules
instead of the polyfills: in this case, pass polyfill: false
option when creating a TimeAgo
instance.
new TimeAgo('en-US', { polyfill: false })
React
There is also a React component built upon this library, that autorefreshes itself.
Intl
(this is an "advanced" section)
Intl
global object is not required for this library, but, for example, if you choose to use the built-in twitter
style then it will format longer intervals as 1d
, 1mo
, 1yr
instead of Apr 10
or Apr 10, 2019
if Intl
is not available: that's because it uses Intl.DateTimeFormat
for formatting absolute dates.
Intl
is present in all modern web browsers and is absent from some of the old ones: Internet Explorer 10, Safari 9 and iOS Safari 9.x (which can be solved using Intl
polyfill).
Node.js starting from 0.12
has Intl
built-in, but only includes English locale data by default. If your app needs to support more locales than English on server side (e.g. Server-Side Rendering) then you'll need to use Intl
polyfill.
An example of applying Intl
polyfill:
npm install intl@1.2.4 --save
Node.js
import IntlPolyfill from 'intl'
const locales = ['en', 'ru', ...]
if (typeof Intl === 'object') {
if (!Intl.DateTimeFormat || Intl.DateTimeFormat.supportedLocalesOf(locales).length !== locales.length) {
Intl.DateTimeFormat = IntlPolyfill.DateTimeFormat
}
} else {
global.Intl = IntlPolyfill
}
Web browser: only download intl
package if the web browser doesn't support it, and only download the required locale.
async function initIntl() {
if (typeof Intl === 'object') {
return
}
await Promise.all([
import('intl'),
import('intl/locale-data/jsonp/en'),
import('intl/locale-data/jsonp/ru'),
...
])
}
initIntl().then(...)
License
MIT