javascript-time-ago 2.5.12 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +10 -1
  2. package/README.md +462 -316
  3. package/bundle/javascript-time-ago.js +1 -1
  4. package/bundle/javascript-time-ago.js.map +1 -1
  5. package/bundle/javascript-time-ago.min.js +1 -1
  6. package/bundle/javascript-time-ago.min.js.map +1 -1
  7. package/commonjs/FullDateFormatter.js +72 -0
  8. package/commonjs/FullDateFormatter.js.map +1 -0
  9. package/commonjs/FullDateFormatter.test.js +26 -0
  10. package/commonjs/FullDateFormatter.test.js.map +1 -0
  11. package/commonjs/TimeAgo.js +208 -106
  12. package/commonjs/TimeAgo.js.map +1 -1
  13. package/commonjs/TimeAgo.test.js +95 -10
  14. package/commonjs/TimeAgo.test.js.map +1 -1
  15. package/commonjs/steps/getStepMinTime.js +26 -19
  16. package/commonjs/steps/getStepMinTime.js.map +1 -1
  17. package/commonjs/steps/getTimeToNextUpdate.js +10 -2
  18. package/commonjs/steps/getTimeToNextUpdate.js.map +1 -1
  19. package/commonjs/style/twitter.js +2 -3
  20. package/commonjs/style/twitter.js.map +1 -1
  21. package/commonjs/style/twitter.test.js +5 -2
  22. package/commonjs/style/twitter.test.js.map +1 -1
  23. package/full-date-formatter/index.cjs +4 -0
  24. package/full-date-formatter/index.cjs.js +9 -0
  25. package/full-date-formatter/index.d.ts +6 -0
  26. package/full-date-formatter/index.js +1 -0
  27. package/full-date-formatter/package.json +15 -0
  28. package/index.cjs +1 -1
  29. package/index.cjs.js +2 -2
  30. package/index.d.ts +14 -4
  31. package/index.js +3 -1
  32. package/modules/FullDateFormatter.js +67 -0
  33. package/modules/FullDateFormatter.js.map +1 -0
  34. package/modules/FullDateFormatter.test.js +22 -0
  35. package/modules/FullDateFormatter.test.js.map +1 -0
  36. package/modules/TimeAgo.js +208 -107
  37. package/modules/TimeAgo.js.map +1 -1
  38. package/modules/TimeAgo.test.js +95 -8
  39. package/modules/TimeAgo.test.js.map +1 -1
  40. package/modules/steps/getStepMinTime.js +26 -19
  41. package/modules/steps/getStepMinTime.js.map +1 -1
  42. package/modules/steps/getTimeToNextUpdate.js +10 -2
  43. package/modules/steps/getTimeToNextUpdate.js.map +1 -1
  44. package/modules/style/twitter.js +3 -3
  45. package/modules/style/twitter.js.map +1 -1
  46. package/modules/style/twitter.test.js +5 -2
  47. package/modules/style/twitter.test.js.map +1 -1
  48. package/package.json +16 -11
package/README.md CHANGED
@@ -4,24 +4,21 @@
4
4
  [![npm downloads](https://img.shields.io/npm/dm/javascript-time-ago.svg?style=flat-square)](https://www.npmjs.com/package/javascript-time-ago)
5
5
  [![coverage](https://img.shields.io/coveralls/catamphetamine/javascript-time-ago/master.svg?style=flat-square)](https://coveralls.io/r/catamphetamine/javascript-time-ago?branch=master)
6
6
 
7
- Localized relative date/time formatting (both for past and future dates).
8
-
9
- Automatically chooses the right units (seconds, minutes, etc) to format a time interval.
10
-
11
- Examples:
7
+ Formats a `Date` into a string like `"1 day ago"`. In any language.
12
8
 
13
9
  * just now
14
10
  * 45s
15
11
  * 5m
16
12
  * 15 minutes ago
17
13
  * 3 hours ago
18
- * in 2 months
14
+ * 2 days ago
15
+ * in 4 months
19
16
  * in 5 years
20
17
  * …
21
18
 
22
- For React users, there's a [React version](https://www.npmjs.com/package/react-time-ago) [See Demo](https://catamphetamine.gitlab.io/react-time-ago/)
19
+ It also tells one how often to [refresh](#refreshing) the label as the time goes by.
23
20
 
24
- This is a readme for version `2.x`. For older versions, [see version `1.x` readme](https://github.com/catamphetamine/javascript-time-ago/tree/1.x). For migrating from version `1.x` to version `2.x`, see a [migration guide](https://github.com/catamphetamine/javascript-time-ago/blob/master/MIGRATION.md).
21
+ There's also a [React version](https://www.npmjs.com/package/react-time-ago) (see [demo](https://catamphetamine.gitlab.io/react-time-ago/))
25
22
 
26
23
  ## Install
27
24
 
@@ -29,20 +26,29 @@ This is a readme for version `2.x`. For older versions, [see version `1.x` readm
29
26
  npm install javascript-time-ago --save
30
27
  ```
31
28
 
32
- If you're not using a bundler then use a [standalone version from a CDN](#cdn).
29
+ Alternatively, one could include it on a web page [directly](#cdn) via a `<script/>` tag.
33
30
 
34
31
  ## Use
35
32
 
33
+ <!-- Migration notes: This is a readme for version `2.x`. If you're using version `1.x`, [see the old readme](https://github.com/catamphetamine/javascript-time-ago/tree/1.x). Also see a [migration guide](https://github.com/catamphetamine/javascript-time-ago/blob/master/MIGRATION.md) from version `1.x` to `2.x`. -->
34
+
35
+ To begin, decide on the set of languages that your application will be translated into. For now, let's assume that it's gonna be just English.
36
+
37
+ Then, for each of those languages, `import` the language data from `javascript-time-ago/locale/..`, and pass it to `TimeAgo.addLocale()` function.
38
+
36
39
  ```js
37
40
  import TimeAgo from 'javascript-time-ago'
38
-
39
- // English.
40
41
  import en from 'javascript-time-ago/locale/en'
41
42
 
42
- TimeAgo.addDefaultLocale(en)
43
+ // Add English language
44
+ TimeAgo.addLocale(en)
45
+ ```
43
46
 
44
- // Create formatter (English).
45
- const timeAgo = new TimeAgo('en-US')
47
+ Now you're ready to create a `new TimeAgo()` formatter for any of those languages, and use it to convert dates into strings.
48
+
49
+ ```js
50
+ // Create English formatter
51
+ const timeAgo = new TimeAgo('en')
46
52
 
47
53
  timeAgo.format(new Date())
48
54
  // "just now"
@@ -57,48 +63,68 @@ timeAgo.format(Date.now() - 24 * 60 * 60 * 1000)
57
63
  // "1 day ago"
58
64
  ```
59
65
 
60
- ## Locales
66
+ To change the output style, see the list of available [formatting styles](#formatting-styles).
67
+
68
+ P.S. After rendering a label, don't forget to [refresh](#refreshing) it as the time goes by.
69
+
70
+ ## Languages
71
+
72
+ This library supports a lot of languages. None of those languages are loaded by default. A developer must manually choose which languages should be loaded and then call `TimeAgo.addLocale()` for each one of them.
61
73
 
62
- This library includes date/time formatting rules and labels for any language.
74
+ The `locale` argument of `new TimeAgo(locale)` constructor will be matched against the list of added languages, and the first matching one will be used. For example, `new TimeAgo("en")` and `new TimeAgo("en-US")` will both use `"en"` language.
63
75
 
64
- No languages are loaded default: a developer must manually choose which languages should be loaded. Languages should be imported from [`javascript-time-ago/locale`](https://unpkg.com/browse/javascript-time-ago/locale/) and then added via `TimeAgo.addLocale()` or `TimeAgo.addDefaultLocale()`.
76
+ If the language for the specified `locale` hasn't been added, it will retry with a "default" `locale`. For that, a "default" `locale` has to have been added by calling `TimeAgo.addDefaultLocale()`. Otherwise, when there's no "default" locale to fall back to, it will just throw an error.
65
77
 
66
- The `locale` argument of `new TimeAgo(locale)` constructor is matched against the list of added languages, and the first matching one is used. For example, locales `"en-US"` and `"en-GB"` both match `"en"` language. If none of the added languages match the `locale`, the "default language" is used. If the "default language" hasn't been added, an error is thrown.
78
+ <!-- or `TimeAgo.setDefaultLocale("en")`. By default, the default `locale` is `"en"`, although it still has to be added manually. -->
67
79
 
68
- The "default language" is `"en"` by default, and can be set either by calling `addDefaultLocale()`:
80
+ So how is "default" locale useful? It frees a developer from worrying about whether the `locale` argument is supported or not. They can just create a `new TimeAgo()` formatter with whatever `locale` argument and not even worry about potentially crashing the application in case it throws an error for that `locale`.
81
+
82
+ In the following example, the application supports three languages — English, German and French — and English is set to be the "default" one that will be used for any other language like Spanish.
69
83
 
70
84
  ```js
71
- import ru from 'javascript-time-ago/locale/ru'
85
+ import en from 'javascript-time-ago/locale/en'
72
86
  import de from 'javascript-time-ago/locale/de'
73
- import es from 'javascript-time-ago/locale/es'
87
+ import fr from 'javascript-time-ago/locale/fr'
74
88
 
75
- TimeAgo.addLocale(ru)
89
+ TimeAgo.addDefaultLocale(en)
76
90
  TimeAgo.addLocale(de)
77
- TimeAgo.addDefaultLocale(es)
91
+ TimeAgo.addLocale(fr)
78
92
  ```
79
93
 
80
- or by calling `setDefaultLocale()`:
94
+ ```js
95
+ // "es" locale hasn't been added, so it falls back to "en".
96
+ const timeAgo = new TimeAgo('es')
97
+
98
+ timeAgo.format(new Date())
99
+ // "just now"
100
+ ```
101
+
102
+ `TimeAgo.addDefaultLocale()` is just a shortcut for `TimeAgo.addLocale()` + `TimeAgo.setDefaultLocale()`, so the code above is the same as the code below.
81
103
 
82
104
  ```js
83
- import ru from 'javascript-time-ago/locale/ru'
105
+ import en from 'javascript-time-ago/locale/en'
84
106
  import de from 'javascript-time-ago/locale/de'
85
- import es from 'javascript-time-ago/locale/es'
107
+ import fr from 'javascript-time-ago/locale/fr'
86
108
 
87
- TimeAgo.addLocale(ru)
109
+ TimeAgo.addLocale(en)
88
110
  TimeAgo.addLocale(de)
89
- TimeAgo.addLocale(es)
111
+ TimeAgo.addLocale(fr)
90
112
 
91
- TimeAgo.setDefaultLocale('es')
113
+ TimeAgo.setDefaultLocale('en')
92
114
  ```
93
115
 
94
- In some cases, a developer might prefer to specify a list of `locales` to choose from rather than a single `locale`:
116
+ `new TimeAgo()` constructor also supports passing a list of `locales` to choose from. In that case, it will choose the first one that works.
95
117
 
96
118
  ```js
119
+ // Add English and German languages
97
120
  TimeAgo.addDefaultLocale(en)
98
121
  TimeAgo.addLocale(de)
99
122
 
100
- // "de" language will be used, as it's the first one to match.
101
- new TimeAgo(['ru-RU', 'de-DE', 'en-US'])
123
+ // "de" language will be chosen because it's the first one that works.
124
+ const timeAgo = new TimeAgo(['ru-RU', 'de-DE', 'en-US'])
125
+
126
+ timeAgo.format(new Date())
127
+ // "gerade jetzt"
102
128
  ```
103
129
 
104
130
  <!--
@@ -109,15 +135,15 @@ require('javascript-time-ago/load-all-locales')
109
135
  ```
110
136
  -->
111
137
 
112
- An example of using Russian language:
138
+ <!--
139
+ An example of formatting dates in Russian:
113
140
 
114
141
  ```js
115
142
  import TimeAgo from 'javascript-time-ago'
116
-
117
- // Russian.
118
143
  import ru from 'javascript-time-ago/locale/ru'
119
144
 
120
- TimeAgo.addDefaultLocale(ru)
145
+ // Add Russian language.
146
+ TimeAgo.addLocale(ru)
121
147
 
122
148
  const timeAgo = new TimeAgo('ru-RU')
123
149
 
@@ -133,40 +159,37 @@ timeAgo.format(Date.now() - 2 * 60 * 60 * 1000)
133
159
  timeAgo.format(Date.now() - 24 * 60 * 60 * 1000)
134
160
  // "1 день назад"
135
161
  ```
162
+ -->
136
163
 
137
- ## Styles
138
-
139
- This library allows for any custom logic for formatting time intervals:
140
-
141
- * 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.
164
+ ## Formatting Styles
142
165
 
143
- * 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"`.
166
+ A "formatting style" defines how a date should be formatted relative to the current time.
144
167
 
145
- Such configuration comes under the name of "style".
168
+ It could be precise up to a second with `"1 second ago"`, `"2 seconds ago"`, `"3 seconds ago"`, etc labels, or it could prefer to combine all those under a single `"less than a minute ago"` label.
146
169
 
147
- While a completely [custom](#custom) "style" could be supplied, this library comes with several [built-in ](https://github.com/catamphetamine/javascript-time-ago/tree/master/source/style) "styles" that some people might find useful.
170
+ It could use verbose labels like `"1 minute ago"` or it could prefer shorter variants like `"1 min. ago"` or even `"1m"`. Or it could prefer to output a full date like `"Dec 11, 2015"` for dates that're older than 1 year from now.
148
171
 
149
- Following is the list of built-in "styles".
172
+ While one could certainly implement their own [custom](#custom-style) formatting "style" from scratch, most applications would be totally fine with one of the few already-available styles that're described below.
150
173
 
151
174
  ### Round
152
175
 
153
- Rounds the time up to the closest time measurement unit (second, minute, hour, etc).
176
+ `"round"` style just rounds the time difference up to the closest unit of time second, minute, hour, etc — and then returns a label for that unit of time.
154
177
 
155
178
  ```js
156
179
  timeAgo.format(Date.now(), 'round')
157
- // 0 seconds ago "just now"
180
+ // 0 seconds ago: "just now"
158
181
 
159
182
  timeAgo.format(Date.now() - 1 * 1000, 'round')
160
- // 1 second ago "1 second ago"
183
+ // 1 second ago: "1 second ago"
161
184
 
162
185
  timeAgo.format(Date.now() - 29 * 1000, 'round')
163
- // 29 seconds ago "29 seconds ago"
186
+ // 29 seconds ago: "29 seconds ago"
164
187
 
165
188
  timeAgo.format(Date.now() - 30 * 1000, 'round')
166
- // 30 seconds ago "1 minute ago"
189
+ // 30 seconds ago: "1 minute ago"
167
190
 
168
191
  timeAgo.format(Date.now() - 1.5 * 60 * 1000, 'round')
169
- // 1.5 minutes ago "2 minutes ago"
192
+ // 1.5 minutes ago: "2 minutes ago"
170
193
  ```
171
194
 
172
195
  * just now
@@ -201,17 +224,17 @@ timeAgo.format(Date.now() - 1.5 * 60 * 1000, 'round')
201
224
 
202
225
  ### Round (minute)
203
226
 
204
- Same as `"round"` style but without seconds. This is the default style.
227
+ `"round-minute"` style is same as `"round"` style but without seconds. This is the default style that is used when no custom style is specified.
205
228
 
206
229
  ```js
207
230
  timeAgo.format(Date.now(), 'round-minute')
208
- // 0 seconds ago "just now"
231
+ // 0 seconds ago: "just now"
209
232
 
210
233
  timeAgo.format(Date.now() - 29 * 1000, 'round-minute')
211
- // 29 seconds ago "just now"
234
+ // 29 seconds ago: "just now"
212
235
 
213
236
  timeAgo.format(Date.now() - 30 * 1000, 'round-minute')
214
- // 30 seconds ago "1 minute ago"
237
+ // 30 seconds ago: "1 minute ago"
215
238
 
216
239
  // The rest is same as "round" style.
217
240
  ```
@@ -223,80 +246,80 @@ timeAgo.format(Date.now() - 30 * 1000, 'round-minute')
223
246
 
224
247
  ### Mini
225
248
 
226
- Same as `"round"` style but as short as possible and without `" ago"`. Also, [doesn't include](https://github.com/catamphetamine/javascript-time-ago/issues/40) "weeks".
249
+ `"mini"` style is same as `"round"` style but with labels that're as short as possible, without the `" ago"` part, and it [doesn't](https://github.com/catamphetamine/javascript-time-ago/issues/40) output "weeks".
227
250
 
228
251
  ```js
229
252
  timeAgo.format(new Date(), 'mini')
230
- // 0 seconds ago "0s"
253
+ // 0 seconds ago: "0s"
231
254
 
232
255
  timeAgo.format(new Date() - 1 * 1000, 'mini')
233
- // 1 second ago "1s"
256
+ // 1 second ago: "1s"
234
257
 
235
258
  timeAgo.format(Date.now() - 2 * 60 * 1000, 'mini')
236
- // 2 minutes ago "2m"
259
+ // 2 minutes ago: "2m"
237
260
 
238
261
  timeAgo.format(Date.now() - 3 * 60 * 60 * 1000, 'mini')
239
- // 3 hours ago "3h"
262
+ // 3 hours ago: "3h"
240
263
 
241
264
  timeAgo.format(Date.now() - 4 * 24 * 60 * 60 * 1000, 'mini')
242
- // 4 days ago "4d"
265
+ // 4 days ago: "4d"
243
266
 
244
267
  timeAgo.format(Date.now() - 23 * 24 * 60 * 60 * 1000, 'mini')
245
- // 23 days ago "23d"
268
+ // 23 days ago: "23d"
246
269
 
247
270
  timeAgo.format(Date.now() - 5 * 30 * 24 * 60 * 60 * 1000, 'mini')
248
- // 5 months ago "5mo"
271
+ // 5 months ago: "5mo"
249
272
 
250
273
  timeAgo.format(Date.now() - 12 * 30 * 24 * 60 * 60 * 1000, 'mini')
251
- // 1 year ago "1yr"
274
+ // 1 year ago: "1yr"
252
275
  ```
253
276
 
254
277
  For best compatibility, `mini.json` labels should be [defined](https://github.com/catamphetamine/javascript-time-ago/tree/master/locale-more-styles) for a locale, otherwise you might [end up with](https://github.com/catamphetamine/javascript-time-ago/issues/49) labels like `"-1m"` for "one minute ago" for some languages. Send `mini.json` pull requests for the missing languages if you speak those.
255
278
 
256
279
  ### Mini (now)
257
280
 
258
- Same as `"mini"` style but outputs `"now"` instead of `"0s"`.
281
+ `"mini-now"` style is same as `"mini"` style with the only difference that it outputs `"now"` instead of `"0s"`.
259
282
 
260
283
  ```js
261
284
  timeAgo.format(new Date(), 'mini-now')
262
- // 0 seconds ago "now"
285
+ // 0 seconds ago: "now"
263
286
 
264
287
  timeAgo.format(new Date() - 1 * 1000, 'mini-now')
265
- // 1 second ago "1s"
288
+ // 1 second ago: "1s"
266
289
 
267
290
  // The rest is same as "mini" style.
268
291
  ```
269
292
 
270
293
  ### Mini (minute)
271
294
 
272
- Same as `"mini"` style but without seconds (starts with minutes).
295
+ `"mini-minute"` style is same as `"mini"` style but without seconds.
273
296
 
274
297
  ```js
275
298
  timeAgo.format(new Date(), 'mini-minute')
276
- // 0 seconds ago "0m"
299
+ // 0 seconds ago: "0m"
277
300
 
278
301
  timeAgo.format(new Date() - 29 * 1000, 'mini-minute')
279
- // 29 seconds ago "0m"
302
+ // 29 seconds ago: "0m"
280
303
 
281
304
  timeAgo.format(new Date() - 30 * 1000, 'mini-minute')
282
- // 30 seconds ago "1m"
305
+ // 30 seconds ago: "1m"
283
306
 
284
307
  // The rest is same as "mini" style.
285
308
  ```
286
309
 
287
310
  ### Mini (minute-now)
288
311
 
289
- Same as `"mini-minute"` style but outputs `"now"` instead of `"0m"`.
312
+ `"mini-minute-now"` style is same as `"mini-minute"` style with the only difference that it outputs `"now"` instead of `"0m"`.
290
313
 
291
314
  ```js
292
315
  timeAgo.format(new Date(), 'mini-minute-now')
293
- // 0 seconds ago "now"
316
+ // 0 seconds ago: "now"
294
317
 
295
318
  timeAgo.format(new Date() - 29 * 1000, 'mini-minute-now')
296
- // 29 seconds ago "now"
319
+ // 29 seconds ago: "now"
297
320
 
298
321
  timeAgo.format(new Date() - 30 * 1000, 'mini-minute-now')
299
- // 30 seconds ago "1m"
322
+ // 30 seconds ago: "1m"
300
323
 
301
324
  // The rest is same as "mini" style.
302
325
  ```
@@ -308,13 +331,13 @@ Same as `"twitter"` style but doesn't output anything before the first minute.
308
331
 
309
332
  ```js
310
333
  timeAgo.format(new Date(), 'twitter-first-minute')
311
- // 0 seconds ago ""
334
+ // 0 seconds ago: ""
312
335
 
313
336
  timeAgo.format(new Date() - 59 * 1000, 'twitter-first-minute')
314
- // 59 seconds ago ""
337
+ // 59 seconds ago: ""
315
338
 
316
339
  timeAgo.format(new Date() - 60 * 1000, 'twitter-first-minute')
317
- // 1 minute ago "1m"
340
+ // 1 minute ago: "1m"
318
341
 
319
342
  // The rest is same as "twitter" style.
320
343
  ```
@@ -322,20 +345,20 @@ timeAgo.format(new Date() - 60 * 1000, 'twitter-first-minute')
322
345
 
323
346
  ### Twitter
324
347
 
325
- Mimics [Twitter](https://twitter.com) style of "time ago" labels (`"1s"`, `"2m"`, `"3h"`, `"Mar 4"`, `"Apr 5, 2012"`)
348
+ `"twitter"` style mimicks [Twitter](https://twitter.com) labels: `"1s"`, `"2m"`, `"3h"`, `"Mar 4"`, `"Apr 5, 2012"`.
326
349
 
327
350
  ```js
328
351
  timeAgo.format(new Date(), 'twitter')
329
- // 0 seconds ago "0s"
352
+ // 0 seconds ago: "0s"
330
353
 
331
354
  timeAgo.format(new Date() - 1 * 1000, 'twitter')
332
- // 1 second ago "1s"
355
+ // 1 second ago: "1s"
333
356
 
334
357
  timeAgo.format(Date.now() - 2 * 60 * 1000, 'twitter')
335
- // 2 minutes ago "2m"
358
+ // 2 minutes ago: "2m"
336
359
 
337
360
  timeAgo.format(Date.now() - 3 * 60 * 60 * 1000, 'twitter')
338
- // 3 hours ago "3h"
361
+ // 3 hours ago: "3h"
339
362
 
340
363
  timeAgo.format(Date.now() - 4 * 24 * 60 * 60 * 1000, 'twitter')
341
364
  // More than 24 hours ago → `month/day` ("Mar 4")
@@ -344,98 +367,134 @@ timeAgo.format(Date.now() - 364 * 24 * 60 * 60 * 1000, 'twitter')
344
367
  // Another year → `month/day/year` ("Mar 5, 2017")
345
368
  ```
346
369
 
347
- `"twitter"` style uses [`Intl`](https://gitlab.com/catamphetamine/relative-time-format#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"`.
370
+ `"twitter"` style uses [`Intl.DateTimeFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat) when formatting `day/month/year` labels. When `Intl` is not available for example, in Internet Explorer it falls back to the usual short labels: `"1d"`, `"1mo"`, `"1yr"`, etc.
348
371
 
349
- For best compatibility, `mini.json` labels should be defined for a [locale](https://github.com/catamphetamine/javascript-time-ago/tree/master/locale-more-styles). Send pull requests for the missing ones.
372
+ For best compatibility, `mini.json` labels should be [defined](https://github.com/catamphetamine/javascript-time-ago/tree/master/locale-more-styles) for a locale. Send `mini.json` pull requests for the missing languages if you speak those.
350
373
 
351
374
  ### Twitter (now)
352
375
 
353
- Same as `"twitter"` style but outputs `"now"` instead of `"0s"`.
376
+ `"twitter-now"` style is same as `"twitter"` style with the only difference that it outputs `"now"` instead of `"0s"`.
354
377
 
355
378
  ```js
356
379
  timeAgo.format(new Date(), 'twitter-now')
357
- // 0 seconds ago "now"
380
+ // 0 seconds ago: "now"
358
381
 
359
382
  timeAgo.format(new Date() - 1 * 1000, 'twitter-now')
360
- // 1 second ago "1s"
383
+ // 1 second ago: "1s"
361
384
 
362
385
  // The rest is same as "twitter" style.
363
386
  ```
364
387
 
365
388
  ### Twitter (minute)
366
389
 
367
- Same as `"twitter"` style but without seconds (starts with minutes).
390
+ `"twitter-minute"` style is same as `"twitter"` style but without seconds.
368
391
 
369
392
  ```js
370
393
  timeAgo.format(new Date(), 'twitter-minute')
371
- // 0 seconds ago "0m"
394
+ // 0 seconds ago: "0m"
372
395
 
373
396
  timeAgo.format(new Date() - 29 * 1000, 'twitter-minute')
374
- // 29 seconds ago "0m"
397
+ // 29 seconds ago: "0m"
375
398
 
376
399
  timeAgo.format(new Date() - 30 * 1000, 'twitter-minute')
377
- // 30 seconds ago "1m"
400
+ // 30 seconds ago: "1m"
378
401
 
379
402
  // The rest is same as "twitter" style.
380
403
  ```
381
404
 
382
405
  ### Twitter (minute-now)
383
406
 
384
- Same as `"twitter-minute"` style but outputs `"now"` instead of `"0m"`.
407
+ `"twitter-minute-now"` style is same as `"twitter-minute"` style with the only difference that it outputs `"now"` instead of `"0m"`.
385
408
 
386
409
  ```js
387
410
  timeAgo.format(new Date(), 'twitter-minute-now')
388
- // 0 seconds ago "now"
411
+ // 0 seconds ago: "now"
389
412
 
390
413
  timeAgo.format(new Date() - 29 * 1000, 'twitter-minute-now')
391
- // 29 seconds ago "now"
414
+ // 29 seconds ago: "now"
392
415
 
393
416
  timeAgo.format(new Date() - 30 * 1000, 'twitter-minute-now')
394
- // 30 seconds ago "1m"
417
+ // 30 seconds ago: "1m"
395
418
 
396
419
  // The rest is same as "twitter" style.
397
420
  ```
398
421
 
399
422
  ### Twitter (first minute)
400
423
 
401
- Same as `"twitter"` style but doesn't output anything before the first minute.
424
+ `"twitter-first-minute"` style is same as `"twitter"` style with the only difference that it doesn't output anything before the first minute.
402
425
 
403
426
  ```js
404
427
  timeAgo.format(new Date(), 'twitter-first-minute')
405
- // 0 seconds ago ""
428
+ // 0 seconds ago: ""
406
429
 
407
430
  timeAgo.format(new Date() - 29 * 1000, 'twitter-first-minute')
408
- // 29 seconds ago ""
431
+ // 29 seconds ago: ""
409
432
 
410
433
  timeAgo.format(new Date() - 30 * 1000, 'twitter-first-minute')
411
- // 30 seconds ago "1m"
434
+ // 30 seconds ago: "1m"
412
435
 
413
436
  // The rest is same as "twitter" style.
414
437
  ```
415
438
 
416
- ## Custom
439
+ ## Custom Style
440
+
441
+ A custom "style" object may be passed as a second argument to `.format(date, style)` function. A `style` object should have two properties: `labels` and `steps`.
442
+
443
+ Refer to the definition of the [built-in styles](https://github.com/catamphetamine/javascript-time-ago/tree/master/source/style) for an example.
444
+
445
+ <details>
446
+ <summary>How does a formatting style work</summary>
447
+
448
+ ######
449
+
450
+ The time range (in seconds) spans from `-∞` to `+∞`, with "now" at `0` point. This entire range gets split into intervals, each with its own label. Example:
451
+
452
+ * Interval from `0` to `-1 second` is assigned `"just now"` label.
453
+ * Interval from `-1 second` to `-1 minute` is assigned `"{0} second(s) ago"` label.
454
+ * Interval from `-1 minute` to `-1 hour` is assigned `"{0} minute(s) ago"` label.
455
+ * ...
456
+ * Interval from `0` to `+1 second` is assigned `"in a moment"` label.
457
+ * Interval from `+1 second` to `+1 minute` is assigned `"in {0} second(s)"` label.
458
+ * Interval from `+1 minute` to `+1 hour` is assigned `"in {0} minute(s)"` label.
459
+ * ...
460
+
461
+ Intervals follow each other without any gaps, so the entire time range is divided into such intervals.
417
462
 
418
- A custom "style" object may be passed as a second parameter to `.format(date, style)`, a `style` being an object defining `labels` and `steps`.
463
+ Now the job of the `format(date)` function is simple: it calculates the time difference (in seconds) between `date` and "now", and then maps that number onto the time range to see what interval it falls into. Then it returns the label for that interval, replacing `{0}` with the time difference number converted to the unit of time used by the interval.
464
+
465
+ For example, for `const date = new Date(Date.now() - 2 * 60 * 1000)`, `format(date)` first calculates the difference between the `date` and `Date.now()`, which is `-2 * 60` seconds, and then maps those `-2 * 60` seconds onto the time range and finds that it falls into the `-1 min … -1 hour` interval. So it returns the label for that interval, which is `"{0} minute(s) ago"`, replacing `{0}` with `2` because the unit of time used by the interval is `"minute"` which is equal to `60` seconds, so `-2 * 60 / 60 === -2`.
466
+
467
+ As one can see, with this approach, there could be an infinite amount of all kinds of formatting styles, and any possible formatting style could be expressed with this simple logic.
468
+ </details>
419
469
 
420
470
  ### Labels
421
471
 
422
- `labels` should be the name of the time labels variant that will be used for generating output. When not defined, is set to [`"long"`](#labels) by default.
472
+ `labels` property value should be the type of labels to output. There're several types of labels available:
423
473
 
424
- `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", "short"]` it will search for `"mini"` labels first and then fall back to `"short"` labels if `"mini"` labels aren't defined for the language.
474
+ * Labels that're provided by [Unicode CLDR](http://cldr.unicode.org/) for all languages:
475
+ * `long` labels are the "normal" ones. Example: `"1 minute ago"`.
476
+ * `short` labels are an abbreviated version of `long` ones. Example: `"1 min. ago"`.
477
+ * `narrow` labels are supposed to be shorter than `short` ones but for some reason they look fine for some languages and weird for other ones. For example, a `short` label for `"1 day ago"` is `"1d ago"` in English, which looks fine, and `"-1 d."` in Russian, which looks weird. So I personally don't use `narrow` labels.
478
+ * Labels that're provided by the community via pull requests, available for a [subset](https://github.com/catamphetamine/javascript-time-ago/tree/master/locale-more-styles) of languages. If your language is missing from the list, you could create a pull request for it.
479
+ * `mini` labels are the shortest. Example: `"1m"`.
480
+ * `now` labels describe the time interval between `-1 second` and `+1 second`. There're 3 labels total: one for `-0.5 sec`, one for `0 sec`, and one for `0.5 sec`. Example: `"just now"`, `"now"`, `"in a moment"`.
425
481
 
426
- [`"long"`, `"short"` and `"narrow"`](https://unpkg.com/browse/relative-time-format/locale/en.json) time labels are always present for each language, because they're provided by [CLDR](http://cldr.unicode.org/). `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.
482
+ The default value for the `labels` property is `"long"`.
427
483
 
428
- Other time labels like `"now"` and `"mini"` are only defined for a [small subset](https://github.com/catamphetamine/javascript-time-ago/tree/master/locale-more-styles) of languages. Send your pull requests for the missing ones.
484
+ `labels` can also be an array of label types, in which case the first supported label type will be used. For example, for `labels: ["mini", "short"]` it will search for `"mini"` labels first and then fall back to `"short"` labels if `"mini"` labels aren't defined for the language. This could be useful when defining a "style" with labels that might not be defined for all languages.
429
485
 
430
- New labels can be added by calling `TimeAgo.addLabels()` function.
486
+ One could also supply custom labels. To do that, define `past` and `future` labels for each unit of time — `second`, `minute`, `hour`, `day`, `week`, `month`, `quarter`, `year` — and then pass the object to `TimeAgo.addLabels()` function.
431
487
 
432
488
  ```js
433
489
  import TimeAgo from 'javascript-time-ago'
434
490
  import en from 'javascript-time-ago/locale/en'
491
+
492
+ // Steps from the built-in "round" style can be reused in custom styles.
435
493
  import { round } from 'javascript-time-ago/steps'
436
494
 
437
495
  TimeAgo.addDefaultLocale(en)
438
496
 
497
+ // Define custom labels.
439
498
  const customLabels = {
440
499
  second: {
441
500
  past: {
@@ -450,115 +509,123 @@ const customLabels = {
450
509
  ...
451
510
  }
452
511
 
512
+ // Add the custom labels for English language under "custom" name.
453
513
  TimeAgo.addLabels('en', 'custom', customLabels)
454
514
 
515
+ // Create English formatter.
455
516
  const timeAgo = new TimeAgo('en-US')
456
517
 
518
+ // Define a custom style that reuses the `steps` from the built-in "round" style
519
+ // and uses the newly added "custom" labels.
457
520
  const customStyle = {
458
521
  steps: round,
459
522
  labels: 'custom'
460
523
  }
461
524
 
525
+ // Format a "10 seconds ago" date using the new custom style.
462
526
  timeAgo.format(Date.now() - 10 * 1000, customStyle)
463
- // "10 seconds earlier"
527
+ // Returns "10 seconds earlier"
464
528
  ```
465
529
 
466
530
  ### Steps
467
531
 
468
- Time interval measurement "steps".
532
+ `steps` property should define a set of intervals that cover the time difference from `0` to `±∞`.
533
+
534
+ The `.format()` function starts at the first step and then moves to next one as long as the time difference passes the `minTime` threshold of the next step. The process is repeated until it stops at a certain step. That step is used to output the label.
469
535
 
470
- 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.
536
+ If the `.format()` function doesn't pass the `minTime` threshold of the first step then it just outputs an empty string.
471
537
 
472
- An example of `"round"` style `steps`:
538
+ Here's an example of `steps` that're used in the built-in `"round"` style:
473
539
 
474
540
  ```js
475
541
  [
476
542
  {
477
- // "second" labels are used for formatting the output.
543
+ // Starting from the time difference `0`,
544
+ // use "second" labels.
478
545
  formatAs: 'second'
479
546
  },
480
547
  {
481
- // This step is effective starting from 59.5 seconds.
482
- minTime: 60,
483
- // "minute" labels are used for formatting the output.
548
+ // When the time difference becomes at least 1 minute (after rounding),
549
+ // use "minute" labels.
484
550
  formatAs: 'minute'
485
551
  },
486
552
  {
487
- // This step is effective starting from 59.5 minutes.
488
- minTime: 60 * 60,
489
- // "hour" labels are used for formatting the output.
553
+ // When the time difference becomes at least 1 hour (after rounding),
554
+ // use "hour" labels.
490
555
  formatAs: 'hour'
491
556
  },
492
557
 
493
558
  ]
494
559
  ```
495
560
 
496
- A step can be described by:
561
+ Each "step" could be described by the following properties:
562
+ * `formatAs?: string` — The labels for which unit of time to use in the output: `"second"`, `"minute"`, etc.
563
+ * `format?: (date) => string?` — If a developer doesn't like to use the standard `formatAs` labels, they could output any custom label for a given `date`.
564
+ * `minTime?: number` — The minimum time difference (in seconds) required for the step. When not specified, will be derived from `formatAs` property — for example, `formatAs: "minute"` → `minTime: 60` with `round: "floor"`.
497
565
 
498
566
  <!-- * `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. -->
499
567
 
500
568
  <!-- In some cases, when using `unit`s that may or may not be defined for a language, a developer could support both cases: when the `unit` is available and when it's not. For that, they'd use a special `minTime: { [stepId]: minTime, ..., default: minTime }` property that overrides `min` when the previous eligible step's `id` is `[stepId]`. -->
501
569
 
502
- <!-- * `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](https://github.com/catamphetamine/javascript-time-ago/tree/master/locale-more-styles), this library also defines `now` unit (`"just now"`). -->
570
+ <!-- * `formatAs: string` — A unit of time, the labels for which are used to generate the output of this step. If the unit of time isn't supported by the language, then the step is ignored. The units of time that're supported in all languages are: `second`, `minute`, `hour`, `day`, `week`, `quarter`, `month`, `year`. For [some languages](https://github.com/catamphetamine/javascript-time-ago/tree/master/locale-more-styles), this library also defines `now` unit (`"just now"`). -->
503
571
 
504
572
  <!-- * `factor` — A divider for the time interval value which is in seconds. For example, if `unit` is `"seconds"` then `factor` should be `1`, and if `unit` is `"minutes"` then `factor` should be `60` because to get the amount of minutes one should divide the amout of seconds by `60`. This `factor` property is actually a redundant one and can be derived from `unit` so it will be removed in the next major version. -->
505
573
 
506
574
  <!-- * `granularity` — (advanced) Time interval value "granularity". For example, it could be set to `5` for minutes to allow only 5-minute increments when formatting time intervals: `0 minutes`, `5 minutes`, `10 minutes`, etc. Perhaps this feature will be removed because there seem to be no use cases of it in the real world. -->
507
575
 
508
- ##### `minTime`
576
+ <details>
577
+ <summary>More on <code>minTime</code></summary>
509
578
 
510
- A minimum time interval (in seconds) required for a step, meaning that `minTime` controls the progression from one step to another. Every step must define a `minTime` in one way or another. The first step's `minTime` is `0` by default.
579
+ ######
511
580
 
512
- If a step is defined by [`formatAs`](#formatas), and its previous step is also defined by `formatAs`, then such step's `minTime`, if not specified, is calculated automatically according to the selected ["rounding"](#rounding). For example, if the previous step is `{ formatAs: 'second' }` and the current step is `{ formatAs: 'minute' }` then the current step's `minTime` is automatically calculated as `59.5` when `round` is "round" (default), and `60` when `round` is "floor".
581
+ `minTime` threshold is the minimum time difference (in seconds) required for the step. Basically, `minTime` controls the progression from one step to another: it can't progress to the next step until the time difference (in seconds) reaches the `minTime` of that step.
513
582
 
514
- While `minTime` is usually a number, it could also be a `function` returning a number in order to support unusual cases like absolute date formatting in [`"twitter"`](https://github.com/catamphetamine/javascript-time-ago/blob/master/source/style/twitter.js) style.
583
+ Every step has to specify `minTime`, either explicitly or implicitly. An example of "implicitly" would be the first step's `minTime` which is `0` by default.
515
584
 
516
- ```js
517
- minTime(
518
- date: number, // The date argument, converted to a timestamp.
519
- {
520
- future: boolean, // Is `true` if `date > now`, or if `date === now`
521
- // and `future: true` option was passed to `.format()`.
522
-
523
- getMinTimeForUnit(unit: string, prevUnit: string?): number?
524
- // Returns the `minTime` for a transition from a
525
- // previous step with `formatAs: prevUnit` to a
526
- // step with `formatAs: unit`.
527
- // For example, if the previous step is `formatAs: "second"`,
528
- // then `getMinTimeForUnit('minute')` returns `59.5`
529
- // when `round` is "round", and `60` when `round` is "floor".
530
- // A developer can also explicitly specify the previous step's unit:
531
- // `getMinTimeForUnit('minute', 'second')`.
532
- // Returns `undefined` if `unit` or `prevUnit` are not supported.
533
- }
534
- ): number
535
- ```
585
+ Another example of "implicitly" would be under-the-hood calcuation of `minTime` based on the unit of time of the step: when a step specifies [`formatAs`](#formatas) property, and the previous step also specifies `formatAs` property, then the step's `minTime`, when not explicitly specified, is calculated automatically from those `formatAs` properties according to the selected ["rounding"](#rounding). For example, if the previous step is `{ formatAs: "second" }` and the current step is `{ formatAs: "minute" }` then the current step's `minTime` is automatically calculated to be `59.5` when `round` is set to `"round"` (default), or `60` when `round` is set to `"floor"`.
536
586
 
537
- ##### `formatAs`
587
+ So in almost all cases, the library automatically calculates `minTime` for you. However, if you're implementing some kind of an extravagant custom "style" then for steps that don't specify `formatAs` property you will be required to specify `minTime` explicitly.
538
588
 
539
- A time measurement unit, the labels for which are used to generate the output of a 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`, `month`, `quarter`, `year`. For [some languages](https://github.com/catamphetamine/javascript-time-ago/tree/master/locale-more-styles), this library also defines `now` unit (`"just now"`).
589
+ In that case, even though `minTime` could be specified as a number, the recommended way is to specify it as a `function` returning a number because that would be the only way to account for the [rounding](#rounding) setting, which, by default, is `"round"`, but could also be `"floor"`.
540
590
 
541
- ##### `format()`
591
+ * `minTime()` — Returns the `minTime` number.
592
+ * Arguments:
593
+ * `date: number` — The `date`, converted to a timestamp number. Not a time difference.
594
+ * `parameters: object`
595
+ * `future: boolean` — Is `true` either when `date > now` or when `date === now` and `future: true` parameter was passed to `.format()`.
596
+ * `getMinTimeForUnit(unit: string, prevUnit?: string): number?` — returns the minimum time difference (in seconds) required for moving from a step with `formatAs: prevUnit` to a step with `formatAs: unit`.
597
+ * Example:
598
+ * When `round` is set to `"round"` (default), `getMinTimeForUnit("minute", "second")` returns `59.5`, meaning that the minimum time difference for moving from `"second"` labels to `"minute"` labels is `59.5` seconds, and for smaller time differences it should stay at `"second"` labels.
599
+ * When `round` is set to `"foor"`, `getMinTimeForUnit("minute", "second")` returns `60`, meaning that the minimum time difference for moving from `"second"` labels to `"minute"` labels is `60` seconds, and for smaller time differences it should stay at `"second"` labels.
600
+ * The second argument — `prevUnit` — is not required and will be automatically set to the previous step's `formatAs` property value.
601
+ * If `unit` or `prevUnit` argument is invalid, `getMinTimeForUnit()` will not throw an error and will just return `undefined`.
602
+ </details>
542
603
 
543
- Alternatively to `formatAs`, a step may specify a `format()` function:
604
+ <details>
605
+ <summary>More on <code>formatAs</code></summary>
544
606
 
545
- ```js
546
- format(
547
- date: number, // The date argument, converted to a timestamp.
548
- locale: string, // The currently selected language. Example: "en".
549
- {
550
- formatAs(unit: string, value: number): string,
551
- // A function that could be used to format `value` in `unit`s.
552
- // Example: `formatAs('second', -2)`
553
- // Outputs: "2 seconds ago"
607
+ ######
554
608
 
555
- now: number, // The current date timestamp.
609
+ `formatAs` is the unit of time, the labels for which will be used to generate the output of the step. If the specified unit of time isn't supported by the language (as explained further), then such step is ignored. The units of time that're supported in all languages are: `second`, `minute`, `hour`, `day`, `week`, `month`, `quarter`, `year`. For [some languages](https://github.com/catamphetamine/javascript-time-ago/tree/master/locale-more-styles), this library defines an additional unit called `now` which can be used to output labels like `"just now"`. So if a step specifies `formatAs: "now"` and `now` unit isn't supported in a given language then such step is just skipped. If your language doesn't have a `now.json`, send a pull request (native speakers only).
610
+ </details>
556
611
 
557
- future: boolean // Is `true` if `date > now`, or if `date === now`
558
- // and `future: true` option was passed to `.format()`.
559
- }
560
- ): string?
561
- ```
612
+ <details>
613
+ <summary>More on <code>format()</code></summary>
614
+
615
+ ######
616
+
617
+ A developer might prefer to output a custom label for a given step. In that case, instead of specifying `formatAs` property, they should specify a `format()` function.
618
+
619
+ * `format()` — Returns a custom label, or `undefined` for an empty output.
620
+ * Arguments:
621
+ * `date: number` — The `date`, converted to a timestamp number. Not a time difference.
622
+ * `locale: string` — The selected language. Example: `"en"`.
623
+ * `parameters: object`
624
+ * `formatAs(unit: string, amount: number): string` — A function that can be used to get a label for an `amount` of given `unit`s of time.
625
+ * Example: `formatAs('second', -2)` returns `"2 seconds ago"`.
626
+ * `now: number` — The current date's timestamp. Use it instead of `Date.now()` to avoid the issue of `Date.now()` being slightly different for each different step.
627
+ * `future: boolean` — Is `true` either when `date > now` or when `date === now` and `future: true` parameter was passed to `.format()`.
628
+ </details>
562
629
 
563
630
  <!--
564
631
  ##### Built-in `steps`
@@ -574,27 +641,6 @@ import { round } from 'javascript-time-ago/steps'
574
641
 
575
642
  <!-- * [`"approximate"`](https://github.com/catamphetamine/javascript-time-ago/blob/master/source/steps/approximate.js) — (legacy) The `steps` used in the [`"approximate"`](https://github.com/catamphetamine/javascript-time-ago/blob/master/source/style/approximate.js) style. -->
576
643
 
577
- ##### Time unit constants
578
-
579
- <!-- The `/steps` export provides a few utility time unit constants. --><!-- and functions. -->
580
-
581
- `/steps` export provides some utility time unit constants that could be used to calculate `minTime` values when defining custom `steps`:
582
-
583
- ```js
584
- import { minute, hour, day, week, month, year } from 'javascript-time-ago/steps'
585
-
586
- // In seconds
587
- minute === 60
588
- hour === 60 * 60
589
- day === 24 * 60 * 60
590
- ...
591
-
592
- const customSteps = [{
593
- minTime: 5 * minute,
594
- ...
595
- }]
596
- ```
597
-
598
644
  <!--
599
645
  The `/steps` export also provides a utility function:
600
646
 
@@ -629,123 +675,145 @@ getDate(1500000000000) === getDate(new Date('2017-07-14'))
629
675
  <!--
630
676
  ### Units
631
677
 
632
- A list of allowed time interval measurement units. Example: `["second", "minute", "hour", ...]`. By default, all available units are defined. This property can be used to filter out some of the non-conventional time units like `"quarter"` which is present in [CLDR](http://cldr.unicode.org/) data. -->
633
-
634
- ### Rounding
635
-
636
- Controls the rounding of an amount of time units.
637
-
638
- The default `round` is `"round"` which equals to `Math.round()`.
639
-
640
- * `0.1` sec. ago → `"0 sec. ago"`
641
- * `0.4` sec. ago → `"0 sec. ago"`
642
- * `0.5` sec. ago → `"1 sec. ago"`
643
- * `0.9` sec. ago → `"1 sec. ago"`
644
- * `1.0` sec. ago → `"1 sec. ago"`
645
-
646
- Some people [asked](https://github.com/catamphetamine/javascript-time-ago/issues/38#issuecomment-707094043) for an alternative rounding method, so there's also `round: "floor"` which equals to `Math.floor()`.
678
+ A list of allowed time interval measurement units. Example: `["second", "minute", "hour", ...]`. By default, all available units are defined. This property can be used to filter out some of the non-conventional units of time like `"quarter"` which is present in [CLDR](http://cldr.unicode.org/) data. -->
647
679
 
648
- * `0.1` sec. ago → `"0 sec. ago"`
649
- * `0.4` sec. ago → `"0 sec. ago"`
650
- * `0.5` sec. ago → `"0 sec. ago"`
651
- * `0.9` sec. ago → `"0 sec. ago"`
652
- * `1.0` sec. ago → `"1 sec. ago"`
653
-
654
- A developer can choose the rounding method by passing `round` option to `timeAgo.format(date, [style], options)`. The default rounding can also be set for a [style](#styles) by setting its `round` property.
680
+ ## Refreshing
655
681
 
656
- ## Future
682
+ After a time label has been rendered, it should periodically be updated (refreshed), because the label will change as the time flows. For that, the application needs to know how often it should refresh the label. It could refresh the label every second but that wouldn't be very efficient. For example, there's absolutely no need to refresh a `"1 year ago"` label every second. So how does one know how soon should the label be refreshed?
657
683
 
658
- When given future dates, `.format()` produces the corresponding output.
684
+ This library provides an easy way to know when is the best time to refresh the label:
659
685
 
660
686
  ```js
661
- timeAgo.format(Date.now() + 5 * 60 * 1000)
662
- // "in 5 minutes"
687
+ const [text, refreshDelay] = timeAgo.format(date, { getTimeToNextUpdate: true })
663
688
  ```
664
689
 
665
- 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()`.
690
+ Example:
666
691
 
667
692
  ```js
668
- // Without `future: true` option:
693
+ let timerId
669
694
 
670
- timeAgo.format(Date.now())
671
- // "just now"
695
+ function renderLoop() {
696
+ const [text, refreshDelay] = timeAgo.format(date, { getTimeToNextUpdate: true })
672
697
 
673
- timeAgo.format(Date.now() + 5 * 60 * 1000)
674
- // "in 5 minutes"
698
+ // Update the label text.
699
+ timeLabel.textContent = text
675
700
 
676
- // With `future: true` option:
701
+ if (refreshDelay !== undefined) {
702
+ timerId = setTimeout(renderLoop, refreshDelay)
703
+ }
704
+ }
677
705
 
678
- timeAgo.format(Date.now(), { future: true })
679
- // "in a moment"
706
+ // Render the label.
707
+ // It will automatically be refreshed when needed
708
+ timeLabel = createTimeLabelElement()
709
+ renderLoop()
680
710
 
681
- timeAgo.format(Date.now() + 5 * 60 * 1000, { future: true })
682
- // "in 5 minutes" (no difference)
711
+ // And when the label is no longer rendered, one should manually stop the periodic refresh.
712
+ clearTimeout(timerId)
683
713
  ```
684
714
 
685
- ## Now
686
-
687
- 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.
715
+ The code above could be simplified by passing a `refresh` function instead of `getTimeToNextUpdate: true` parameter:
688
716
 
689
717
  ```js
690
- timeAgo.format(60 * 1000, { now: 0 })
691
- // "1 minute ago"
692
- ```
693
-
694
- ## Update Interval
695
-
696
- 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.
718
+ const [text, stopRefreshing] = timeAgo.format(date, {
719
+ // It will automatically call the `refresh()` function every time the label needs to be refreshed.
720
+ refresh: (text) => {
721
+ // Update the label text.
722
+ timeLabel.textContent = text
723
+ }
724
+ })
697
725
 
698
- 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`.
726
+ // Render the label.
727
+ // It will automatically be refreshed when needed.
728
+ timeLabel = createTimeLabelElement()
729
+ timeLabel.textContent = text
699
730
 
700
- ```js
701
- getTimeToNextUpdate(
702
- date: number, // The date argument, converted to a timestamp.
703
- {
704
- getTimeToNextUpdateForUnit(unit: string): number?,
705
- // Returns "time to next update" for a time unit.
706
- // This is what the library calls internally
707
- // when `formatAs` is configured for a `step`.
708
- // Example: `getTimeToNextUpdateForUnit('minute')`.
709
- // Can return `undefined` in edge cases:
710
- // for example, when `unit` is "now".
711
-
712
- now: number, // The current date timestamp.
713
-
714
- future: boolean // Is `true` if `date > now`, or if `date === now`
715
- // and `future: true` option was passed to `.format()`.
716
- }
717
- ): number?
731
+ // And when the label is no longer rendered, one should manually stop the periodic refresh.
732
+ stopRefreshing()
718
733
  ```
719
734
 
720
- The application can then pass `getTimeToNextUpdate: true` option to `.format()` to get the best time to update the relative date label.
735
+ If you're only using the built-in "styles", the returned `timeToNextUpdate` number is always defined. If you're using a custom "style", it is possible that it doesn't support `timeToNextUpdate` feature "out of the box" and in that case the returned `timeToNextUpdate` could be `undefined`. So as a general rule, when using a custom "style", always check that `timeToNextUpdate` is not `undefined`, and if it is, then assign some default refresh interval to it like one minute (`60 * 1000`).
736
+
737
+ <!--
738
+ <details>
739
+ <summary>An example of how an application could use <code>timeToNextUpdate</code> to refresh the relative time label.</summary>
721
740
 
722
741
  ```js
723
742
  const timeAgo = new TimeAgo('en-US')
724
743
 
744
+ const DEFAULT_REFRESH_INTERVAL = 60 * 1000
745
+
746
+ // `setTimeout()` timer reference.
725
747
  let updateTimer
726
748
 
727
- function render() {
728
- // Format the date.
729
- const [formattedDate, timeToNextUpdate] = timeAgo.format(date, {
749
+ function renderLabel() {
750
+ // Format the date and get the time to next update.
751
+ const [text, timeToNextUpdate] = timeAgo.format(date, {
730
752
  getTimeToNextUpdate: true
731
753
  })
732
754
  // Update the label.
733
- setFormattedDate(formattedDate)
734
- // Schedule next render.
735
- // `timeToNextUpdate` may be `undefined`, so provide a sensible default.
736
- updateTimer = setTimeout(render, getSafeTimeoutInterval(timeToNextUpdate || 60 * 1000))
755
+ labelElement.innerText = text
756
+ // Schedule a re-render of the label.
757
+ // The returend `timeToNextUpdate` could potentially be `undefined` when using a custom "style",
758
+ // so a developer should provide some sensible default value.
759
+ updateTimer = setTimeoutSafe(renderLabel, timeToNextUpdate || DEFAULT_REFRESH_INTERVAL)
737
760
  }
738
761
 
739
- // `setTimeout()` has a bug where it fires immediately
740
- // when the interval is longer than about `24.85` days.
762
+ // Start a recursive re-render of the label.
763
+ renderLabel()
764
+
765
+ // `setTimeout()` function has a bug when it fires immediately
766
+ // when the delay is longer than about `24.85` days.
741
767
  // https://stackoverflow.com/questions/3468607/why-does-settimeout-break-for-large-millisecond-delay-values
742
- const SET_TIMEOUT_MAX_SAFE_INTERVAL = 2147483647
743
- function getSafeTimeoutInterval(interval) {
744
- return Math.min(interval, SET_TIMEOUT_MAX_SAFE_INTERVAL)
768
+ //
769
+ // Since `renderLabel()` function uses `setTimeout()` for recursion,
770
+ // that would mean infinite recursion.
771
+ //
772
+ // `setTimeoutSafe()` function works around that bug
773
+ // by capping the delay at the maximum allowed value.
774
+ //
775
+ function setTimeoutSafe(func, delay) {
776
+ return setTimeout(func, getSafeTimeoutDelay(delay))
777
+ }
778
+ function getSafeTimeoutDelay(delay) {
779
+ return Math.min(delay, 2147483647)
745
780
  }
746
781
  ```
782
+ </details>
783
+ -->
747
784
 
748
- Notice that `setTimeout()` has a bug where it [fires immediately](https://stackoverflow.com/questions/3468607/why-does-settimeout-break-for-large-millisecond-delay-values) when the interval is longer than about `24.85` days, so the interval should not exceed that number. Otherwise, it will result in an infinite recursion.
785
+ <!-- The `getTimeToNextUpdate: true` feature works out-of-the-box with any of the [built-in](#rounding) formatting "styles". For it to work well with a [custom](#custom-style) formatting "style", it has to satisfy certain conditions. -->
786
+
787
+ <details>
788
+ <summary>See the conditions that a custom formatting "style" has to meet in order to work well with <code>getTimeToNextUpdate: true</code> feature</summary>
789
+
790
+ * When each `step` in a given formatting "style" specifies a `minTime`, `timeToNextUpdate` can easily be calcuated by subtracting the current time difference from the `minTime` threshold of the next step.
791
+ * For steps that don't explicitly specify `minTime`, `minTime` can still be derived from `formatAs` property.
792
+ * For steps that don't specify niether `minTime` nor `formatAs` property, the calculation of `timeToNextUpdate` becomes slightly more complicated: such steps are required to specify a `getTimeToNextUpdate()` function property.
793
+ * `getTimeToNextUpdate()`
794
+ * Returns:
795
+ * `timeToNextUpdate: number?` — The time until next update. If it returns `undefined` then it's interpreted as "the current label is constant and will not change" — for example, `"Jan 1, 2000"`.
796
+ * Arguments:
797
+ * `date: number` — The `date`, converted to a timestamp number. Not a time difference.
798
+ * `parameters: object`
799
+ * `getTimeToNextUpdateForUnit(unit: string): number` — A function that returns the "time until next update" (in milliseconds) for a given `unit` of time. For example, when using the default rounding method, and the argument is `"minute"`, and the current time difference is `60 * 1000` milliseconds, then the current label is gonna be `"1 minute ago"` and the "time until next update" for that label will be `30 * 1000` milliseconds because that's the time after which `"1 minute ago"` label changes into `"2 minutes ago"`.
800
+ * The library itself uses this function when calculating the "time until next update" for steps that don't specify `minTime` but do specify `formatAs` property.
801
+ * `getTimeToNextUpdateForUnit("now")` always returns `undefined`.
802
+ * The return value of the function depends on the exact time that it was called at, i.e. when called at different times, it will produce different results. Example:
803
+ * Consider that the current time difference is `-20` seconds and the unit of time is `"minute"`.
804
+ * Initially, it outputs `"0 minutes ago"` because it rounds `20/60` to `0`.
805
+ * What will `getTimeToNextUpdateForUnit("minute")` return?
806
+ * When `round` value is `"round"` (default), it will return `10 * 1000` milliseconds, because `20 + 10` = `30`, and `30` seconds of time difference is the moment when it rounds `0.5 minute` to `1 minute` and changes the label from `"0 minutes ago"` to `"1 minute ago"`.
807
+ * When `round` value is `"floor"`, it will return `40 * 1000` milliseconds, because `20 + 40` = `60`, and `60` seconds of time difference is the moment when it rounds `1.0 minute` to `1 minute` and changes the label from `"0 minutes ago"` to `"1 minute ago"`.
808
+ * Now imagine that `20` seconds have passed. What will `getTimeToNextUpdateForUnit("minute")` return now?
809
+ * When `round` value is `"round"` (default), it will return `50 * 1000` milliseconds, because `20 + 20 + 50` = `90`, and `90` seconds of time difference is the moment when it rounds `1.5 minute` to `2 minutes` and changes the label from `"1 minute ago"` to `"2 minutes ago"`.
810
+ * When `round` value is `"floor"`, it will return `20 * 1000` milliseconds, because `20 + 20 + 20` = `60`, and `60` seconds of time difference is the moment when it rounds `1.0 minute` to `1 minute` and changes the label from `"0 minutes ago"` to `"1 minute ago"`.
811
+ * As the time goes by, `getTimeToNextUpdateForUnit("minute")` will keep returning a different result every other time it is called.
812
+ * `now: number` — The current date's timestamp. Use it instead of `Date.now()` to avoid the issue of `Date.now()` being slightly different for each different step.
813
+ * `future: boolean` — Is `true` either when `date > now` or when `date === now` and `future: true` parameter was passed to `.format()`.
814
+ <!-- * `round: string` — The rounding method that is used to convert a fractional time difference number to an integer. Either `"round"` or `"floor"`. Example: `"0.75 minutes ago"` → `"1 minute ago"` in case of `round: "round"`. -->
815
+ <!-- * `getRoundFunction(round: string): (number) => number` — Returns the rounding function that corresponds to the `round: string` parameter above. For example, for `round: "floor"` it returns `Math.floor` function, and for `round: "round"` (or anything else) it returns `Math.round` function. A developer could use it to convert a fractional time difference number to an integer: `getRoundFunction(round)(number)`. -->
816
+ </details>
749
817
 
750
818
  <!--
751
819
  ## Caching
@@ -760,71 +828,111 @@ const object = cache.get('key1', 'key2', ...) || cache.put('key1', 'key2', ...,
760
828
  ```
761
829
  -->
762
830
 
763
- ## Localization internals
831
+ ## Rounding
832
+
833
+ When printing a label such as `"1 minute ago"`, the time difference number has to be rounded for readability. For example, it won't print `"0.49 minutes ago"`. It has to round that number first.
764
834
 
765
- This library uses an [`Intl.RelativeTimeFormat`](https://www.npmjs.com/package/relative-time-format) polyfill under the hood. The polyfill is only [`3.5 kB`](https://github.com/tc39/proposal-intl-relative-time#polyfills) in size so it won't affect the total bundle size.
835
+ The `round` setting controls how exactly fractional numbers should be rounded to integers.
836
+
837
+ The default `round` setting is `"round"` which follows `Math.round()` behavior.
838
+
839
+ * `0.1` sec. ago → `"0 sec. ago"`
840
+ * `0.4` sec. ago → `"0 sec. ago"`
841
+ * `0.5` sec. ago → `"1 sec. ago"`
842
+ * `0.9` sec. ago → `"1 sec. ago"`
843
+ * `1.0` sec. ago → `"1 sec. ago"`
766
844
 
767
- Some people have
768
- [requested](https://github.com/catamphetamine/javascript-time-ago/issues/21) the ability to use native [`Intl.RelativeTimeFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat) and [`Intl.PluralRules`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules) instead of the polyfills: in this case, pass `polyfill: false` option when creating a `TimeAgo` instance.
845
+ Some people [asked](https://github.com/catamphetamine/javascript-time-ago/issues/38#issuecomment-707094043) for an alternative rounding method, so there's an alternative `round` setting value `"floor"` which follows `Math.floor()` behavior.
846
+
847
+ * `0.1` sec. ago → `"0 sec. ago"`
848
+ * `0.4` sec. ago → `"0 sec. ago"`
849
+ * `0.5` sec. ago → `"0 sec. ago"`
850
+ * `0.9` sec. ago → `"0 sec. ago"`
851
+ * `1.0` sec. ago → `"1 sec. ago"`
852
+
853
+ A developer can specify the preferred rounding by passing a `round` parameter to `timeAgo.format(date, [style,] options)`.
854
+
855
+ The default rounding method could also be specified "globally" for a given [style](#formatting-styles) by specifying a `round` property in the style object.
856
+
857
+ ## Past vs Future
858
+
859
+ When given a past date, `.format()` returns an `"... ago"` label.
860
+
861
+ When given a future date, `.format()` returns an `"in ..."` label.
769
862
 
770
863
  ```js
771
- new TimeAgo('en-US', { polyfill: false })
864
+ timeAgo.format(Date.now() + 5 * 60 * 1000)
865
+ // "in 5 minutes"
772
866
  ```
773
867
 
774
- ## React
868
+ When given a "now" date, which is neither past, nor future, `.format()` doesn't really know how it should represent the time difference — as `-0` or as `+0`.
775
869
 
776
- For React users, there's a [React version](https://www.npmjs.com/package/react-time-ago) [See Demo](https://catamphetamine.gitlab.io/react-time-ago/).
870
+ By default, it treats it as `-0`, meaning that it will return `"0 seconds ago"` rather than `"in 0 seconds"`.
777
871
 
778
- ## CDN
872
+ To instruct `.format()` to treat it as `+0`, pass `future: true` parameter.
779
873
 
780
- One can use any npm CDN service, e.g. [unpkg.com](https://unpkg.com) or [jsdelivr.com](https://jsdelivr.com)
874
+ ```js
875
+ // Without `future: true`
876
+ timeAgo.format(Date.now())
877
+ // "just now"
781
878
 
782
- ```html
783
- <!-- Example `[version]`: `2.x` -->
784
- <script src="https://unpkg.com/javascript-time-ago@[version]/bundle/javascript-time-ago.js"></script>
879
+ // With `future: true`
880
+ timeAgo.format(Date.now(), { future: true })
881
+ // "in a moment"
882
+ ```
785
883
 
786
- <script>
787
- TimeAgo.addDefaultLocale({
788
- locale: 'en',
789
- now: {
790
- now: {
791
- current: "now",
792
- future: "in a moment",
793
- past: "just now"
794
- }
795
- },
796
- long: {
797
- year: {
798
- past: {
799
- one: "{0} year ago",
800
- other: "{0} years ago"
801
- },
802
- future: {
803
- one: "in {0} year",
804
- other: "in {0} years"
805
- }
806
- },
807
- ...
808
- }
809
- })
810
- </script>
884
+ ## `now` parameter
811
885
 
812
- <script>
813
- alert(new TimeAgo('en-US').format(new Date()))
814
- </script>
886
+ The `.format()` function accepts an optional `now: number` parameter. It is used in tests to specify the exact "base" timestamp relative to which the time difference will be calculated.
887
+
888
+ ```js
889
+ timeAgo.format(60 * 1000, { now: 0 })
890
+ // "1 minute ago"
891
+ ```
892
+
893
+ ## Full Date Formatter
894
+
895
+ Usually, when rendering a "relative" time label, one should also provide an "absolute" time label, perhaps somewhere in a tooltip, for the user to be able to know the precise date/time of the event.
896
+
897
+ This library exports a "helper" date formatter for just that:
898
+
899
+ ```js
900
+ import FullDateFormatter from 'javascript-time-ago/full-date-formatter'
901
+
902
+ const formatter = new FullDateFormatter('en')
903
+ const tooltipText = formatter.format(date)
904
+ // Example output: "Saturday, January 1, 2000 at 3:00:00 AM"
905
+ ```
906
+
907
+ `FullDateFormatter` uses [`Intl.DateTimeFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat) under the hood, which works in all modern browsers, and falls back to `Date.toString()` for ancient web browsers like Internet Explorer.
908
+
909
+ ## React
910
+
911
+ For React users, there's a [React version](https://www.npmjs.com/package/react-time-ago) of this library. See [demo](https://catamphetamine.gitlab.io/react-time-ago/).
912
+
913
+ ## `Intl.RelativeTimeFormat` Polyfill
914
+
915
+ This library uses an [`Intl.RelativeTimeFormat`](https://www.npmjs.com/package/relative-time-format) polyfill under the hood. That polyfill is only [`3.5 kB`](https://github.com/tc39/proposal-intl-relative-time#polyfills) in size so it won't affect the total bundle size.
916
+
917
+ Still, some people have
918
+ [requested](https://github.com/catamphetamine/javascript-time-ago/issues/21) the ability to use native [`Intl.RelativeTimeFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat) and [`Intl.PluralRules`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules) instead of the polyfills. To do that, pass `polyfill: false` option when creating a `TimeAgo` instance.
919
+
920
+ ```js
921
+ new TimeAgo('en-US', { polyfill: false })
815
922
  ```
816
923
 
817
- ## Intl
924
+ <!--
925
+ ## `Intl` Polyfill
818
926
 
819
- (this is an "advanced" section)
927
+ (this is an "advanced details" section and you should probably skip it)
820
928
 
821
- [`Intl`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/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.
929
+ [`Intl`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) global object is not required by this library, but some "special" formatting styles may still use it. For example, `"twitter"` style uses `Intl.DateTimeFormat` to format long intervals as absolute dates for example, `Jan 1` or `Jan 1, 2000` — and if `Intl` is not available, it falls back to formatting those intervals as `1d`, `1mo`, `1yr`.
822
930
 
823
- `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](http://caniuse.com/#search=intl) (which can be solved using [`Intl` polyfill](https://github.com/andyearnshaw/Intl.js)).
931
+ `Intl` is present in all modern web browsers and is only absent from some of the ancient ones: [Internet Explorer 10, Safari 9 and iOS Safari 9.x](http://caniuse.com/#search=intl). If your application must support those ancient browsers, consider using an [`Intl` polyfill](https://www.npmjs.com/package/intl).
824
932
 
825
- 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](https://github.com/andyearnshaw/Intl.js).
933
+ Node.js, starting from version `0.12`, has `Intl` built-in, but it only includes English locale data by default. If your app needs to support more languages than just English on server side (for example, if it employs Server-Side Rendering) then you'll need to use an [`Intl` polyfill](https://www.npmjs.com/package/intl).
826
934
 
827
- An example of applying [`Intl` polyfill](https://github.com/andyearnshaw/Intl.js):
935
+ An example of using an [`Intl` polyfill](https://www.npmjs.com/package/intl):
828
936
 
829
937
  ```
830
938
  npm install intl@1.2.4 --save
@@ -846,7 +954,7 @@ if (typeof Intl === 'object') {
846
954
  }
847
955
  ```
848
956
 
849
- Web browser: only download `intl` package if the web browser doesn't support it, and only download the required locale.
957
+ Web browsers (only downloads `intl` package if the web browser doesn't support it, and only downloads the required locales).
850
958
 
851
959
  ```js
852
960
  async function initIntl() {
@@ -863,10 +971,7 @@ async function initIntl() {
863
971
 
864
972
  initIntl().then(...)
865
973
  ```
866
-
867
- ## TypeScript
868
-
869
- This library comes with TypeScript "typings". If you happen to find any bugs in those, create an issue.
974
+ -->
870
975
 
871
976
  <!--
872
977
  ## Contributing
@@ -906,6 +1011,45 @@ npm install [module name with version].tar.gz
906
1011
  ```
907
1012
  -->
908
1013
 
1014
+ ## CDN
1015
+
1016
+ To include this library directly via a `<script/>` tag on a page, one can use any npm CDN service, e.g. [unpkg.com](https://unpkg.com) or [jsdelivr.com](https://jsdelivr.com)
1017
+
1018
+ ```html
1019
+ <!-- Example `[version]`: `2.x` -->
1020
+ <script src="https://unpkg.com/javascript-time-ago@[version]/bundle/javascript-time-ago.js"></script>
1021
+
1022
+ <script>
1023
+ TimeAgo.addDefaultLocale({
1024
+ locale: 'en',
1025
+ now: {
1026
+ now: {
1027
+ current: "now",
1028
+ future: "in a moment",
1029
+ past: "just now"
1030
+ }
1031
+ },
1032
+ long: {
1033
+ year: {
1034
+ past: {
1035
+ one: "{0} year ago",
1036
+ other: "{0} years ago"
1037
+ },
1038
+ future: {
1039
+ one: "in {0} year",
1040
+ other: "in {0} years"
1041
+ }
1042
+ },
1043
+ ...
1044
+ }
1045
+ })
1046
+ </script>
1047
+
1048
+ <script>
1049
+ alert(new TimeAgo('en-US').format(new Date()))
1050
+ </script>
1051
+ ```
1052
+
909
1053
  ## Tests
910
1054
 
911
1055
  This component comes with a 100% code coverage.
@@ -924,7 +1068,7 @@ npm run test-coverage
924
1068
 
925
1069
  The code coverage report can be viewed by opening `./coverage/lcov-report/index.html`.
926
1070
 
927
- The `handlebars@4.5.3` [work](https://github.com/handlebars-lang/handlebars.js/issues/1646#issuecomment-578306544)[around](https://github.com/facebook/jest/issues/9396#issuecomment-573328488) in `devDependencies` is for the test coverage to not produce empty reports:
1071
+ Previously, when generating a code coverage report, it used to print a cryptic error message in the console and then produce an empty code coverage report:
928
1072
 
929
1073
  ```
930
1074
  Handlebars: Access has been denied to resolve the property "statements" because it is not an "own property" of its parent.
@@ -932,6 +1076,8 @@ You can add a runtime option to disable the check or this warning:
932
1076
  See https://handlebarsjs.com/api-reference/runtime-options.html#options-to-control-prototype-access for details
933
1077
  ```
934
1078
 
1079
+ That has been [worked](https://github.com/handlebars-lang/handlebars.js/issues/1646#issuecomment-578306544) [around](https://github.com/facebook/jest/issues/9396#issuecomment-573328488) by "anchoring" `handlebars` version in `devDependencies` at `handlebars@4.5.3`.
1080
+
935
1081
  ## GitHub
936
1082
 
937
1083
  On March 9th, 2020, GitHub, Inc. silently [banned](https://medium.com/@catamphetamine/how-github-blocked-me-and-all-my-libraries-c32c61f061d3) my account (erasing all my repos, issues and comments, even in my employer's private repos) without any notice or explanation. Because of that, all source codes had to be promptly moved to GitLab. The [GitHub repo](https://github.com/catamphetamine/javascript-time-ago) is now only used as a backup (you can star the repo there too), and the primary repo is now the [GitLab one](https://gitlab.com/catamphetamine/javascript-time-ago). Issues can be reported in any repo.