├── .babelrc
├── .gitattributes
├── .gitignore
├── .npmignore
├── .nycrc
├── .travis.yml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── LICENSE
├── MIGRATION.md
├── README.md
├── bin
├── create-commonjs-package-json.js
├── generate-load-all-locales.js
└── generate-locale-messages.js
├── cache
├── index.cjs
├── index.cjs.js
├── index.js
└── package.json
├── gradation
├── README.md
├── index.cjs
├── index.cjs.js
├── index.js
└── package.json
├── index.cjs
├── index.cjs.js
├── index.d.ts
├── index.js
├── load-all-locales
├── index.cjs
├── index.cjs.js
├── index.js
└── package.json
├── locale-more-styles
├── README.md
├── ckb
│ ├── long-time.json
│ ├── mini.json
│ ├── now.json
│ └── short-time.json
├── da
│ ├── long-time.json
│ ├── mini.json
│ └── now.json
├── de
│ ├── long-time.json
│ ├── mini.json
│ └── now.json
├── el
│ ├── mini.json
│ └── now.json
├── en
│ ├── long-time.json
│ ├── mini.json
│ ├── now.json
│ └── short-time.json
├── eo
│ ├── long-time.json
│ ├── mini.json
│ ├── now.json
│ └── short-time.json
├── es
│ ├── long-time.json
│ ├── mini.json
│ └── now.json
├── fr
│ ├── long-time.json
│ ├── mini.json
│ └── now.json
├── hi
│ ├── mini.json
│ └── now.json
├── id
│ ├── long-time.json
│ ├── mini.json
│ ├── now.json
│ └── short-time.json
├── it
│ ├── long-time.json
│ ├── mini.json
│ └── now.json
├── ko
│ ├── mini.json
│ └── now.json
├── nl
│ ├── long-time.json
│ ├── mini.json
│ └── now.json
├── pl
│ ├── long-time.json
│ ├── mini.json
│ ├── now.json
│ └── short-time.json
├── pt
│ ├── long-time.json
│ ├── mini.json
│ └── now.json
├── ro
│ ├── long-time.json
│ ├── mini.json
│ └── now.json
├── ru
│ ├── long-time.json
│ ├── mini.json
│ ├── now.json
│ └── short-time.json
├── sv
│ ├── long-time.json
│ ├── mini.json
│ └── now.json
└── zh
│ ├── mini.json
│ └── now.json
├── package-lock.json
├── package.json
├── project.sublime-project
├── prop-types
├── index.cjs
├── index.cjs.js
├── index.js
└── package.json
├── rollup.config.mjs
├── source
├── LocaleDataStore.js
├── PropTypes.js
├── TimeAgo.js
├── TimeAgo.test.js
├── cache.js
├── cache.test.js
├── isStyleObject.js
├── isStyleObject.test.js
├── locale.js
├── locale.test.js
├── round.js
├── steps
│ ├── approximate.js
│ ├── approximate.test.js
│ ├── getStep.js
│ ├── getStep.test.js
│ ├── getStepDenominator.js
│ ├── getStepDenominator.test.js
│ ├── getStepMinTime.js
│ ├── getStepMinTime.test.js
│ ├── getTimeToNextUpdate.js
│ ├── getTimeToNextUpdate.test.js
│ ├── getTimeToNextUpdateForUnit.js
│ ├── getTimeToNextUpdateForUnit.test.js
│ ├── helpers.js
│ ├── helpers.test.js
│ ├── index.js
│ ├── renameLegacyProperties.js
│ ├── renameLegacyProperties.test.js
│ ├── round.js
│ ├── round.test.js
│ └── units.js
└── style
│ ├── approximate.js
│ ├── approximateTime.js
│ ├── approximateTime.test.js
│ ├── getStyleByName.js
│ ├── mini.js
│ ├── mini.test.js
│ ├── miniMinute.js
│ ├── miniMinute.test.js
│ ├── miniMinuteNow.js
│ ├── miniMinuteNow.test.js
│ ├── miniNow.js
│ ├── miniNow.test.js
│ ├── renameLegacyProperties.js
│ ├── renameLegacyProperties.test.js
│ ├── round.js
│ ├── round.test.js
│ ├── roundMinute.js
│ ├── roundMinute.test.js
│ ├── twitter.js
│ ├── twitter.test.js
│ ├── twitterFirstMinute.js
│ ├── twitterFirstMinute.test.js
│ ├── twitterMinute.js
│ ├── twitterMinute.test.js
│ ├── twitterMinuteNow.js
│ ├── twitterMinuteNow.test.js
│ ├── twitterNow.js
│ └── twitterNow.test.js
├── steps
├── index.cjs
├── index.cjs.js
├── index.js
└── package.json
└── test
├── TimeAgo.js
├── addLabels.test.js
├── exports.cache.test.js
├── exports.gradation.test.js
├── exports.prop-types.test.js
├── exports.steps.test.js
├── exports.test.js
├── locale
└── de.test.js
├── setup.js
└── setupLocales.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-env"
4 | ],
5 |
6 | "plugins": [
7 | ["@babel/plugin-transform-for-of", { loose: true }],
8 | "@babel/plugin-syntax-import-assertions"
9 | ],
10 |
11 | "env": {
12 | "es6": {
13 | "presets": [
14 | ["@babel/preset-env", { modules: false }]
15 | ]
16 | },
17 | "test": {
18 | "plugins": ["istanbul"]
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 |
7 | # Standard to msysgit
8 | *.doc diff=astextplain
9 | *.DOC diff=astextplain
10 | *.docx diff=astextplain
11 | *.DOCX diff=astextplain
12 | *.dot diff=astextplain
13 | *.DOT diff=astextplain
14 | *.pdf diff=astextplain
15 | *.PDF diff=astextplain
16 | *.rtf diff=astextplain
17 | *.RTF diff=astextplain
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # testing package
2 | /javascript-isomorphic-render-*.tgz
3 |
4 | # test coverage folder
5 | /coverage/
6 | /.nyc_output/
7 |
8 | # npm modules
9 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
10 | /node_modules/
11 |
12 | # npm errors
13 | npm-debug.log
14 |
15 | # github pages
16 | /gh-pages/
17 |
18 | # for OS X users
19 | .DS_Store
20 |
21 | # cache files for sublime text
22 | *.tmlanguage.cache
23 | *.tmPreferences.cache
24 | *.stTheme.cache
25 |
26 | # workspace files are user-specific
27 | *.sublime-workspace
28 |
29 | # webpack build target folder
30 | /modules/
31 | /commonjs/
32 |
33 | # NUL
34 | NUL
35 |
36 | # browser builds
37 | /bundle/
38 |
39 | # locale data files
40 | /locale/
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # git
2 | .gitignore
3 | .gitattributes
4 |
5 | # Babel
6 | .babelrc
7 |
8 | # Sources aren't needed for npm
9 | /source/
10 |
11 | # testing package
12 | /javascript-isomorphic-render-*.tgz
13 |
14 | # Travis CI
15 | .travis.yml
16 |
17 | # test coverage folder
18 | /coverage/
19 | /.nyc_output/
20 |
21 | # npm errors
22 | npm-debug.log
23 |
24 | # github pages
25 | /gh-pages/
26 |
27 | # for OS X users
28 | .DS_Store
29 |
30 | # cache files for sublime text
31 | *.tmlanguage.cache
32 | *.tmPreferences.cache
33 | *.stTheme.cache
34 |
35 | # workspace files are user-specific
36 | *.sublime-workspace
37 | *.sublime-project
38 |
39 | # webpack is used in development
40 | /webpack.config.babel.js
41 |
42 | # tests aren't needed for npm
43 | /test/
--------------------------------------------------------------------------------
/.nycrc:
--------------------------------------------------------------------------------
1 | {
2 | "require": [
3 | "@babel/register"
4 | ],
5 | "reporter": [
6 | "lcov",
7 | "text-summary"
8 | ],
9 | "include": [
10 | "source/**/*.js"
11 | ],
12 | "exclude": [
13 | "source/PropTypes.js",
14 | "**/*.test.js"
15 | ],
16 | "sourceMap": false,
17 | "instrument": false,
18 | "cache": true,
19 | "all": true
20 | }
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "stable"
4 | sudo: false
5 | script:
6 | - "npm run test-coverage"
7 | after_success:
8 | - "npm install coveralls && npm run coveralls"
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
35 |
36 | 2.4.0 / 19.05.2022
37 | ==================
38 |
39 | * Moved the package to use "ES Modules" exports.
40 |
41 | 2.3.13 / 11.02.2022
42 | ==================
43 |
44 | [Fixed](https://github.com/catamphetamine/javascript-time-ago/issues/60) the CDN bundle `javascript-time-ago.js` when it exported the `TimeAgo` class as a `default` property instead of exporting the `TimeAgo` class itself.
45 |
46 | 2.3.6 / 25.05.2021
47 | ==================
48 |
49 | * [Added](https://github.com/catamphetamine/javascript-time-ago/pull/47) `mini` style (aka `twitter` style) for some locales: `da`, `sv`, `nl`, `it`, `fr`, `es`. By [@trustpilot](https://github.com/trustpilot).
50 |
51 | 2.3.5 / 12.05.2021
52 | ==================
53 |
54 | * Added [additional `pt` locale styles](https://github.com/catamphetamine/javascript-time-ago/pull/45) by [Victor Biasibetti](https://github.com/victorbiasibetti).
55 |
56 | 2.3.3 / 11.11.2020
57 | ==================
58 |
59 | * Changed the default `style` from `"approximate"` (legacy) to `"round-minute"`. This isn't a "breaking change" because no application would be "broken" by something like that, and relative time would still be shown in a similar way, only without too much approximation.
60 |
61 | 2.3.1 / 20.10.2020
62 | ==================
63 |
64 | * (advanced) Renamed `getMinTimeToFrom()` to `getMinTimeForUnit()`.
65 |
66 | 2.3.0 / 14.10.2020
67 | ==================
68 |
69 | * `test(timestamp)` function of a step is now deprecated. Use `minTime(timestamp)` function instead.
70 |
71 | * Fixed `getTimeToNextUpdate()`.
72 |
73 | * Renamed `"mini-time"` labels to `"mini"`.
74 |
75 | * Added styles: `"mini"`, `"mini-now"`, `"mini-minute"`, `"mini-minute-now"`.
76 |
77 | * Added a new `round` property (described in the readme): it can be `"round"` or `"floor"`. The default is `"round"`.
78 |
79 | * (Could be a breaking change for those who read `"tiny"` from JSON files directly) Removed "tiny" labels from JSON files. `"tiny"` labels type name still works.
80 |
81 | 2.2.9 / 14.10.2020
82 | ==================
83 |
84 | * Fixed `"twitter-..."` styles.
85 |
86 | 2.2.8 / 12.10.2020
87 | ==================
88 |
89 | * Added `"twitter-minute-now"` style.
90 |
91 | * The threshold for `"now"` -> `"1m"`/`"1 minute ago"` is now 30 seconds rather than 40 seconds.
92 |
93 | 2.2.6 / 12.10.2020
94 | ==================
95 |
96 | * Changed `"twitter"` style: doesn't output `"now"` for 0 seconds.
97 |
98 | * Added `"twitter-now"` style that outputs `"now"` for 0 seconds.
99 |
100 | 2.2.5 / 11.10.2020
101 | ==================
102 |
103 | * Added `addDefaultLocale()` static function (similar to `addLocale()` but also calls `setDefaultLocale()`).
104 |
105 | * `"twitter"` style used to output `"now"` for 0 seconds. Then it was changed to `"0s"`. Now it has been [changed](https://github.com/catamphetamine/javascript-time-ago/issues/38) to `"now"` again.
106 |
107 | * Added "CDN" section in the readme that documents using the library with `` tags (without a bundler).
108 |
109 | 2.2.0 / 09.10.2020
110 | ==================
111 |
112 | * Renamed steps' `unit` to `formatAs`. The older name still works. Maybe it will be renamed to something else in some future.
113 |
114 | * Renamed steps' `threshold` to `minTime`. The older name still works but is considered deprecated.
115 |
116 | * Renamed steps' `threshold_for_idOrUnit: value` to `minTime: { id: value }`. The older way still works but is considered deprecated. Maybe `minTime: {}` object will be deprecated too in some future.
117 |
118 | * Added `test(date, { now, future })` function to steps: it can be an alternative to `minTime`. See "twitter" style for an example.
119 |
120 | * Added a third argument to steps' `format()` function: an object having shape `{ formatAs(unit, value): string, future: boolean }`.
121 |
122 | * Added `TimeAgo.addLabels(locale, name, labels)` function, that can be used to expand localized time labels.
123 |
124 | * Added `"twitter-first-minute"` style: same as `"twitter"` but doesn't output anything before the first minute. This is how `"twitter"` style worked initially.
125 |
126 | * Added `getTimeToNextUpdate` feature (see README).
127 |
128 | * Updated `relative-time-format` to the latest version: `0.1.x` -> `1.0.0`.
129 |
130 | * Locale files are now `*.json` files. There's no `quantify` function there now: now it's just labels. `{locale}/index.js` files are still there just for legacy compatibility.
131 |
132 | * Added 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.
133 |
134 | * The bundle is now generated by Rollup rather than Webpack.
135 |
136 | 2.1.5 / 07.10.2020
137 | ==================
138 |
139 | * Custom styles: renamed `gradation` to `steps` and `flavour` to `labels`. The older names still work but are considered deprecated.
140 |
141 | * `factor` property of a "step" is now not required: if not present, it's assumed equal to the `unit` in seconds (for example, the default `factor` is `60 * 60` for `unit: "hour"`).
142 |
143 | 2.1.4 / 06.10.2020
144 | ==================
145 |
146 | * Renamed `"default"` style to `"round"`. The older name still works but is considered deprecated.
147 |
148 | * Added `"round-minute"` style: same as `"round"` but without seconds.
149 |
150 | * Renamed `"time"` style to `"approximate-time"`. The older name still works but is considered deprecated.
151 |
152 | * Renamed `"canonical"` gradation to `"round"`. The older name still works but is considered deprecated.
153 |
154 | * Renamed `"tiny"` time labels style to `"mini-time"`. `"tiny"` time labels style still works but is considered deprecated.
155 |
156 | * (internals) Renamed `tiny.json` locale files to `mini-time.json`. Removed `now` unit from `mini-time.json`.
157 |
158 | * (internals) `"approximate"` (previously `"convenient"`) style now uses `long` labels instead of `long-convenient.json`.
159 |
160 | * (internals) Removed `now` unit from `mini-time.json` files.
161 |
162 | * (internals) Removed `long-convenient.json` and `short-convenient.json` files: `long.json` and `short.json` in combination with `now.json` are used instead.
163 |
164 | 2.1.0 / 05.10.2020
165 | ==================
166 |
167 | * (could be considered a breaking change, but it doesn't actually break any apps) `"twitter"` style now outputs something like `"1s"` in case of `"1 second ago"`. Previously it didn't output anything when the time difference was less than a minute. The rationale for the change is that Twitter actually does output seconds when the time difference is less than a minute. There's still a small difference from Twitter: Twitter outputs `"now"` in case of `"0 seconds ago"` while this library outputs `"0s"` — the rationale is that "now" could be too long is different languages, and also it would look too contrasty compared to its "sibling" `"Xs"` time labels.
168 |
169 | * Added `"default"` style (`long` time labels + `canonical` gradation). Update: it's now called `"round"` instead of `"default"`.
170 |
171 | * Added `future` option on `.format(value, style, options)` function: it determines, whether to use the `"future"` variant of `"now"` when formatting `0` time difference. By default, it uses the `"past"` variant of `"now"` when formatting `0` time difference: `"just now"` instead of `"in a moment"`.
172 |
173 | * (miscellaneous) Added dedicated `"now.json"` labels for `"now"` time unit.
174 |
175 | 2.0.10 / 16.07.2020
176 | ==================
177 |
178 | * Added `"tiny"` time labels for `"de"` locale.
179 |
180 | 2.0.0 / 14.01.2018
181 | ==================
182 |
183 | * Moved `RelativeTimeFormat` to a separate `relative-time-format` package.
184 |
185 | * (breaking change) Removed `.locale()` static function. Use `.addLocale()` instead.
186 |
187 | * (breaking change) `flavour` property renamed in non-single-word cases: underscores (`_`) got replaced with dashes (`-`). Examples: `short_convenient` -> `short-convenient`, `long_convenient` -> `long-convenient`, `short_time` -> `short-time`, `long_time` -> `long-time`. The relevant keys in locale `index.js` files got renamed the same way.
188 |
189 | * (breaking change) `RelativeTimeFormat` is no longer exported from this library.
190 |
191 | * (could be a breaking change) Re-did `/prop-types`, `/gradation`, `/cache` exports as sub-packages. This could possibly change their import behavior. Maybe `/prop-types` did change — I changed some export strategies for it.
192 |
193 | * (unlikely a breaking change) `yue-Hant` locale removed (due to its removal from CLDR).
194 |
195 | * (unlikely to be a breaking change) Removed handling for a case when "now" unit had "past"/"future" which is an object of quantifier messages instead of a string. The rationale that having "now" unit with "past"/"future" which are objects of quantifier messages wouldn't make sense because "now" is a moment and one can't differentiate between "past moment", "current moment" and "next moment" in real life.
196 |
197 | 1.0.33 / 29.11.2018
198 | ===================
199 |
200 | * Resolved cyclic dependency between `JavascriptTimeAgo.js` and `RelativeTimeFormat.js`.
201 |
202 | * `JavascriptTimeAgo.default_locale` variable no longer exists (it wasn't public or documented).
203 |
204 | 1.0.32 / 04.11.2018
205 | ===================
206 |
207 | * Added `.addLocale()` alias for `.locale()` function (better naming). The old `.locale()` function name is now deprecated and will be removed in some next major version release.
208 |
209 | * Added `RelativeTimeFormat.addLocale()` proxy function which simply calls `JavascriptTimeAgo.addLocale()`.
210 |
211 | 1.0.19 / 12.01.2018
212 | ===================
213 |
214 | * Refactored `twitter` style and styles overall: style can now have `threshold(now)` function and also gradation step can have `format(value, locale)` function instead of `unit`.
215 |
216 | 1.0.17 / 11.01.2018
217 | ===================
218 |
219 | * Renamed `override` to `custom` for styles
220 |
221 | 1.0.15 / 11.01.2018
222 | ===================
223 |
224 | * Renamed `fuzzy` style to `time`.
225 | * Refactored `gradation`s and `style`s.
226 | * `gradation` is now not being exported from `index.js` along with `day`, `month` and `year` (one can still `import` it manually from `gradation.js`).
227 | * `es6` folder got renamed to `modules`
228 | * `build` folder got renamed to `commonjs`
229 |
230 | 1.0.11 / 10.01.2018
231 | ===================
232 |
233 | * Renamed `plural` to `quantify` inside locale data.
234 | * Implemented `Intl.RelativeTimeFormat` proposal polyfill which is now being exported.
235 |
236 | 1.0.10 / 09.01.2018
237 | ===================
238 |
239 | * (can be a breaking change for custom styles) Renamed `just-now` unit to `now` and `xxx-concise` flavour to `xxx_time` (+ flavour `.json` files got renamed accordingly).
240 |
241 | 1.0.8 / 09.01.2018
242 | ===================
243 |
244 | * (breaking change) When defining a custom `style` its `override()` function takes `date` and `time` parameters: now `date` parameter of `override()` is not guaranteed to be set (can be inferred from `time`).
245 |
246 | 1.0.2 / 08.01.2018
247 | ===================
248 |
249 | * (breaking change) Due to a long-standing engineering flaw in `intl-messageformat` library (the locale data loading process) I dismissed it and this library is now using raw CLDR locale data instead so built-in locale data now holds an extra property: the `plural` function taking a number and returning the pluralization type of that number ("one", "few", etc). Therefore, if adding raw CLDR locale data for locales which are not built-in this pluralization function must be passed as the second argument to `.locale(localeDataCLDR, pluralsClassifier)`.
250 |
251 | * (breaking change) `javascriptTimeAgo.styles` is no more accesible: pass `style` as a string instead.
252 |
253 | * (breaking change) `locales` folder inside the package renamed to `locale` (e.g. `javascript-time-ago/locales/en` -> `javascript-time-ago/locale/en`).
254 |
255 | * `style.flavour` can now be an array
256 |
257 | 0.4.4 / 22.12.2016
258 | ===================
259 |
260 | * Changed `yesterday` and `tomorrow` labels for Russian localization
261 |
262 | 0.2.0 / 13.04.2016
263 | ===================
264 |
265 | * Moved `intl-messageformat` to `peerDependencies`
266 |
267 | 0.1.0 / 03.04.2016
268 | ===================
269 |
270 | * Initial release
271 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and free environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a censorship-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, gender identity and expression, level of experience,
9 | education, socio-economic status, nationality, personal appearance, race,
10 | religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating an open and free environment
15 | include:
16 |
17 | * Not constraining the language to be "welcoming" or "inclusive"
18 | * Not demanding show of empathy towards other community members
19 | * Not dictating anyone to be respectful of differing viewpoints and experiences
20 | * Not forcing anyone to change their views or opinions regardless of those
21 | * Not intimidating other people into accepting your own views or opinions
22 | * Not blackmailing other people to disclose their personal views or opinions
23 | * Not constraining other people from publishing their personal views or opinions in an unintrusive way
24 | * Focusing on what is best for the ecosystem
25 |
26 | Examples of acceptable behavior by participants include:
27 |
28 | * The use of sexualized language
29 | * Occasional trolling or insulting comments that are not completely off-topic
30 |
31 | Examples of unacceptable behavior by participants include:
32 |
33 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
34 | * Unwelcome sexual attention or advances
35 | * Public harassment or personal attacks when carried out in an bold or intrusive way
36 | * Private harassment
37 | * Any actions that are in violation of the local laws or otherwise considered illegal
38 | * Other conduct which could reasonably be considered inappropriate in an open and free setting
39 |
40 | ## Our Responsibilities
41 |
42 | Project maintainers are responsible for clarifying the standards of acceptable
43 | behavior and are free to take appropriate and fair corrective action in
44 | response to any instances of unacceptable behavior.
45 |
46 | Project maintainers have the right and authority to remove, edit, or
47 | reject comments, commits, code, wiki edits, issues, and other contributions
48 | that are not aligned to this Code of Conduct, or to ban temporarily or
49 | permanently any contributor for other behaviors that they deem inappropriate,
50 | threatening, offensive, or harmful.
51 |
52 | ## Scope
53 |
54 | This Code of Conduct applies both within project spaces and in public spaces
55 | when an individual is representing the project or its community. Examples of
56 | representing a project or community include using an official project e-mail
57 | address, posting via an official social media account, or acting as an appointed
58 | representative at an online or offline event. Representation of a project may be
59 | further defined and clarified by project maintainers.
60 |
61 | ## Enforcement
62 |
63 | Instances of unacceptable behavior may be reported by contacting the project team.
64 | The complaints will likely be reviewed and investigated and may result in a response that
65 | is deemed necessary and appropriate to the circumstances. The project team should maintain confidentiality with regard to the reporter of an incident.
66 | Further details of specific enforcement policies may be posted separately.
67 |
68 | Project maintainers who do not follow the Code of Conduct in good
69 | faith may face temporary or permanent repercussions as determined by other
70 | members of the project's leadership.
71 |
72 | ## Attribution
73 |
74 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
75 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
76 |
77 | [homepage]: https://www.contributor-covenant.org
78 |
79 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | (The MIT License)
2 |
3 | Copyright (c) 2016 @catamphetamine
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | 'Software'), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/MIGRATION.md:
--------------------------------------------------------------------------------
1 | # Migration from version `1.x`
2 |
3 | ### `.locale()` function removed.
4 |
5 | If there're any `.locale()` static function calls then replace them with `.addLocale()`.
6 |
7 | ### `flavour`s that got renamed.
8 |
9 | If any custom styles were defined replace the following `flavour`s in their definition:
10 |
11 | * `short_convenient` -> `short-convenient`
12 | * `long_convenient` -> `long-convenient`
13 | * `short_time` -> `short-time`
14 | * `long_time` -> `long-time`
15 |
16 | ### `RelativeTimeFormat` is no longer exported
17 |
18 | If `RelativeTimeFormat` was imported from `javascript-time-ago` then it must now be imported from `relative-time-format` instead.
--------------------------------------------------------------------------------
/bin/create-commonjs-package-json.js:
--------------------------------------------------------------------------------
1 | // Creates a `package.json` file in the CommonJS build folder.
2 | // That marks that whole folder as CommonJS so that Node.js doesn't complain
3 | // about `require()`-ing those files.
4 |
5 | import fs from 'fs'
6 |
7 | fs.writeFileSync('./commonjs/package.json', JSON.stringify({
8 | name: 'javascript-time-ago/commonjs',
9 | type: 'commonjs',
10 | private: true
11 | }, null, 2), 'utf8')
--------------------------------------------------------------------------------
/bin/generate-load-all-locales.js:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 | import fs from 'fs'
3 |
4 | const locales = []
5 |
6 | fs.readdirSync(path.resolve('./locale')).forEach((locale) => {
7 | const localePath = path.resolve('./locale', locale)
8 | if (fs.statSync(localePath).isDirectory()) {
9 | if (fs.existsSync(path.resolve('./locale', locale, 'package.json'))) {
10 | locales.push(locale)
11 | }
12 | }
13 | })
14 |
15 | // Write `index.js` file.
16 | fs.writeFileSync(path.resolve('./load-all-locales/index.js'),
17 | `
18 | import TimeAgo from "javascript-time-ago"
19 |
20 | ${locales.map(locale => 'import ' + getLocaleVariableName(locale) + ' from "javascript-time-ago/locale/' + locale + '.json"').join('\n')}
21 |
22 | ${locales.map(locale => 'TimeAgo.addLocale(' + getLocaleVariableName(locale) + ')').join('\n')}
23 | `
24 | .trim())
25 |
26 | // Write `index.cjs` file.
27 | fs.writeFileSync(path.resolve('./load-all-locales/index.cjs'),
28 | `
29 | var TimeAgo = require("javascript-time-ago")
30 | ${locales.map(locale => 'TimeAgo.addLocale(require("javascript-time-ago/locale/' + locale + '.json"))').join('\n')}
31 | `
32 | .trim())
33 |
34 | // Write `index.cjs.js` file.
35 | fs.writeFileSync(path.resolve('./load-all-locales/index.cjs.js'),
36 | `
37 | // This file id deprecated.
38 | // It's the same as \`index.cjs\`, just with an added \`.js\` file extension.
39 | // It only exists for compatibility with the software that doesn't like \`*.cjs\` file extension.
40 | // https://gitlab.com/catamphetamine/libphonenumber-js/-/issues/61#note_950728292
41 |
42 | var TimeAgo = require("javascript-time-ago")
43 | ${locales.map(locale => 'TimeAgo.addLocale(require("javascript-time-ago/locale/' + locale + '.json"))').join('\n')}
44 | `
45 | .trim())
46 |
47 | // ES6
48 | // `
49 | // import TimeAgo from "javascript-time-ago"
50 | //
51 | // ${locales.map(locale => 'import ' + locale + ' from "javascript-time-ago/locale/' + locale + '"').join('\n')}
52 | //
53 | // ${locales.map(locale => 'TimeAgo.addLocale(' + locale + ')').join('\n')}
54 | // `
55 |
56 | // Transforms a locale name to a javascript variable name.
57 | // Example: "zh-Hant-MO" -> "zh_Hant_MO"
58 | // Example: "en-001" -> "en_001"
59 | function getLocaleVariableName(locale) {
60 | return locale.replace(/[^a-zA-Z_\d]/g, '_')
61 | }
--------------------------------------------------------------------------------
/bin/generate-locale-messages.js:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 | import fs from 'fs-extra'
3 |
4 | const localesDirectory = path.resolve('./locale')
5 |
6 | const ADDITIONAL_STYLES = [
7 | // 'now' should come before 'mini' because `now.current`
8 | // is the default for `mini.now`.
9 | 'now',
10 | 'mini',
11 | 'short-time',
12 | 'long-time'
13 | ]
14 |
15 | const ALL_LOCALES = getAllLocales()
16 |
17 | for (const locale of ALL_LOCALES) {
18 | writeLocaleDataFile(locale)
19 | createLegacyCompatibilityLocaleFolder(locale)
20 | }
21 |
22 | addLocaleExports(ALL_LOCALES)
23 |
24 | /**
25 | * Returns a list of all locales supported by `relative-time-format`.
26 | * @return {string[]}
27 | */
28 | export function getAllLocales() {
29 | const LOCALE_FILE_NAME_REG_EXP = /([^\/]+)\.json$/
30 | return fs.readdirSync(path.join('./node_modules/relative-time-format/locale/'))
31 | .filter(_ => fs.statSync(path.join('./node_modules/relative-time-format/locale', _)).isFile() && LOCALE_FILE_NAME_REG_EXP.test(_))
32 | .map(_ => _.match(LOCALE_FILE_NAME_REG_EXP)[1])
33 | }
34 |
35 | function getNowLabel(localeData) {
36 | if (localeData.now) {
37 | if (localeData.now.now.current) {
38 | return localeData.now.now.current
39 | }
40 | }
41 | return localeData.long.second.current
42 | }
43 |
44 | function readJsonFromFile(path) {
45 | return JSON.parse(fs.readFileSync(path, 'utf8'))
46 | }
47 |
48 | function writeLocaleDataFile(locale) {
49 | // CLDR locale data: "long", "short", "narrow" labels.
50 | const baseLocaleData = readJsonFromFile('./node_modules/relative-time-format/locale/' + locale + '.json')
51 |
52 | const localeData = {
53 | ...baseLocaleData,
54 | locale
55 | }
56 |
57 | for (const style of ADDITIONAL_STYLES) {
58 | const labelsFilePath = path.join('./locale-more-styles', locale, `${style}.json`)
59 | if (fs.existsSync(labelsFilePath)) {
60 | const labels = readJsonFromFile(labelsFilePath)
61 | localeData[style] = labels
62 | if (style === 'mini') {
63 | if (!labels.now) {
64 | const nowLabel = getNowLabel(localeData)
65 | if (nowLabel) {
66 | labels.now = nowLabel
67 | }
68 | }
69 | }
70 | }
71 | }
72 |
73 | // Locales are guaranteed to have a "now" label.
74 | if (!getNowLabel(localeData)) {
75 | throw new Error(`"now" label not found for locale "${locale}"`)
76 | }
77 |
78 | // Create the locale `*.json` file.
79 | fs.outputFileSync(
80 | path.join(localesDirectory, `${locale}.json`),
81 | JSON.stringify(localeData, null, '\t')
82 | )
83 |
84 | // Create the locale `*.json.js` file.
85 | //
86 | // Stupid Node.js can't even `import` JSON files.
87 | // https://stackoverflow.com/questions/72348042/typeerror-err-unknown-file-extension-unknown-file-extension-json-for-node
88 | // Using a `*.json.js` duplicate file workaround.
89 | //
90 | fs.outputFileSync(
91 | path.join(localesDirectory, `${locale}.json.js`),
92 | 'export default ' + JSON.stringify(localeData, null, '\t')
93 | )
94 |
95 | // Create the locale `*.json.d.ts` file.
96 | fs.outputFileSync(
97 | path.join(localesDirectory, `${locale}.json.d.ts`),
98 | `
99 | import { LocaleData } from '../index.d.js';
100 |
101 | declare const localeData: LocaleData;
102 | export default localeData;
103 | `.trim()
104 | )
105 | }
106 |
107 | // (deprecated)
108 | // Creates a legacy-compatibility locale directory export.
109 | function createLegacyCompatibilityLocaleFolder(locale) {
110 | const localeDirectory = path.join(localesDirectory, locale)
111 |
112 | // // Create a legacy-compatibility `index.js` file.
113 | // fs.outputFileSync(
114 | // path.join(localeDirectory, 'index.js'),
115 | // `
116 | // export { default } from '../${locale}.json.js'
117 | // `.trim()
118 | // )
119 |
120 | // // Create a legacy-compatibility `index.cjs` file.
121 | // fs.outputFileSync(
122 | // path.join(localeDirectory, 'index.cjs'),
123 | // `
124 | // var localeData = require('../${locale}.json')
125 | // exports = module.exports = localeData
126 | // exports['default'] = localeData
127 | // `.trim()
128 | // )
129 |
130 | // // Create a legacy-compatibility `index.cjs.js` file.
131 | // // It's the same as `index.cjs`, just with an added `.js` file extension.
132 | // // It only exists for compatibility with the software that doesn't like `*.cjs` file extension.
133 | // // https://gitlab.com/catamphetamine/libphonenumber-js/-/issues/61#note_950728292
134 | // fs.outputFileSync(
135 | // path.join(localeDirectory, 'index.cjs.js'),
136 | // `
137 | // var localeData = require('../${locale}.json')
138 | // exports = module.exports = localeData
139 | // exports['default'] = localeData
140 | // `.trim()
141 | // )
142 |
143 | // Create `package.json` for the legacy-compatibility locale directory.
144 | fs.outputFileSync(
145 | path.join(localeDirectory, 'package.json'),
146 | JSON.stringify({
147 | private: true,
148 | name: `javascript-time-ago/locale/${locale}`,
149 | main: `../${locale}.json`,
150 | module: `../${locale}.json.js`,
151 | types: `../${locale}.json.d.ts`,
152 | type: 'module',
153 | exports: {
154 | '.': {
155 | types: `../${locale}.json.d.ts`,
156 | import: `../${locale}.json.js`,
157 | require: `../${locale}.json`
158 | }
159 | },
160 | sideEffects: false
161 | }, null, '\t')
162 | )
163 | }
164 |
165 | // Add `export` entries in `package.json`.
166 | function addLocaleExports(ALL_LOCALES) {
167 | // Read `package.json` file.
168 | const packageJson = readJsonFromFile('./package.json')
169 |
170 | // Remove all locale exports.
171 | for (const path of Object.keys(packageJson.exports)) {
172 | if (path.startsWith('./locale/')) {
173 | delete packageJson.exports[path]
174 | }
175 | }
176 |
177 | // Re-add all locale exports.
178 | packageJson.exports = {
179 | ...packageJson.exports,
180 | ...ALL_LOCALES.reduce((all, locale) => {
181 | all[`./locale/${locale}`] = {
182 | import: `./locale/${locale}.json.js`,
183 | require: `./locale/${locale}.json`
184 | }
185 | all[`./locale/${locale}.json`] = {
186 | import: `./locale/${locale}.json.js`,
187 | require: `./locale/${locale}.json`
188 | }
189 | return all
190 | }, {})
191 | }
192 |
193 | // Save `package.json` file.
194 | fs.writeFileSync('./package.json', JSON.stringify(packageJson, null, 2) + '\n', 'utf8')
195 | }
--------------------------------------------------------------------------------
/cache/index.cjs:
--------------------------------------------------------------------------------
1 | exports = module.exports = require('../commonjs/cache.js').default
2 | exports['default'] = require('../commonjs/cache.js').default
--------------------------------------------------------------------------------
/cache/index.cjs.js:
--------------------------------------------------------------------------------
1 | // This file is deprecated.
2 | // It's the same as `index.cjs`, just with an added `.js` file extension.
3 | // It only exists for compatibility with the software that doesn't like `*.cjs` file extension.
4 | // https://gitlab.com/catamphetamine/libphonenumber-js/-/issues/61#note_950728292
5 |
6 | exports = module.exports = require('../commonjs/cache.js').default
7 | exports['default'] = require('../commonjs/cache.js').default
--------------------------------------------------------------------------------
/cache/index.js:
--------------------------------------------------------------------------------
1 | // I guess this export is deprecated.
2 | export { default } from '../modules/cache.js'
--------------------------------------------------------------------------------
/cache/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "name": "javascript-time-ago/cache",
4 | "main": "index.cjs",
5 | "module": "index.js",
6 | "type": "module",
7 | "exports": {
8 | ".": {
9 | "import": "./index.js",
10 | "require": "./index.cjs"
11 | }
12 | },
13 | "sideEffects": false
14 | }
--------------------------------------------------------------------------------
/gradation/README.md:
--------------------------------------------------------------------------------
1 | Deprecated: `gradation` is a legacy name of `steps`. Use `/steps` subpackage instead.
--------------------------------------------------------------------------------
/gradation/index.cjs:
--------------------------------------------------------------------------------
1 | exports = module.exports = require('../commonjs/steps/index.js')
2 | exports['default'] = require('../commonjs/steps/index.js')
--------------------------------------------------------------------------------
/gradation/index.cjs.js:
--------------------------------------------------------------------------------
1 | // This file id deprecated.
2 | // It's the same as `index.cjs`, just with an added `.js` file extension.
3 | // It only exists for compatibility with the software that doesn't like `*.cjs` file extension.
4 | // https://gitlab.com/catamphetamine/libphonenumber-js/-/issues/61#note_950728292
5 |
6 | exports = module.exports = require('../commonjs/steps/index.js')
7 | exports['default'] = require('../commonjs/steps/index.js')
--------------------------------------------------------------------------------
/gradation/index.js:
--------------------------------------------------------------------------------
1 | export * from '../modules/steps/index.js'
--------------------------------------------------------------------------------
/gradation/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "name": "javascript-time-ago/gradation",
4 | "main": "index.cjs",
5 | "module": "index.js",
6 | "type": "module",
7 | "exports": {
8 | ".": {
9 | "import": "./index.js",
10 | "require": "./index.cjs"
11 | }
12 | },
13 | "sideEffects": false
14 | }
--------------------------------------------------------------------------------
/index.cjs:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | exports = module.exports = require('./commonjs/TimeAgo.js').default
4 | exports['default'] = require('./commonjs/TimeAgo.js').default
5 |
6 | var locale = require('./commonjs/locale.js')
7 |
8 | exports.intlDateTimeFormatSupported = locale.intlDateTimeFormatSupported
9 | exports.intlDateTimeFormatSupportedLocale = locale.intlDateTimeFormatSupportedLocale
--------------------------------------------------------------------------------
/index.cjs.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | // This file id deprecated.
4 | // It's the same as `index.cjs`, just with an added `.js` file extension.
5 | // It only exists for compatibility with the software that doesn't like `*.cjs` file extension.
6 | // https://gitlab.com/catamphetamine/libphonenumber-js/-/issues/61#note_950728292
7 |
8 | exports = module.exports = require('./commonjs/TimeAgo.js').default
9 | exports['default'] = require('./commonjs/TimeAgo.js').default
10 |
11 | var locale = require('./commonjs/locale.js')
12 |
13 | exports.intlDateTimeFormatSupported = locale.intlDateTimeFormatSupported
14 | exports.intlDateTimeFormatSupportedLocale = locale.intlDateTimeFormatSupportedLocale
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | export type DateInput = Date | number;
2 |
3 | export type Locale = string;
4 |
5 | // Users can add custom styles via `TimeAgo.addLabels(locale, styleName, labels)`.
6 | export type CustomLabelStyleName = string;
7 | // There're also "legacy" label styles like "time" or "long-time" that have been deprecated.
8 | // Users can still use those by adding them manually via `TimeAgo.addLabels()`.
9 | export type LabelStyleName = 'long' | 'short' | 'narrow' | 'mini' | 'now' | CustomLabelStyleName;
10 |
11 | export type Rounding = 'round' | 'floor';
12 |
13 | // https://github.com/eemeli/make-plural/blob/master/packages/compiler/src/compile-range.js#L1
14 | export type Count = 'zero' | 'one' | 'two' | 'few' | 'many' | 'other';
15 |
16 | export type CommonUnit = 'year' | 'month' | 'week' | 'day' | 'hour' | 'minute' | 'second';
17 | export type Unit = CommonUnit | 'quarter' | 'now';
18 |
19 | export type CountLabels = {
20 | [count in Count]?: string;
21 | }
22 |
23 | export interface PastAndFutureLabels {
24 | past: string | CountLabels;
25 | future: string | CountLabels;
26 | previous?: string;
27 | current?: string;
28 | next?: string;
29 | }
30 |
31 | export interface PastAndFutureNowLabels {
32 | past: string;
33 | future: string;
34 | current: string;
35 | }
36 |
37 | export type UnitLabels = string | CountLabels | PastAndFutureLabels
38 |
39 | export type Labels = {
40 | year: UnitLabels;
41 | quarter?: UnitLabels;
42 | month: UnitLabels;
43 | week: UnitLabels;
44 | day: UnitLabels;
45 | hour: UnitLabels;
46 | minute: UnitLabels;
47 | second: UnitLabels;
48 | }
49 |
50 | export type UnitLabelsMini = string | CountLabels;
51 |
52 | export type MiniLabels = {
53 | year: UnitLabelsMini;
54 | quarter?: UnitLabelsMini;
55 | month: UnitLabelsMini;
56 | week: UnitLabelsMini;
57 | day: UnitLabelsMini;
58 | hour: UnitLabelsMini;
59 | minute: UnitLabelsMini;
60 | second: UnitLabelsMini;
61 | }
62 |
63 | export type NowLabels = {
64 | now: string | PastAndFutureNowLabels;
65 | }
66 |
67 | export type LocaleData = {
68 | locale: Locale;
69 | short: Labels;
70 | narrow: Labels;
71 | long: Labels;
72 | mini?: MiniLabels;
73 | now?: NowLabels;
74 | }
75 |
76 | export type FormatStyleName =
77 | 'round' |
78 | 'round-minute' |
79 | 'mini' |
80 | 'mini-now' |
81 | 'mini-minute' |
82 | 'mini-minute-now' |
83 | 'twitter' |
84 | 'twitter-now' |
85 | 'twitter-minute' |
86 | 'twitter-minute-now' |
87 | 'twitter-first-minute';
88 |
89 | export type MinTimeFunction = (date: number, options: {
90 | future: boolean,
91 | getMinTimeForUnit: (unit: Unit, prevUnit?: Unit) => number | void
92 | }) => number;
93 |
94 | export interface Step {
95 | formatAs?: Unit;
96 |
97 | minTime?: number | MinTimeFunction;
98 |
99 | format?(date: DateInput, locale: Locale, options: {
100 | formatAs: (unit: Unit, value: number) => string,
101 | now: number,
102 | future: boolean
103 | }): string | void;
104 |
105 | getTimeToNextUpdate?(date: DateInput, options: {
106 | getTimeToNextUpdateForUnit: (unit: Unit) => number | void,
107 | now: number,
108 | future: boolean
109 | }): number | void;
110 | }
111 |
112 | export interface Style {
113 | steps: Step[];
114 | labels: LabelStyleName | LabelStyleName[];
115 | round?: Rounding;
116 | }
117 |
118 | interface FormatOptions {
119 | now?: number;
120 | future?: boolean;
121 | getTimeToNextUpdate?: boolean;
122 | round?: Rounding;
123 | }
124 |
125 | export default class TimeAgo {
126 | constructor(locale: Locale | Locale[], options?: { polyfill?: boolean });
127 | // When `getTimeToNextUpdate: true` option is passed to `.format()`,
128 | // it returns an array containing the formatted time and the "time to next update" interval.
129 | // https://gitlab.com/catamphetamine/javascript-time-ago#update-interval
130 | // Perhaps it's not the best solution, and it would be better to introduce a new function called
131 | // `.formatAndGetTimeToNextUpdate()`. But at this stage that would require a "major" version number update,
132 | // and I wouldn't prefer doing that for such an insignificant change.
133 | format(date: DateInput, style?: FormatStyleName | Style, options?: FormatOptions): FormatOptions['getTimeToNextUpdate'] extends true ? [string, number?] : string;
134 | format(date: DateInput, options: Options): Options['getTimeToNextUpdate'] extends true ? [string, number?] : string;
135 | getLabels(labelsType: LabelStyleName | LabelStyleName[]): Labels;
136 | static addLocale(localeData: LocaleData): void;
137 | static addDefaultLocale(localeData: LocaleData): void;
138 | static setDefaultLocale(locale: Locale): void;
139 | static addLabels(locale: Locale, name: LabelStyleName, labels: Labels): void;
140 | }
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | export { default } from './modules/TimeAgo.js'
2 |
3 | // I guess these two are deprecated.
4 | export {
5 | intlDateTimeFormatSupported as intlDateTimeFormatSupported,
6 | intlDateTimeFormatSupportedLocale as intlDateTimeFormatSupportedLocale
7 | } from './modules/locale.js'
--------------------------------------------------------------------------------
/load-all-locales/index.cjs:
--------------------------------------------------------------------------------
1 | var TimeAgo = require("javascript-time-ago")
2 | TimeAgo.addLocale(require("javascript-time-ago/locale/af.json"))
3 | TimeAgo.addLocale(require("javascript-time-ago/locale/am.json"))
4 | TimeAgo.addLocale(require("javascript-time-ago/locale/ar.json"))
5 | TimeAgo.addLocale(require("javascript-time-ago/locale/ar-AE.json"))
6 | TimeAgo.addLocale(require("javascript-time-ago/locale/as.json"))
7 | TimeAgo.addLocale(require("javascript-time-ago/locale/ast.json"))
8 | TimeAgo.addLocale(require("javascript-time-ago/locale/az.json"))
9 | TimeAgo.addLocale(require("javascript-time-ago/locale/be.json"))
10 | TimeAgo.addLocale(require("javascript-time-ago/locale/bg.json"))
11 | TimeAgo.addLocale(require("javascript-time-ago/locale/bgc.json"))
12 | TimeAgo.addLocale(require("javascript-time-ago/locale/bn.json"))
13 | TimeAgo.addLocale(require("javascript-time-ago/locale/br.json"))
14 | TimeAgo.addLocale(require("javascript-time-ago/locale/brx.json"))
15 | TimeAgo.addLocale(require("javascript-time-ago/locale/bs.json"))
16 | TimeAgo.addLocale(require("javascript-time-ago/locale/bs-Cyrl.json"))
17 | TimeAgo.addLocale(require("javascript-time-ago/locale/ca.json"))
18 | TimeAgo.addLocale(require("javascript-time-ago/locale/ccp.json"))
19 | TimeAgo.addLocale(require("javascript-time-ago/locale/ce.json"))
20 | TimeAgo.addLocale(require("javascript-time-ago/locale/ceb.json"))
21 | TimeAgo.addLocale(require("javascript-time-ago/locale/chr.json"))
22 | TimeAgo.addLocale(require("javascript-time-ago/locale/cs.json"))
23 | TimeAgo.addLocale(require("javascript-time-ago/locale/cv.json"))
24 | TimeAgo.addLocale(require("javascript-time-ago/locale/cy.json"))
25 | TimeAgo.addLocale(require("javascript-time-ago/locale/da.json"))
26 | TimeAgo.addLocale(require("javascript-time-ago/locale/de.json"))
27 | TimeAgo.addLocale(require("javascript-time-ago/locale/dsb.json"))
28 | TimeAgo.addLocale(require("javascript-time-ago/locale/dz.json"))
29 | TimeAgo.addLocale(require("javascript-time-ago/locale/ee.json"))
30 | TimeAgo.addLocale(require("javascript-time-ago/locale/el.json"))
31 | TimeAgo.addLocale(require("javascript-time-ago/locale/en.json"))
32 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-001.json"))
33 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-150.json"))
34 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-AG.json"))
35 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-AI.json"))
36 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-AT.json"))
37 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-AU.json"))
38 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-BB.json"))
39 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-BE.json"))
40 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-BM.json"))
41 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-BS.json"))
42 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-BW.json"))
43 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-BZ.json"))
44 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-CA.json"))
45 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-CC.json"))
46 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-CH.json"))
47 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-CK.json"))
48 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-CM.json"))
49 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-CX.json"))
50 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-CY.json"))
51 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-DE.json"))
52 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-DG.json"))
53 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-DK.json"))
54 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-DM.json"))
55 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-ER.json"))
56 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-FI.json"))
57 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-FJ.json"))
58 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-FK.json"))
59 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-FM.json"))
60 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-GB.json"))
61 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-GD.json"))
62 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-GG.json"))
63 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-GH.json"))
64 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-GI.json"))
65 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-GM.json"))
66 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-GY.json"))
67 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-HK.json"))
68 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-IE.json"))
69 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-IL.json"))
70 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-IM.json"))
71 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-IN.json"))
72 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-IO.json"))
73 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-JE.json"))
74 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-JM.json"))
75 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-KE.json"))
76 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-KI.json"))
77 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-KN.json"))
78 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-KY.json"))
79 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-LC.json"))
80 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-LR.json"))
81 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-LS.json"))
82 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-MG.json"))
83 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-MO.json"))
84 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-MS.json"))
85 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-MT.json"))
86 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-MU.json"))
87 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-MV.json"))
88 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-MW.json"))
89 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-MY.json"))
90 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-NA.json"))
91 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-NF.json"))
92 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-NG.json"))
93 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-NL.json"))
94 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-NR.json"))
95 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-NU.json"))
96 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-NZ.json"))
97 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-PG.json"))
98 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-PK.json"))
99 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-PN.json"))
100 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-PW.json"))
101 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-RW.json"))
102 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-SB.json"))
103 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-SC.json"))
104 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-SD.json"))
105 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-SE.json"))
106 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-SG.json"))
107 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-SH.json"))
108 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-SI.json"))
109 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-SL.json"))
110 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-SS.json"))
111 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-SX.json"))
112 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-SZ.json"))
113 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-TC.json"))
114 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-TK.json"))
115 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-TO.json"))
116 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-TT.json"))
117 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-TV.json"))
118 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-TZ.json"))
119 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-UG.json"))
120 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-VC.json"))
121 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-VG.json"))
122 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-VU.json"))
123 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-WS.json"))
124 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-ZA.json"))
125 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-ZM.json"))
126 | TimeAgo.addLocale(require("javascript-time-ago/locale/en-ZW.json"))
127 | TimeAgo.addLocale(require("javascript-time-ago/locale/eo.json"))
128 | TimeAgo.addLocale(require("javascript-time-ago/locale/es.json"))
129 | TimeAgo.addLocale(require("javascript-time-ago/locale/es-419.json"))
130 | TimeAgo.addLocale(require("javascript-time-ago/locale/es-AR.json"))
131 | TimeAgo.addLocale(require("javascript-time-ago/locale/es-BO.json"))
132 | TimeAgo.addLocale(require("javascript-time-ago/locale/es-BR.json"))
133 | TimeAgo.addLocale(require("javascript-time-ago/locale/es-BZ.json"))
134 | TimeAgo.addLocale(require("javascript-time-ago/locale/es-CL.json"))
135 | TimeAgo.addLocale(require("javascript-time-ago/locale/es-CO.json"))
136 | TimeAgo.addLocale(require("javascript-time-ago/locale/es-CR.json"))
137 | TimeAgo.addLocale(require("javascript-time-ago/locale/es-CU.json"))
138 | TimeAgo.addLocale(require("javascript-time-ago/locale/es-DO.json"))
139 | TimeAgo.addLocale(require("javascript-time-ago/locale/es-EC.json"))
140 | TimeAgo.addLocale(require("javascript-time-ago/locale/es-GT.json"))
141 | TimeAgo.addLocale(require("javascript-time-ago/locale/es-HN.json"))
142 | TimeAgo.addLocale(require("javascript-time-ago/locale/es-MX.json"))
143 | TimeAgo.addLocale(require("javascript-time-ago/locale/es-NI.json"))
144 | TimeAgo.addLocale(require("javascript-time-ago/locale/es-PA.json"))
145 | TimeAgo.addLocale(require("javascript-time-ago/locale/es-PE.json"))
146 | TimeAgo.addLocale(require("javascript-time-ago/locale/es-PR.json"))
147 | TimeAgo.addLocale(require("javascript-time-ago/locale/es-PY.json"))
148 | TimeAgo.addLocale(require("javascript-time-ago/locale/es-SV.json"))
149 | TimeAgo.addLocale(require("javascript-time-ago/locale/es-US.json"))
150 | TimeAgo.addLocale(require("javascript-time-ago/locale/es-UY.json"))
151 | TimeAgo.addLocale(require("javascript-time-ago/locale/es-VE.json"))
152 | TimeAgo.addLocale(require("javascript-time-ago/locale/et.json"))
153 | TimeAgo.addLocale(require("javascript-time-ago/locale/eu.json"))
154 | TimeAgo.addLocale(require("javascript-time-ago/locale/fa.json"))
155 | TimeAgo.addLocale(require("javascript-time-ago/locale/ff-Adlm.json"))
156 | TimeAgo.addLocale(require("javascript-time-ago/locale/fi.json"))
157 | TimeAgo.addLocale(require("javascript-time-ago/locale/fil.json"))
158 | TimeAgo.addLocale(require("javascript-time-ago/locale/fo.json"))
159 | TimeAgo.addLocale(require("javascript-time-ago/locale/fr.json"))
160 | TimeAgo.addLocale(require("javascript-time-ago/locale/fr-CA.json"))
161 | TimeAgo.addLocale(require("javascript-time-ago/locale/fur.json"))
162 | TimeAgo.addLocale(require("javascript-time-ago/locale/fy.json"))
163 | TimeAgo.addLocale(require("javascript-time-ago/locale/ga.json"))
164 | TimeAgo.addLocale(require("javascript-time-ago/locale/gd.json"))
165 | TimeAgo.addLocale(require("javascript-time-ago/locale/gl.json"))
166 | TimeAgo.addLocale(require("javascript-time-ago/locale/gu.json"))
167 | TimeAgo.addLocale(require("javascript-time-ago/locale/ha.json"))
168 | TimeAgo.addLocale(require("javascript-time-ago/locale/he.json"))
169 | TimeAgo.addLocale(require("javascript-time-ago/locale/hi.json"))
170 | TimeAgo.addLocale(require("javascript-time-ago/locale/hi-Latn.json"))
171 | TimeAgo.addLocale(require("javascript-time-ago/locale/hr.json"))
172 | TimeAgo.addLocale(require("javascript-time-ago/locale/hsb.json"))
173 | TimeAgo.addLocale(require("javascript-time-ago/locale/hu.json"))
174 | TimeAgo.addLocale(require("javascript-time-ago/locale/hy.json"))
175 | TimeAgo.addLocale(require("javascript-time-ago/locale/ia.json"))
176 | TimeAgo.addLocale(require("javascript-time-ago/locale/id.json"))
177 | TimeAgo.addLocale(require("javascript-time-ago/locale/ig.json"))
178 | TimeAgo.addLocale(require("javascript-time-ago/locale/is.json"))
179 | TimeAgo.addLocale(require("javascript-time-ago/locale/it.json"))
180 | TimeAgo.addLocale(require("javascript-time-ago/locale/ja.json"))
181 | TimeAgo.addLocale(require("javascript-time-ago/locale/jgo.json"))
182 | TimeAgo.addLocale(require("javascript-time-ago/locale/jv.json"))
183 | TimeAgo.addLocale(require("javascript-time-ago/locale/ka.json"))
184 | TimeAgo.addLocale(require("javascript-time-ago/locale/kea.json"))
185 | TimeAgo.addLocale(require("javascript-time-ago/locale/kgp.json"))
186 | TimeAgo.addLocale(require("javascript-time-ago/locale/kk.json"))
187 | TimeAgo.addLocale(require("javascript-time-ago/locale/kl.json"))
188 | TimeAgo.addLocale(require("javascript-time-ago/locale/km.json"))
189 | TimeAgo.addLocale(require("javascript-time-ago/locale/kn.json"))
190 | TimeAgo.addLocale(require("javascript-time-ago/locale/ko.json"))
191 | TimeAgo.addLocale(require("javascript-time-ago/locale/kok.json"))
192 | TimeAgo.addLocale(require("javascript-time-ago/locale/ks.json"))
193 | TimeAgo.addLocale(require("javascript-time-ago/locale/ksh.json"))
194 | TimeAgo.addLocale(require("javascript-time-ago/locale/ku.json"))
195 | TimeAgo.addLocale(require("javascript-time-ago/locale/ky.json"))
196 | TimeAgo.addLocale(require("javascript-time-ago/locale/lb.json"))
197 | TimeAgo.addLocale(require("javascript-time-ago/locale/lkt.json"))
198 | TimeAgo.addLocale(require("javascript-time-ago/locale/lo.json"))
199 | TimeAgo.addLocale(require("javascript-time-ago/locale/lt.json"))
200 | TimeAgo.addLocale(require("javascript-time-ago/locale/lv.json"))
201 | TimeAgo.addLocale(require("javascript-time-ago/locale/mai.json"))
202 | TimeAgo.addLocale(require("javascript-time-ago/locale/mi.json"))
203 | TimeAgo.addLocale(require("javascript-time-ago/locale/mk.json"))
204 | TimeAgo.addLocale(require("javascript-time-ago/locale/ml.json"))
205 | TimeAgo.addLocale(require("javascript-time-ago/locale/mn.json"))
206 | TimeAgo.addLocale(require("javascript-time-ago/locale/mni.json"))
207 | TimeAgo.addLocale(require("javascript-time-ago/locale/mr.json"))
208 | TimeAgo.addLocale(require("javascript-time-ago/locale/ms.json"))
209 | TimeAgo.addLocale(require("javascript-time-ago/locale/mt.json"))
210 | TimeAgo.addLocale(require("javascript-time-ago/locale/my.json"))
211 | TimeAgo.addLocale(require("javascript-time-ago/locale/mzn.json"))
212 | TimeAgo.addLocale(require("javascript-time-ago/locale/nb.json"))
213 | TimeAgo.addLocale(require("javascript-time-ago/locale/ne.json"))
214 | TimeAgo.addLocale(require("javascript-time-ago/locale/nl.json"))
215 | TimeAgo.addLocale(require("javascript-time-ago/locale/nn.json"))
216 | TimeAgo.addLocale(require("javascript-time-ago/locale/no.json"))
217 | TimeAgo.addLocale(require("javascript-time-ago/locale/or.json"))
218 | TimeAgo.addLocale(require("javascript-time-ago/locale/pa.json"))
219 | TimeAgo.addLocale(require("javascript-time-ago/locale/pcm.json"))
220 | TimeAgo.addLocale(require("javascript-time-ago/locale/pl.json"))
221 | TimeAgo.addLocale(require("javascript-time-ago/locale/ps.json"))
222 | TimeAgo.addLocale(require("javascript-time-ago/locale/ps-PK.json"))
223 | TimeAgo.addLocale(require("javascript-time-ago/locale/pt.json"))
224 | TimeAgo.addLocale(require("javascript-time-ago/locale/pt-AO.json"))
225 | TimeAgo.addLocale(require("javascript-time-ago/locale/pt-CH.json"))
226 | TimeAgo.addLocale(require("javascript-time-ago/locale/pt-CV.json"))
227 | TimeAgo.addLocale(require("javascript-time-ago/locale/pt-GQ.json"))
228 | TimeAgo.addLocale(require("javascript-time-ago/locale/pt-GW.json"))
229 | TimeAgo.addLocale(require("javascript-time-ago/locale/pt-LU.json"))
230 | TimeAgo.addLocale(require("javascript-time-ago/locale/pt-MO.json"))
231 | TimeAgo.addLocale(require("javascript-time-ago/locale/pt-MZ.json"))
232 | TimeAgo.addLocale(require("javascript-time-ago/locale/pt-PT.json"))
233 | TimeAgo.addLocale(require("javascript-time-ago/locale/pt-ST.json"))
234 | TimeAgo.addLocale(require("javascript-time-ago/locale/pt-TL.json"))
235 | TimeAgo.addLocale(require("javascript-time-ago/locale/qu.json"))
236 | TimeAgo.addLocale(require("javascript-time-ago/locale/raj.json"))
237 | TimeAgo.addLocale(require("javascript-time-ago/locale/rm.json"))
238 | TimeAgo.addLocale(require("javascript-time-ago/locale/ro.json"))
239 | TimeAgo.addLocale(require("javascript-time-ago/locale/ru.json"))
240 | TimeAgo.addLocale(require("javascript-time-ago/locale/sah.json"))
241 | TimeAgo.addLocale(require("javascript-time-ago/locale/sc.json"))
242 | TimeAgo.addLocale(require("javascript-time-ago/locale/sd.json"))
243 | TimeAgo.addLocale(require("javascript-time-ago/locale/se.json"))
244 | TimeAgo.addLocale(require("javascript-time-ago/locale/se-FI.json"))
245 | TimeAgo.addLocale(require("javascript-time-ago/locale/si.json"))
246 | TimeAgo.addLocale(require("javascript-time-ago/locale/sk.json"))
247 | TimeAgo.addLocale(require("javascript-time-ago/locale/sl.json"))
248 | TimeAgo.addLocale(require("javascript-time-ago/locale/so.json"))
249 | TimeAgo.addLocale(require("javascript-time-ago/locale/sq.json"))
250 | TimeAgo.addLocale(require("javascript-time-ago/locale/sr.json"))
251 | TimeAgo.addLocale(require("javascript-time-ago/locale/sr-Cyrl-BA.json"))
252 | TimeAgo.addLocale(require("javascript-time-ago/locale/sr-Latn.json"))
253 | TimeAgo.addLocale(require("javascript-time-ago/locale/sr-Latn-BA.json"))
254 | TimeAgo.addLocale(require("javascript-time-ago/locale/su.json"))
255 | TimeAgo.addLocale(require("javascript-time-ago/locale/sv.json"))
256 | TimeAgo.addLocale(require("javascript-time-ago/locale/sw.json"))
257 | TimeAgo.addLocale(require("javascript-time-ago/locale/ta.json"))
258 | TimeAgo.addLocale(require("javascript-time-ago/locale/te.json"))
259 | TimeAgo.addLocale(require("javascript-time-ago/locale/tg.json"))
260 | TimeAgo.addLocale(require("javascript-time-ago/locale/th.json"))
261 | TimeAgo.addLocale(require("javascript-time-ago/locale/ti.json"))
262 | TimeAgo.addLocale(require("javascript-time-ago/locale/tk.json"))
263 | TimeAgo.addLocale(require("javascript-time-ago/locale/to.json"))
264 | TimeAgo.addLocale(require("javascript-time-ago/locale/tr.json"))
265 | TimeAgo.addLocale(require("javascript-time-ago/locale/tt.json"))
266 | TimeAgo.addLocale(require("javascript-time-ago/locale/ug.json"))
267 | TimeAgo.addLocale(require("javascript-time-ago/locale/uk.json"))
268 | TimeAgo.addLocale(require("javascript-time-ago/locale/ur.json"))
269 | TimeAgo.addLocale(require("javascript-time-ago/locale/ur-IN.json"))
270 | TimeAgo.addLocale(require("javascript-time-ago/locale/uz.json"))
271 | TimeAgo.addLocale(require("javascript-time-ago/locale/uz-Cyrl.json"))
272 | TimeAgo.addLocale(require("javascript-time-ago/locale/vi.json"))
273 | TimeAgo.addLocale(require("javascript-time-ago/locale/wae.json"))
274 | TimeAgo.addLocale(require("javascript-time-ago/locale/wo.json"))
275 | TimeAgo.addLocale(require("javascript-time-ago/locale/xh.json"))
276 | TimeAgo.addLocale(require("javascript-time-ago/locale/yi.json"))
277 | TimeAgo.addLocale(require("javascript-time-ago/locale/yo.json"))
278 | TimeAgo.addLocale(require("javascript-time-ago/locale/yo-BJ.json"))
279 | TimeAgo.addLocale(require("javascript-time-ago/locale/yrl.json"))
280 | TimeAgo.addLocale(require("javascript-time-ago/locale/yue.json"))
281 | TimeAgo.addLocale(require("javascript-time-ago/locale/yue-Hans.json"))
282 | TimeAgo.addLocale(require("javascript-time-ago/locale/zh.json"))
283 | TimeAgo.addLocale(require("javascript-time-ago/locale/zh-Hans-HK.json"))
284 | TimeAgo.addLocale(require("javascript-time-ago/locale/zh-Hans-MO.json"))
285 | TimeAgo.addLocale(require("javascript-time-ago/locale/zh-Hans-SG.json"))
286 | TimeAgo.addLocale(require("javascript-time-ago/locale/zh-Hant.json"))
287 | TimeAgo.addLocale(require("javascript-time-ago/locale/zh-Hant-HK.json"))
288 | TimeAgo.addLocale(require("javascript-time-ago/locale/zh-Hant-MO.json"))
289 | TimeAgo.addLocale(require("javascript-time-ago/locale/zu.json"))
--------------------------------------------------------------------------------
/load-all-locales/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "name": "javascript-time-ago/load-all-locales",
4 | "main": "index.cjs",
5 | "module": "index.js",
6 | "type": "module",
7 | "exports": {
8 | ".": {
9 | "import": "./index.js",
10 | "require": "./index.cjs"
11 | }
12 | },
13 | "sideEffects": [
14 | "./index.js",
15 | "./index.cjs",
16 | "./index.cjs.js"
17 | ]
18 | }
--------------------------------------------------------------------------------
/locale-more-styles/README.md:
--------------------------------------------------------------------------------
1 | These are the additional localized time interval labels.
2 |
3 | ## `now`
4 |
5 | `"now"` labels. Shorter is better.
6 |
7 | * `"now"` (current) — Right now.
8 | * `"just now"` (past) — A moment ago.
9 | * `"in a moment"` (future) — In a moment.
10 |
11 | ## `mini`
12 |
13 | (previoiusly called `"tiny"`)
14 |
15 | The shortest ones available, without the `" ago"`/`"in "` part.
16 |
17 | * `1s`
18 | * `2m`
19 | * `3d`
20 | * `4yr`
21 | * …
22 |
23 | These're the ones used in `"mini"` and `"twitter"` styles.
24 |
25 | ## `long-time`
26 |
27 | (maybe these should be removed?)
28 |
29 | These're the `long` labels without the `" ago"`/`"in "` part.
30 |
31 | * `1 second`
32 | * `2 minutes`
33 | * `3 days`
34 | * `4 years`
35 | * …
36 |
37 | These labels can be used in scenarios like a progress bar displaying the time left: `"2 hours"`, `"15 minutes"`.
38 |
39 | ## `short-time`
40 |
41 | (maybe these should be removed?)
42 |
43 | These're the `short` labels without the `" ago"`/`"in "` part.
44 |
45 | * `1 sec.`
46 | * `2 min.`
47 | * `3 days`
48 | * `4 yr.`
49 | * …
50 |
51 | These labels can be used in scenarios like a progress bar displaying the time left: `"2 hr."`, `"15 min."`.
--------------------------------------------------------------------------------
/locale-more-styles/ckb/long-time.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": "{0} ساڵ",
3 | "month": "{0} مانگ",
4 | "week": "{0} حەفتە",
5 | "day": "{0} ڕۆژ",
6 | "hour": "{0} کاتژمێر",
7 | "minute": "{0} خولەک",
8 | "second": "{0} چرکە"
9 | }
10 |
--------------------------------------------------------------------------------
/locale-more-styles/ckb/mini.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": "{0}ساڵ",
3 | "month": "{0}مانگ",
4 | "week": "{0}حەفتە",
5 | "day": "{0}ڕۆژ",
6 | "hour": "{0}کاتژمێر",
7 | "minute": "{0}خولەک",
8 | "second": "{0}چرکە"
9 | }
10 |
--------------------------------------------------------------------------------
/locale-more-styles/ckb/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "now": {
3 | "current": "ئێستا",
4 | "future": "لە ئێستادا",
5 | "past": "تەنها ئێستا"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/locale-more-styles/ckb/short-time.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": "{0} ساڵ",
3 | "month": "{0} مانگ",
4 | "week": "{0} حەفتە",
5 | "day": {
6 | "one": "{0} ڕۆژ",
7 | "other": "{0} ڕۆژ"
8 | },
9 | "hour": "{0} کاتژمێر",
10 | "minute": "{0} خولەک",
11 | "second": "{0} چرکە"
12 | }
13 |
--------------------------------------------------------------------------------
/locale-more-styles/da/long-time.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": {
3 | "one": "{0} år",
4 | "other": "{0} år"
5 | },
6 | "month": {
7 | "one": "{0} måned",
8 | "other": "{0} måneder"
9 | },
10 | "week": {
11 | "one": "{0} uge",
12 | "other": "{0} uger"
13 | },
14 | "day": {
15 | "one": "{0} dag",
16 | "other": "{0} dage"
17 | },
18 | "hour": {
19 | "one": "{0} time",
20 | "other": "{0} timer"
21 | },
22 | "minute": {
23 | "one": "{0} minut",
24 | "other": "{0} minutter"
25 | },
26 | "second": {
27 | "one": "{0} sekund",
28 | "other": "{0} sekunder"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/locale-more-styles/da/mini.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": "{0} år",
3 | "month": {
4 | "one": "{0} md.",
5 | "other": "{0} mdr."
6 | },
7 | "week": {
8 | "one": "{0} uge",
9 | "other": "{0} uger"
10 | },
11 | "day": {
12 | "one": "{0} dag",
13 | "other": "{0} dage"
14 | },
15 | "hour": "{0} t.",
16 | "minute": "{0} min.",
17 | "second": "{0} sek."
18 | }
--------------------------------------------------------------------------------
/locale-more-styles/da/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "now": {
3 | "current": "nu",
4 | "future": "om et øjeblik",
5 | "past": "lige nu"
6 | }
7 | }
--------------------------------------------------------------------------------
/locale-more-styles/de/long-time.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": {
3 | "one": "{0} Jahr",
4 | "other": "{0} Jahre"
5 | },
6 | "month": {
7 | "one": "{0} Monat",
8 | "other": "{0} Monate"
9 | },
10 | "week": {
11 | "one": "{0} Woche",
12 | "other": "{0} Wochen"
13 | },
14 | "day": {
15 | "one": "{0} Tag",
16 | "other": "{0} Tage"
17 | },
18 | "hour": {
19 | "one": "{0} Stunde",
20 | "other": "{0} Stunden"
21 | },
22 | "minute": {
23 | "one": "{0} Minute",
24 | "other": "{0} Minuten"
25 | },
26 | "second": {
27 | "one": "{0} Sekunde",
28 | "other": "{0} Sekunden"
29 | }
30 | }
--------------------------------------------------------------------------------
/locale-more-styles/de/mini.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": "{0} J.",
3 | "month": "{0} Mt.",
4 | "week": "{0} Wo.",
5 | "day": "{0} T.",
6 | "hour": "{0} Std.",
7 | "minute": "{0} Min.",
8 | "second": "{0} s"
9 | }
--------------------------------------------------------------------------------
/locale-more-styles/de/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "now": {
3 | "current": "jetzt",
4 | "future": "in einem Moment",
5 | "past": "gerade jetzt"
6 | }
7 | }
--------------------------------------------------------------------------------
/locale-more-styles/el/mini.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": "{0}χρ.",
3 | "month": "{0}μην.",
4 | "week": "{0}εβδ.",
5 | "day": "{0}ημ.",
6 | "hour": "{0}ώρ.",
7 | "minute": "{0}λ.",
8 | "second": "{0}δ."
9 | }
--------------------------------------------------------------------------------
/locale-more-styles/el/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "now": {
3 | "current": "τώρα",
4 | "past": "μόλις τώρα",
5 | "future": "τώρα"
6 | }
7 | }
--------------------------------------------------------------------------------
/locale-more-styles/en/long-time.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": {
3 | "one": "{0} year",
4 | "other": "{0} years"
5 | },
6 | "month": {
7 | "one": "{0} month",
8 | "other": "{0} months"
9 | },
10 | "week": {
11 | "one": "{0} week",
12 | "other": "{0} weeks"
13 | },
14 | "day": {
15 | "one": "{0} day",
16 | "other": "{0} days"
17 | },
18 | "hour": {
19 | "one": "{0} hour",
20 | "other": "{0} hours"
21 | },
22 | "minute": {
23 | "one": "{0} minute",
24 | "other": "{0} minutes"
25 | },
26 | "second": {
27 | "one": "{0} second",
28 | "other": "{0} seconds"
29 | }
30 | }
--------------------------------------------------------------------------------
/locale-more-styles/en/mini.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": "{0}yr",
3 | "month": "{0}mo",
4 | "week": "{0}wk",
5 | "day": "{0}d",
6 | "hour": "{0}h",
7 | "minute": "{0}m",
8 | "second": "{0}s"
9 | }
--------------------------------------------------------------------------------
/locale-more-styles/en/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "now": {
3 | "current": "now",
4 | "future": "in a moment",
5 | "past": "just now"
6 | }
7 | }
--------------------------------------------------------------------------------
/locale-more-styles/en/short-time.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": "{0} yr.",
3 | "month": "{0} mo.",
4 | "week": "{0} wk.",
5 | "day": {
6 | "one": "{0} day",
7 | "other": "{0} days"
8 | },
9 | "hour": "{0} hr.",
10 | "minute": "{0} min.",
11 | "second": "{0} sec."
12 | }
--------------------------------------------------------------------------------
/locale-more-styles/eo/long-time.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": {
3 | "one": "{0} jaro",
4 | "other": "{0} jaroj"
5 | },
6 | "month": {
7 | "one": "{0} monato",
8 | "other": "{0} monatoj"
9 | },
10 | "week": {
11 | "one": "{0} semajno",
12 | "other": "{0} semajnoj"
13 | },
14 | "day": {
15 | "one": "{0} tago",
16 | "other": "{0} tagoj"
17 | },
18 | "hour": {
19 | "one": "{0} horo",
20 | "other": "{0} horoj"
21 | },
22 | "minute": {
23 | "one": "{0} minuto",
24 | "other": "{0} minutoj"
25 | },
26 | "second": {
27 | "one": "{0} sekundo",
28 | "other": "{0} sekundoj"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/locale-more-styles/eo/mini.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": "{0} j.",
3 | "month": "{0} m.",
4 | "week": "{0} sem.",
5 | "day": "{0} t.",
6 | "hour": "{0} h.",
7 | "minute": "{0} m.",
8 | "second": "{0} sek."
9 | }
10 |
--------------------------------------------------------------------------------
/locale-more-styles/eo/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "now": {
3 | "current": "nun",
4 | "future": "baldaŭ",
5 | "past": "ĵus"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/locale-more-styles/eo/short-time.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": "{0} ja.",
3 | "month": "{0} mo.",
4 | "week": "{0} sem.",
5 | "day": "{0} ta.",
6 | "hour": "{0} hor.",
7 | "minute": "{0} min.",
8 | "second": "{0} sek."
9 | }
10 |
--------------------------------------------------------------------------------
/locale-more-styles/es/long-time.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": {
3 | "one": "{0} año",
4 | "other": "{0} años"
5 | },
6 | "month": {
7 | "one": "{0} mes",
8 | "other": "{0} meses"
9 | },
10 | "week": {
11 | "one": "{0} semana",
12 | "other": "{0} semanas"
13 | },
14 | "day": {
15 | "one": "{0} día",
16 | "other": "{0} días"
17 | },
18 | "hour": {
19 | "one": "{0} hora",
20 | "other": "{0} horas"
21 | },
22 | "minute": {
23 | "one": "{0} minuto",
24 | "other": "{0} minutos"
25 | },
26 | "second": {
27 | "one": "{0} segundo",
28 | "other": "{0} segundos"
29 | }
30 | }
--------------------------------------------------------------------------------
/locale-more-styles/es/mini.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": {
3 | "one": "{0} año",
4 | "other": "{0} años"
5 | },
6 | "month": {
7 | "one": "{0} mes",
8 | "other": "{0} meses"
9 | },
10 | "week": "{0} sem.",
11 | "day": {
12 | "one": "{0} día",
13 | "other": "{0} días"
14 | },
15 | "hour": {
16 | "one": "{0} hora",
17 | "other": "{0} horas"
18 | },
19 | "minute": "{0} min.",
20 | "second": "{0} seg."
21 | }
--------------------------------------------------------------------------------
/locale-more-styles/es/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "now": {
3 | "current": "ahora",
4 | "future": "enseguida",
5 | "past": "ahora mismo"
6 | }
7 | }
--------------------------------------------------------------------------------
/locale-more-styles/fr/long-time.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": {
3 | "one": "{0} an",
4 | "other": "{0} ans"
5 | },
6 | "month": {
7 | "one": "{0} mois",
8 | "other": "{0} mois"
9 | },
10 | "week": {
11 | "one": "{0} semaine",
12 | "other": "{0} semaines"
13 | },
14 | "day": {
15 | "one": "{0} jour",
16 | "other": "{0} jours"
17 | },
18 | "hour": {
19 | "one": "{0} heure",
20 | "other": "{0} heures"
21 | },
22 | "minute": {
23 | "one": "{0} minute",
24 | "other": "{0} minutes"
25 | },
26 | "second": {
27 | "one": "{0} seconde",
28 | "other": "{0} secondes"
29 | }
30 | }
--------------------------------------------------------------------------------
/locale-more-styles/fr/mini.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": {
3 | "one": "{0} an",
4 | "other": "{0} ans"
5 | },
6 | "month": "{0} mois",
7 | "week": "{0} sem.",
8 | "day": {
9 | "one": "{0} jour",
10 | "other": "{0} jours"
11 | },
12 | "hour": "{0} h",
13 | "minute": "{0} min.",
14 | "second": "{0} sec."
15 | }
--------------------------------------------------------------------------------
/locale-more-styles/fr/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "now": {
3 | "current": "maintenant",
4 | "future": "dans un instant",
5 | "past": "à l'instant"
6 | }
7 | }
--------------------------------------------------------------------------------
/locale-more-styles/hi/mini.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": "{0}वर्ष",
3 | "quarter": {
4 | "one": "{0}तिमाही",
5 | "other": "{0}तिमाहियों"
6 | },
7 | "month": "{0}माह",
8 | "week": "{0}सप्ताह",
9 | "day": "{0}दिन",
10 | "hour": "{0}घं॰",
11 | "minute": "{0}मि॰",
12 | "second": "{0}से॰"
13 | }
--------------------------------------------------------------------------------
/locale-more-styles/hi/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "now": {
3 | "current": "अब",
4 | "future": "अभी",
5 | "past": "अभी"
6 | }
7 | }
--------------------------------------------------------------------------------
/locale-more-styles/id/long-time.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": "{0} tahun",
3 | "month": "{0} bulan",
4 | "week": "{0} minggu",
5 | "day": "{0} hari",
6 | "hour": "{0} jam",
7 | "minute": "{0} menit",
8 | "second": "{0} detik"
9 | }
--------------------------------------------------------------------------------
/locale-more-styles/id/mini.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": "{0}thn",
3 | "month": "{0}bln",
4 | "week": "{0}mg",
5 | "day": "{0}hr",
6 | "hour": "{0}jam",
7 | "minute": "{0}mnt",
8 | "second": "{0}dtk"
9 | }
--------------------------------------------------------------------------------
/locale-more-styles/id/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "now": {
3 | "current": "sekarang",
4 | "future": "beberapa saat lagi",
5 | "past": "baru saja"
6 | }
7 | }
--------------------------------------------------------------------------------
/locale-more-styles/id/short-time.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": "{0} thn.",
3 | "month": "{0} bln.",
4 | "week": "{0} mg.",
5 | "day": "{0} hr.",
6 | "hour": "{0} jam.",
7 | "minute": "{0} mnt.",
8 | "second": "{0} dtk."
9 | }
--------------------------------------------------------------------------------
/locale-more-styles/it/long-time.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": {
3 | "one": "{0} anno",
4 | "other": "{0} anni"
5 | },
6 | "month": {
7 | "one": "{0} mese",
8 | "other": "{0} mesi"
9 | },
10 | "week": {
11 | "one": "{0} settimana",
12 | "other": "{0} settimane"
13 | },
14 | "day": {
15 | "one": "{0} giorno",
16 | "other": "{0} giorni"
17 | },
18 | "hour": {
19 | "one": "{0} ora",
20 | "other": "{0} ore"
21 | },
22 | "minute": {
23 | "one": "{0} minuto",
24 | "other": "{0} minuti"
25 | },
26 | "second": {
27 | "one": "{0} secondo",
28 | "other": "{0} secondi"
29 | }
30 | }
--------------------------------------------------------------------------------
/locale-more-styles/it/mini.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": {
3 | "one": "{0} anno",
4 | "other": "{0} anni"
5 | },
6 | "month": {
7 | "one": "{0} mese",
8 | "other": "{0} mesi"
9 | },
10 | "week": "{0} sett.",
11 | "day": "{0} gior.",
12 | "hour": {
13 | "one": "{0} ora",
14 | "other": "{0} ore"
15 | },
16 | "minute": "{0} min.",
17 | "second": "{0} sec."
18 | }
--------------------------------------------------------------------------------
/locale-more-styles/it/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "now": {
3 | "current": "adesso",
4 | "future": "tra poco",
5 | "past": "proprio ora"
6 | }
7 | }
--------------------------------------------------------------------------------
/locale-more-styles/ko/mini.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": "{0}년",
3 | "quarter": "{0}분기",
4 | "month": "{0}개월",
5 | "week": "{0}주",
6 | "day": "{0}일",
7 | "hour": "{0}시간",
8 | "minute": "{0}분",
9 | "second": "{0}초"
10 | }
--------------------------------------------------------------------------------
/locale-more-styles/ko/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "now": {
3 | "current": "지금",
4 | "future": "잠시 후",
5 | "past": "방금"
6 | }
7 | }
--------------------------------------------------------------------------------
/locale-more-styles/nl/long-time.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": {
3 | "one": "{0} jaar",
4 | "other": "{0} jaar"
5 | },
6 | "month": {
7 | "one": "{0} maand",
8 | "other": "{0} maanden"
9 | },
10 | "week": {
11 | "one": "{0} week",
12 | "other": "{0} weken"
13 | },
14 | "day": {
15 | "one": "{0} dag",
16 | "other": "{0} dagen"
17 | },
18 | "hour": {
19 | "one": "{0} uur",
20 | "other": "{0} uur"
21 | },
22 | "minute": {
23 | "one": "{0} minuut",
24 | "other": "{0} minuten"
25 | },
26 | "second": {
27 | "one": "{0} seconde",
28 | "other": "{0} seconden"
29 | }
30 | }
--------------------------------------------------------------------------------
/locale-more-styles/nl/mini.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": "{0} jr.",
3 | "month": "{0} mnd.",
4 | "week": {
5 | "one": "{0} week",
6 | "other": "{0} wkn."
7 | },
8 | "day": {
9 | "one": "{0} dag",
10 | "other": "{0} dgn."
11 | },
12 | "hour": "{0} uur",
13 | "minute": "{0} min.",
14 | "second": "{0} sec."
15 | }
--------------------------------------------------------------------------------
/locale-more-styles/nl/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "now": {
3 | "current": "nu",
4 | "future": "zometeen",
5 | "past": "zojuist"
6 | }
7 | }
--------------------------------------------------------------------------------
/locale-more-styles/pl/long-time.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": {
3 | "one": "{0} rok",
4 | "other": "{0} lata"
5 | },
6 | "month": {
7 | "one": "{0} miesiąc",
8 | "other": "{0} miesiące"
9 | },
10 | "week": {
11 | "one": "{0} tydzień",
12 | "other": "{0} tygodnie"
13 | },
14 | "day": {
15 | "one": "{0} dzień",
16 | "other": "{0} dni"
17 | },
18 | "hour": {
19 | "one": "{0} godzina",
20 | "other": "{0} godziny"
21 | },
22 | "minute": {
23 | "one": "{0} minuta",
24 | "other": "{0} minuty"
25 | },
26 | "second": {
27 | "one": "{0} sekunda",
28 | "other": "{0} sekundy"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/locale-more-styles/pl/mini.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": "{0}r",
3 | "month": "{0}m",
4 | "week": "{0}t",
5 | "day": "{0}d",
6 | "hour": "{0}g",
7 | "minute": "{0}m",
8 | "second": "{0}s"
9 | }
10 |
--------------------------------------------------------------------------------
/locale-more-styles/pl/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "now": {
3 | "current": "teraz",
4 | "future": "za chwilę",
5 | "past": "przed chwilą"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/locale-more-styles/pl/short-time.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": "{0} r.",
3 | "month": "{0} m-c",
4 | "week": "{0} tyg.",
5 | "day": {
6 | "one": "{0} dzień",
7 | "other": "{0} dni"
8 | },
9 | "hour": "{0} godz.",
10 | "minute": "{0} min.",
11 | "second": "{0} sek."
12 | }
13 |
--------------------------------------------------------------------------------
/locale-more-styles/pt/long-time.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": {
3 | "one": "{0} ano",
4 | "other": "{0} anos"
5 | },
6 | "month": {
7 | "one": "{0} mês",
8 | "other": "{0} meses"
9 | },
10 | "week": {
11 | "one": "{0} semana",
12 | "other": "{0} semanas"
13 | },
14 | "day": {
15 | "one": "{0} dia",
16 | "other": "{0} dias"
17 | },
18 | "hour": {
19 | "one": "{0} hora",
20 | "other": "{0} horas"
21 | },
22 | "minute": {
23 | "one": "{0} minuto",
24 | "other": "{0} minutos"
25 | },
26 | "second": {
27 | "one": "{0} segundo",
28 | "other": "{0} segundos"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/locale-more-styles/pt/mini.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": "{0}ano",
3 | "month": "{0}mes",
4 | "week": "{0}sem.",
5 | "day": "{0} d",
6 | "hour": "{0} h",
7 | "minute": "{0} m",
8 | "second": "{0} s"
9 | }
10 |
--------------------------------------------------------------------------------
/locale-more-styles/pt/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "now": {
3 | "current": "agora",
4 | "future": "em instantes",
5 | "past": "agora mesmo"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/locale-more-styles/ro/long-time.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": {
3 | "one": "{0} an",
4 | "other": "{0} ani"
5 | },
6 | "month": {
7 | "one": "{0} lună",
8 | "other": "{0} luni"
9 | },
10 | "week": {
11 | "one": "{0} săptămână",
12 | "other": "{0} săptămâni"
13 | },
14 | "day": {
15 | "one": "{0} zi",
16 | "other": "{0} zile"
17 | },
18 | "hour": {
19 | "one": "{0} oră",
20 | "other": "{0} ore"
21 | },
22 | "minute": {
23 | "one": "{0} minut",
24 | "other": "{0} minute"
25 | },
26 | "second": {
27 | "one": "{0} secundă",
28 | "other": "{0} secunde"
29 | }
30 | }
--------------------------------------------------------------------------------
/locale-more-styles/ro/mini.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": {
3 | "one": "{0} an",
4 | "other": "{0} ani"
5 | },
6 | "month": {
7 | "one": "{0} lună",
8 | "other": "{0} luni"
9 | },
10 | "week": "{0} săp.",
11 | "day": {
12 | "one": "{0} zi",
13 | "other": "{0} zile"
14 | },
15 | "hour": "{0} h",
16 | "minute": "{0} min.",
17 | "second": "{0} sec."
18 | }
--------------------------------------------------------------------------------
/locale-more-styles/ro/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "now": {
3 | "current": "acum",
4 | "future": "într-un moment",
5 | "past": "acum un moment"
6 | }
7 | }
--------------------------------------------------------------------------------
/locale-more-styles/ru/long-time.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": {
3 | "one": "{0} год",
4 | "many": "{0} лет",
5 | "other": "{0} года"
6 | },
7 | "month": {
8 | "one": "{0} месяц",
9 | "many": "{0} месяцев",
10 | "other": "{0} месяца"
11 | },
12 | "week": {
13 | "one": "{0} неделю",
14 | "many": "{0} недель",
15 | "other": "{0} недели"
16 | },
17 | "day": {
18 | "one": "{0} день",
19 | "few": "{0} дня",
20 | "other": "{0} дней"
21 | },
22 | "hour": {
23 | "one": "{0} час",
24 | "many": "{0} часов",
25 | "other": "{0} часа"
26 | },
27 | "minute": {
28 | "one": "{0} минута",
29 | "many": "{0} минут",
30 | "other": "{0} минуты"
31 | },
32 | "second": {
33 | "one": "{0} секунда",
34 | "many": "{0} секунд",
35 | "other": "{0} секунды"
36 | }
37 | }
--------------------------------------------------------------------------------
/locale-more-styles/ru/mini.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": {
3 | "many": "{0} л",
4 | "other": "{0} г"
5 | },
6 | "month": "{0} мес",
7 | "week": "{0} нед",
8 | "day": "{0} д",
9 | "hour": "{0} ч",
10 | "minute": "{0} мин",
11 | "second": "{0} с"
12 | }
--------------------------------------------------------------------------------
/locale-more-styles/ru/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "now": {
3 | "current": "сейчас",
4 | "past": "только что",
5 | "future": "сейчас"
6 | }
7 | }
--------------------------------------------------------------------------------
/locale-more-styles/ru/short-time.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": {
3 | "many": "{0} л.",
4 | "other": "{0} г."
5 | },
6 | "month": "{0} мес.",
7 | "week": "{0} нед.",
8 | "day": {
9 | "one": "{0} д.",
10 | "other": "{0} дн."
11 | },
12 | "hour": "{0} ч.",
13 | "minute": "{0} мин.",
14 | "second": "{0} сек."
15 | }
--------------------------------------------------------------------------------
/locale-more-styles/sv/long-time.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": {
3 | "one": "{0} år",
4 | "other": "{0} år"
5 | },
6 | "month": {
7 | "one": "{0} månad",
8 | "other": "{0} månader"
9 | },
10 | "week": {
11 | "one": "{0} vecka",
12 | "other": "{0} veckor"
13 | },
14 | "day": {
15 | "one": "{0} dag",
16 | "other": "{0} dagar"
17 | },
18 | "hour": {
19 | "one": "{0} timme",
20 | "other": "{0} timmar"
21 | },
22 | "minute": {
23 | "one": "{0} minut",
24 | "other": "{0} minuter"
25 | },
26 | "second": {
27 | "one": "{0} sekund",
28 | "other": "{0} sekunder"
29 | }
30 | }
--------------------------------------------------------------------------------
/locale-more-styles/sv/mini.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": "{0} år",
3 | "month": "{0} mån.",
4 | "week": "{0} v.",
5 | "day": {
6 | "one": "{0} dag",
7 | "other": "{0} dagar"
8 | },
9 | "hour": "{0} tim.",
10 | "minute": "{0} min",
11 | "second": "{0} sek."
12 | }
--------------------------------------------------------------------------------
/locale-more-styles/sv/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "now": {
3 | "current": "nu",
4 | "future": "om ett ögonblick",
5 | "past": "alldeles nyss"
6 | }
7 | }
--------------------------------------------------------------------------------
/locale-more-styles/zh/mini.json:
--------------------------------------------------------------------------------
1 | {
2 | "year": "{0}年",
3 | "month": "{0}个月",
4 | "week": "{0}周",
5 | "day": "{0}天",
6 | "hour": "{0}小时",
7 | "minute": "{0}分钟",
8 | "second": "{0}秒钟"
9 | }
--------------------------------------------------------------------------------
/locale-more-styles/zh/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "now": {
3 | "current": "现在",
4 | "past": "现在",
5 | "future": "现在"
6 | }
7 | }
--------------------------------------------------------------------------------
/project.sublime-project:
--------------------------------------------------------------------------------
1 | {
2 | "folders":
3 | [
4 | {
5 | "follow_symlinks": true,
6 | "path": ".",
7 | "folder_exclude_patterns": [".nyc_output", "bundle", "node_modules", "coverage", "modules", "commonjs", "project.sublime-workspace"]
8 | }
9 | ],
10 | "settings":
11 | {
12 | "trim_trailing_white_space_on_save": true
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/prop-types/index.cjs:
--------------------------------------------------------------------------------
1 | exports = module.exports = require('../commonjs/PropTypes.js')
2 | exports['default'] = require('../commonjs/PropTypes.js')
--------------------------------------------------------------------------------
/prop-types/index.cjs.js:
--------------------------------------------------------------------------------
1 | // This file id deprecated.
2 | // It's the same as `index.cjs`, just with an added `.js` file extension.
3 | // It only exists for compatibility with the software that doesn't like `*.cjs` file extension.
4 | // https://gitlab.com/catamphetamine/libphonenumber-js/-/issues/61#note_950728292
5 |
6 | exports = module.exports = require('../commonjs/PropTypes.js')
7 | exports['default'] = require('../commonjs/PropTypes.js')
--------------------------------------------------------------------------------
/prop-types/index.js:
--------------------------------------------------------------------------------
1 | export * from '../modules/PropTypes.js'
--------------------------------------------------------------------------------
/prop-types/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "name": "javascript-time-ago/prop-types",
4 | "main": "index.cjs",
5 | "module": "index.js",
6 | "type": "module",
7 | "exports": {
8 | ".": {
9 | "import": "./index.js",
10 | "require": "./index.cjs"
11 | }
12 | },
13 | "sideEffects": false
14 | }
--------------------------------------------------------------------------------
/rollup.config.mjs:
--------------------------------------------------------------------------------
1 | import commonjs from 'rollup-plugin-commonjs'
2 | // import json from 'rollup-plugin-json'
3 | import node from 'rollup-plugin-node-resolve'
4 | import { terser } from 'rollup-plugin-terser'
5 |
6 | export default [
7 | // (deprecated)
8 | // Old bundle: old file name (with ".min")
9 | // and exports `window['javascript-time-ago'].default` variable.
10 | {
11 | input: 'index.js',
12 | plugins: [
13 | node(),
14 | commonjs(),
15 | // json(),
16 | terser()
17 | ],
18 | output: {
19 | format: 'umd',
20 | name: 'javascript-time-ago', // Should be "TimeAgo".
21 | file: 'bundle/javascript-time-ago.min.js',
22 | sourcemap: true,
23 | // exports: 'default'
24 | }
25 | },
26 | // New bundle: new file name (without ".min")
27 | // and exports `TimeAgo` global variable.
28 | {
29 | input: 'modules/TimeAgo.js',
30 | plugins: [
31 | node(),
32 | commonjs(),
33 | // json(),
34 | terser()
35 | ],
36 | output: {
37 | format: 'umd',
38 | name: 'TimeAgo',
39 | file: 'bundle/javascript-time-ago.js',
40 | exports: 'default',
41 | sourcemap: true
42 | }
43 | }
44 | ]
--------------------------------------------------------------------------------
/source/LocaleDataStore.js:
--------------------------------------------------------------------------------
1 | // For all locales added
2 | // their relative time formatter messages will be stored here.
3 | const localesData = {}
4 |
5 | export function getLocaleData(locale) {
6 | return localesData[locale]
7 | }
8 |
9 | export function addLocaleData(localeData) {
10 | if (!localeData) {
11 | throw new Error('[javascript-time-ago] No locale data passed.')
12 | }
13 | // This locale data is stored in a global variable
14 | // and later used when calling `.format(time)`.
15 | localesData[localeData.locale] = localeData
16 | }
--------------------------------------------------------------------------------
/source/PropTypes.js:
--------------------------------------------------------------------------------
1 | // Deprecated: Moved to `react-time-ago`.
2 |
3 | import PropTypes from 'prop-types'
4 |
5 | const {
6 | oneOfType,
7 | arrayOf,
8 | string,
9 | number,
10 | shape,
11 | func
12 | } = PropTypes
13 |
14 | // The first step isn't required to define `minTime` or `test()`.
15 | const step = oneOfType([
16 | shape({
17 | minTime: number,
18 | formatAs: string.isRequired
19 | }),
20 | shape({
21 | test: func,
22 | formatAs: string.isRequired
23 | }),
24 | shape({
25 | minTime: number,
26 | format: func.isRequired
27 | }),
28 | shape({
29 | test: func,
30 | format: func.isRequired
31 | })
32 | ])
33 |
34 | // Formatting style.
35 | export const style = oneOfType([
36 | // Not using `oneOf()` here, because that way
37 | // this package wouldn't support some hypothetical
38 | // new styles added to `javascript-time-ago` in some future.
39 | string,
40 | shape({
41 | steps: arrayOf(step).isRequired,
42 | labels: oneOfType([
43 | string,
44 | arrayOf(string)
45 | ]).isRequired
46 | })
47 | ])
--------------------------------------------------------------------------------
/source/cache.js:
--------------------------------------------------------------------------------
1 | /**
2 | * A basic in-memory cache.
3 | *
4 | * import Cache from 'javascript-time-ago/Cache'
5 | * const cache = new Cache()
6 | * const object = cache.get('key1', 'key2', ...) || cache.put('key1', 'key2', ..., createObject())
7 | */
8 | export default class Cache {
9 | constructor() {
10 | this.cache = {}
11 | }
12 |
13 | get(...keys) {
14 | let cache = this.cache
15 | for (const key of keys) {
16 | if (typeof cache !== 'object') {
17 | return
18 | }
19 | cache = cache[key]
20 | }
21 | return cache
22 | }
23 |
24 | put(...keys) {
25 | const value = keys.pop()
26 | const lastKey = keys.pop()
27 | let cache = this.cache
28 | for (const key of keys) {
29 | if (typeof cache[key] !== 'object') {
30 | cache[key] = {}
31 | }
32 | cache = cache[key]
33 | }
34 | return cache[lastKey] = value
35 | }
36 | }
--------------------------------------------------------------------------------
/source/cache.test.js:
--------------------------------------------------------------------------------
1 | import Cache from './cache.js'
2 |
3 | describe('cache', () => {
4 | it('should cache', () => {
5 | const cache = new Cache()
6 |
7 | const value = {}
8 | expect(cache.get('123', '456')).to.be.undefined
9 | expect(cache.put('123', '456', value)).to.equal(value)
10 | expect(cache.get('123', '456')).to.equal(value)
11 |
12 | expect(cache.put('123', '789', 123)).to.equal(123)
13 | })
14 | })
15 |
--------------------------------------------------------------------------------
/source/isStyleObject.js:
--------------------------------------------------------------------------------
1 | export default function isStyleObject(object) {
2 | return isObject(object) && (
3 | Array.isArray(object.steps) ||
4 | // `gradation` property is deprecated: it has been renamed to `steps`.
5 | Array.isArray(object.gradation) ||
6 | // `flavour` property is deprecated: it has been renamed to `labels`.
7 | Array.isArray(object.flavour) ||
8 | typeof object.flavour === 'string' ||
9 | Array.isArray(object.labels) ||
10 | typeof object.labels === 'string' ||
11 | // `units` property is deprecated.
12 | Array.isArray(object.units) ||
13 | // `custom` property is deprecated.
14 | typeof object.custom === 'function'
15 | )
16 | }
17 |
18 | const OBJECT_CONSTRUCTOR = {}.constructor
19 | function isObject(object) {
20 | return typeof object !== undefined && object !== null && object.constructor === OBJECT_CONSTRUCTOR
21 | }
--------------------------------------------------------------------------------
/source/isStyleObject.test.js:
--------------------------------------------------------------------------------
1 | import isStyleObject from './isStyleObject.js'
2 |
3 | describe('isStyleObject', () => {
4 | it('should detect a style object', () => {
5 | isStyleObject({
6 | gradation: []
7 | }).should.equal(true)
8 | isStyleObject({
9 | steps: []
10 | }).should.equal(true)
11 | isStyleObject({
12 | flavour: 'long'
13 | }).should.equal(true)
14 | isStyleObject({
15 | flavour: ['long']
16 | }).should.equal(true)
17 | isStyleObject({
18 | labels: 'long'
19 | }).should.equal(true)
20 | isStyleObject({
21 | labels: ['long']
22 | }).should.equal(true)
23 | isStyleObject({
24 | units: ['now']
25 | }).should.equal(true)
26 | isStyleObject({
27 | future: true,
28 | round: 'floor',
29 | now: 0,
30 | getTimeToNextUpdate: true
31 | }).should.equal(false)
32 | isStyleObject('round').should.equal(false)
33 | })
34 | })
--------------------------------------------------------------------------------
/source/locale.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Chooses the most appropriate locale
3 | * (one of the registered ones)
4 | * based on the list of preferred `locales` supplied by the user.
5 | *
6 | * @param {string[]} locales - the list of preferable locales (in [IETF format](https://en.wikipedia.org/wiki/IETF_language_tag)).
7 | * @param {Function} isLocaleDataAvailable - tests if a locale is available.
8 | *
9 | * @returns {string} The most suitable locale.
10 | *
11 | * @example
12 | * // Returns 'en'
13 | * chooseLocale(['en-US'], undefined, (locale) => locale === 'ru' || locale === 'en')
14 | */
15 | export default function chooseLocale(locales, isLocaleDataAvailable) {
16 | // This is not an intelligent algorithm,
17 | // but it will do for this library's case.
18 | // `sr-Cyrl-BA` -> `sr-Cyrl` -> `sr`.
19 | for (let locale of locales) {
20 | if (isLocaleDataAvailable(locale)) {
21 | return locale
22 | }
23 | const parts = locale.split('-')
24 | while (parts.length > 1) {
25 | parts.pop()
26 | locale = parts.join('-')
27 | if (isLocaleDataAvailable(locale)) {
28 | return locale
29 | }
30 | }
31 | }
32 |
33 | throw new Error(`No locale data has been registered for any of the locales: ${locales.join(', ')}`)
34 | }
35 |
36 | /**
37 | * Whether can use `Intl.DateTimeFormat` for these `locales`.
38 | * Returns the first suitable one.
39 | * @param {(string|string[])} locales
40 | * @return {?string} The first locale that can be used.
41 | */
42 | export function intlDateTimeFormatSupportedLocale(locales) {
43 | /* istanbul ignore else */
44 | if (intlDateTimeFormatSupported()) {
45 | return Intl.DateTimeFormat.supportedLocalesOf(locales)[0]
46 | }
47 | }
48 |
49 | /**
50 | * Whether can use `Intl.DateTimeFormat`.
51 | * @return {boolean}
52 | */
53 | export function intlDateTimeFormatSupported() {
54 | // Babel transforms `typeof` into some "branches"
55 | // so istanbul will show this as "branch not covered".
56 | /* istanbul ignore next */
57 | const isIntlAvailable = typeof Intl === 'object'
58 | return isIntlAvailable && typeof Intl.DateTimeFormat === 'function'
59 | }
60 |
--------------------------------------------------------------------------------
/source/locale.test.js:
--------------------------------------------------------------------------------
1 | import chooseLocale, { intlDateTimeFormatSupportedLocale } from './locale.js'
2 |
3 | describe('locale', () => {
4 | it(`should tell if can use Intl for date formatting`, () => {
5 | intlDateTimeFormatSupportedLocale('en').should.equal('en')
6 | intlDateTimeFormatSupportedLocale('en-XX').should.equal('en-XX')
7 | intlDateTimeFormatSupportedLocale(['en', 'ru']).should.equal('en')
8 | })
9 |
10 | it(`should choose the most appropriate locale`, () => {
11 | function arrayToObject(array) {
12 | return array.reduce((object, locale) => {
13 | object[locale] = true
14 | return object
15 | }, {})
16 | }
17 |
18 | function choose(locale, locales, defaultLocale = 'en') {
19 | if (typeof locale === 'string') {
20 | locale = [locale]
21 | }
22 | locale = locale.concat(defaultLocale)
23 | return chooseLocale(locale, _ => locales.includes(_))
24 | }
25 |
26 | choose('ru-RU', ['en', 'ru']).should.equal('ru')
27 | choose('en-GB', ['en', 'ru']).should.equal('en')
28 | choose('fr-FR', ['en', 'ru']).should.equal('en')
29 | choose(['fr-FR', 'de-DE'], ['en', 'ru']).should.equal('en')
30 | choose(['fr-FR', 'de-DE'], ['en', 'de']).should.equal('de')
31 | choose(['fr-FR', 'de-DE'], ['en', 'de', 'fr']).should.equal('fr')
32 | choose('fr-FR', ['en', 'fr-FR']).should.equal('fr-FR')
33 |
34 | expect(() => choose('fr-FR', ['de', 'ru'])).to.throw(
35 | 'No locale data has been registered for any of the locales: fr-FR'
36 | )
37 | })
38 | })
--------------------------------------------------------------------------------
/source/round.js:
--------------------------------------------------------------------------------
1 | export function getRoundFunction(round) {
2 | switch (round) {
3 | case 'floor':
4 | return Math.floor
5 | default:
6 | return Math.round
7 | }
8 | }
9 |
10 | // For non-negative numbers.
11 | export function getDiffRatioToNextRoundedNumber(round) {
12 | switch (round) {
13 | case 'floor':
14 | // Math.floor(x) = x
15 | // Math.floor(x + 1) = x + 1
16 | return 1
17 | default:
18 | // Math.round(x) = x
19 | // Math.round(x + 0.5) = x + 1
20 | return 0.5
21 | }
22 | }
--------------------------------------------------------------------------------
/source/steps/approximate.js:
--------------------------------------------------------------------------------
1 | import { minute, hour, day, week, month, year } from './units.js'
2 |
3 | // "factor" is a legacy property.
4 | // Developers shouldn't need to use it in their custom steps.
5 |
6 | // "threshold" is a legacy name of "min".
7 | // Developers should use "min" property name instead of "threshold".
8 |
9 | // "threshold_for_idOrUnit: value" is a legacy way of specifying "min: { id: value }".
10 | // Developers should use "min" property instead of "threshold".
11 |
12 | // just now
13 | // 1 minute ago
14 | // 2 minutes ago
15 | // 5 minutes ago
16 | // 10 minutes ago
17 | // 15 minutes ago
18 | // 20 minutes ago
19 | // …
20 | // 50 minutes ago
21 | // an hour ago
22 | // 2 hours ago
23 | // …
24 | // 20 hours ago
25 | // a day ago
26 | // 2 days ago
27 | // 5 days ago
28 | // a week ago
29 | // 2 weeks ago
30 | // 3 weeks ago
31 | // a month ago
32 | // 2 months ago
33 | // 4 months ago
34 | // a year ago
35 | // 2 years ago
36 | // …
37 | export default [
38 | {
39 | // This step returns the amount of seconds
40 | // by dividing the amount of seconds by `1`.
41 | factor: 1,
42 | // "now" labels are used for formatting the output.
43 | unit: 'now'
44 | },
45 | {
46 | // When the language doesn't support `now` unit,
47 | // the first step is ignored, and it uses this `second` unit.
48 | threshold: 1,
49 | // `threshold_for_now` should be the same as `threshold` on minutes.
50 | threshold_for_now: 45.5,
51 | // This step returns the amount of seconds
52 | // by dividing the amount of seconds by `1`.
53 | factor: 1,
54 | // "second" labels are used for formatting the output.
55 | unit: 'second'
56 | },
57 | {
58 | // `threshold` should be the same as `threshold_for_now` on seconds.
59 | threshold: 45.5,
60 | // Return the amount of minutes by dividing the amount
61 | // of seconds by the amount of seconds in a minute.
62 | factor: minute,
63 | // "minute" labels are used for formatting the output.
64 | unit: 'minute'
65 | },
66 | {
67 | // This step is effective starting from 2.5 minutes.
68 | threshold: 2.5 * minute,
69 | // Allow only 5-minute increments of minutes starting from 2.5 minutes.
70 | // `granularity` — (advanced) Time interval value "granularity".
71 | // For example, it could be set to `5` for minutes to allow only 5-minute increments
72 | // when formatting time intervals: `0 minutes`, `5 minutes`, `10 minutes`, etc.
73 | // Perhaps this feature will be removed because there seem to be no use cases
74 | // of it in the real world.
75 | granularity: 5,
76 | // Return the amount of minutes by dividing the amount
77 | // of seconds by the amount of seconds in a minute.
78 | factor: minute,
79 | // "minute" labels are used for formatting the output.
80 | unit: 'minute'
81 | },
82 | {
83 | // This step is effective starting from 22.5 minutes.
84 | threshold: 22.5 * minute,
85 | // Return the amount of minutes by dividing the amount
86 | // of seconds by the amount of seconds in half-an-hour.
87 | factor: 0.5 * hour,
88 | // "half-hour" labels are used for formatting the output.
89 | // (if available, which is no longer the case)
90 | unit: 'half-hour'
91 | },
92 | {
93 | // This step is effective starting from 42.5 minutes.
94 | threshold: 42.5 * minute,
95 | threshold_for_minute: 52.5 * minute,
96 | // Return the amount of minutes by dividing the amount
97 | // of seconds by the amount of seconds in an hour.
98 | factor: hour,
99 | // "hour" labels are used for formatting the output.
100 | unit: 'hour'
101 | },
102 | {
103 | // This step is effective starting from 20.5 hours.
104 | threshold: (20.5 / 24) * day,
105 | // Return the amount of minutes by dividing the amount
106 | // of seconds by the amount of seconds in a day.
107 | factor: day,
108 | // "day" labels are used for formatting the output.
109 | unit: 'day'
110 | },
111 | {
112 | // This step is effective starting from 5.5 days.
113 | threshold: 5.5 * day,
114 | // Return the amount of minutes by dividing the amount
115 | // of seconds by the amount of seconds in a week.
116 | factor: week,
117 | // "week" labels are used for formatting the output.
118 | unit: 'week'
119 | },
120 | {
121 | // This step is effective starting from 3.5 weeks.
122 | threshold: 3.5 * week,
123 | // Return the amount of minutes by dividing the amount
124 | // of seconds by the amount of seconds in a month.
125 | factor: month,
126 | // "month" labels are used for formatting the output.
127 | unit: 'month'
128 | },
129 | {
130 | // This step is effective starting from 10.5 months.
131 | threshold: 10.5 * month,
132 | // Return the amount of minutes by dividing the amount
133 | // of seconds by the amount of seconds in a year.
134 | factor: year,
135 | // "year" labels are used for formatting the output.
136 | unit: 'year'
137 | }
138 | ]
--------------------------------------------------------------------------------
/source/steps/approximate.test.js:
--------------------------------------------------------------------------------
1 | import getStep from './getStep.js'
2 | import steps from './approximate.js'
3 |
4 | describe('steps/approximate', () => {
5 | it('should get step correctly', () => {
6 | const getStepFor = (secondsPassed) => getStep(steps, secondsPassed, {
7 | now: 0,
8 | units: [
9 | 'now',
10 | 'second',
11 | 'minute',
12 | 'hour',
13 | 'day',
14 | 'week',
15 | 'month',
16 | 'year'
17 | ]
18 | })
19 |
20 | expect(getStepFor(0).unit).to.equal('now')
21 | expect(getStepFor(1).unit).to.equal('now')
22 | expect(getStepFor(45).unit).to.equal('now')
23 |
24 | expect(getStepFor(46).unit).to.equal('minute')
25 | expect(getStepFor(46).factor).to.equal(60)
26 | expect(getStepFor(46).granularity).to.be.undefined
27 |
28 | expect(getStepFor(2.5 * 60 - 1).unit).to.equal('minute')
29 | expect(getStepFor(2.5 * 60 - 1).factor).to.equal(60)
30 | expect(getStepFor(2.5 * 60 - 1).granularity).to.be.undefined
31 |
32 | expect(getStepFor(2.5 * 60).unit).to.equal('minute')
33 | expect(getStepFor(2.5 * 60).factor).to.equal(60)
34 | expect(getStepFor(2.5 * 60).granularity).to.equal(5)
35 |
36 | expect(getStepFor(52.5 * 60 - 1).unit).to.equal('minute')
37 | expect(getStepFor(52.5 * 60 - 1).factor).to.equal(60)
38 | expect(getStepFor(52.5 * 60 - 1).granularity).to.equal(5)
39 |
40 | expect(getStepFor(52.5 * 60).unit).to.equal('hour')
41 | expect(getStepFor(52.5 * 60).factor).to.equal(60 * 60)
42 | })
43 |
44 | it('should get step correctly ("now" unit not allowed)', () => {
45 | const getStepFor = (secondsPassed) => getStep(steps, secondsPassed, {
46 | now: 0,
47 | units: [
48 | 'second',
49 | 'minute',
50 | 'hour',
51 | 'day',
52 | 'week',
53 | 'month',
54 | 'year'
55 | ]
56 | })
57 |
58 | expect(getStepFor(0)).to.be.undefined
59 | expect(getStepFor(1).unit).to.equal('second')
60 | expect(getStepFor(45).unit).to.equal('second')
61 | expect(getStepFor(46).unit).to.equal('minute')
62 | })
63 | })
--------------------------------------------------------------------------------
/source/steps/getStep.js:
--------------------------------------------------------------------------------
1 | import getStepDenominator from './getStepDenominator.js'
2 | import getStepMinTime from './getStepMinTime.js'
3 | import { getRoundFunction } from '../round.js'
4 |
5 | /**
6 | * Finds an appropriate `step` of `steps` for the time interval (in seconds).
7 | *
8 | * @param {Object[]} steps - Time formatting steps.
9 | *
10 | * @param {number} secondsPassed - Time interval (in seconds).
11 | * `< 0` for past dates and `> 0` for future dates.
12 | *
13 | * @param {number} options.now - Current timestamp.
14 | *
15 | * @param {boolean} [options.future] - Whether the date should be formatted as a future one
16 | * instead of a past one.
17 | *
18 | * @param {string} [options.round] - (undocumented) Rounding mechanism.
19 | *
20 | * @param {string[]} [options.units] - A list of allowed time units.
21 | * (Example: ['second', 'minute', 'hour', …])
22 | *
23 | * @param {boolean} [options.getNextStep] - Pass true to return `[step, nextStep]` instead of just `step`.
24 | *
25 | * @return {Object|Object[]} [step] — Either a `step` or `[prevStep, step, nextStep]`.
26 | */
27 | export default function getStep(steps, secondsPassed, { now, future, round, units, getNextStep }) {
28 | // Ignore steps having not-supported time units in `formatAs`.
29 | steps = filterStepsByUnits(steps, units)
30 | const step = _getStep(steps, secondsPassed, { now, future, round })
31 | if (getNextStep) {
32 | if (step) {
33 | const prevStep = steps[steps.indexOf(step) - 1]
34 | const nextStep = steps[steps.indexOf(step) + 1]
35 | return [prevStep, step, nextStep]
36 | }
37 | return [undefined, undefined, steps[0]]
38 | }
39 | return step
40 | }
41 |
42 | function _getStep(steps, secondsPassed, { now, future, round }) {
43 | // If no steps fit the conditions then return nothing.
44 | if (steps.length === 0) {
45 | return
46 | }
47 |
48 | // Find the most appropriate step.
49 | const i = getStepIndex(steps, secondsPassed, {
50 | now,
51 | future: future || secondsPassed < 0,
52 | round
53 | })
54 |
55 | // If no step is applicable the return nothing.
56 | if (i === -1) {
57 | return
58 | }
59 |
60 | const step = steps[i]
61 |
62 | // Apply granularity to the time amount
63 | // (and fall back to the previous step
64 | // if the first level of granularity
65 | // isn't met by this amount)
66 | if (step.granularity) {
67 | // Recalculate the amount of seconds passed based on `granularity`.
68 | const secondsPassedGranular = getRoundFunction(round)((Math.abs(secondsPassed) / getStepDenominator(step)) / step.granularity) * step.granularity
69 | // If the granularity for this step is too high,
70 | // then fall back to the previous step.
71 | // (if there is any previous step)
72 | if (secondsPassedGranular === 0 && i > 0) {
73 | return steps[i - 1]
74 | }
75 | }
76 |
77 | return step
78 | }
79 |
80 | /**
81 | * Iterates through steps until it finds the maximum one satisfying the `minTime` threshold.
82 | * @param {Object} steps - Steps.
83 | * @param {number} secondsPassed - How much seconds have passed since the date till `now`.
84 | * @param {number} options.now - Current timestamp.
85 | * @param {boolean} options.future - Whether the time interval should be formatted as a future one.
86 | * @param {number} [i] - Gradation step currently being tested.
87 | * @return {number} Gradation step index.
88 | */
89 | function getStepIndex(steps, secondsPassed, options, i = 0) {
90 | const minTime = getStepMinTime(steps[i], {
91 | prevStep: steps[i - 1],
92 | timestamp: options.now - secondsPassed * 1000,
93 | ...options
94 | })
95 | // If `minTime` isn't defined or deduceable for this step, then stop.
96 | if (minTime === undefined) {
97 | return i - 1
98 | }
99 | // If the `minTime` threshold for moving from previous step
100 | // to this step is too high then return the previous step.
101 | if (Math.abs(secondsPassed) < minTime) {
102 | return i - 1
103 | }
104 | // If it's the last step then return it.
105 | if (i === steps.length - 1) {
106 | return i
107 | }
108 | // Move to the next step.
109 | return getStepIndex(steps, secondsPassed, options, i + 1)
110 | }
111 |
112 | /**
113 | * Leaves only allowed steps.
114 | * @param {Object[]} steps
115 | * @param {string[]} units - Allowed time units.
116 | * @return {Object[]}
117 | */
118 | function filterStepsByUnits(steps, units) {
119 | return steps.filter(({ unit, formatAs }) => {
120 | // "unit" is now called "formatAs".
121 | unit = unit || formatAs
122 | // If this step has a `unit` defined
123 | // then this `unit` must be in the list of allowed `units`.
124 | if (unit) {
125 | return units.indexOf(unit) >= 0
126 | }
127 | // A step is not required to specify a `unit`:
128 | // alternatively, it could specify `format()`.
129 | // (see "twitter" style for an example)
130 | return true
131 | })
132 | }
--------------------------------------------------------------------------------
/source/steps/getStep.test.js:
--------------------------------------------------------------------------------
1 | import getStep from './getStep.js'
2 | import round from './round.js'
3 |
4 | describe('getStep', () => {
5 | it('should return nothing if no time units are supported', () => {
6 | expect(getStep(round, 0, { units: ['femtosecond'] })).to.be.undefined
7 | })
8 |
9 | // it('should throw if a non-first step does not have a `minTime` or `test()`', () => {
10 | // expect(getStep([{ unit: 'second' }], 2, { units: ['second'] })).to.deep.equal({ unit: 'second' })
11 | //
12 | // expect(() => {
13 | // getStep([{ unit: 'second' }, { unit: 'minute' }], 2, { units: ['second', 'minute'] })
14 | // }).to.throw(
15 | // 'Each step must define either `minTime` or `test()`, except for the first one. Got "undefined", undefined. Step: {"unit":"minute"}'
16 | // )
17 | // })
18 |
19 | it('should fall back to previous step if granularity is too high for the next step', () => {
20 | const steps = round.slice()
21 |
22 | steps[1].formatAs.should.equal('second')
23 | steps[1].granularity = 3
24 |
25 | getStep(steps, 1.49, { now: 0, units: ['now', 'second'] }).formatAs.should.equal('now')
26 |
27 | // And if there's no previous step, then use the current one.
28 |
29 | const firstStep = steps[0]
30 | steps.splice(0, 1)
31 |
32 | getStep(steps, 1.49, { now: 0, units: ['now', 'second'] }).formatAs.should.equal('second')
33 |
34 | steps.unshift(firstStep)
35 |
36 | delete steps[1].granularity
37 | })
38 |
39 | it('should support `minTime` object', () => {
40 | expect(getStep(
41 | [
42 | { unit: 'second' },
43 | {
44 | minTime: { default: 10 },
45 | unit: 'minute'
46 | }
47 | ],
48 | 5,
49 | { now: 0, units: ['second', 'minute'] }
50 | ).unit).to.equal('second')
51 |
52 | expect(getStep(
53 | [
54 | { unit: 'second' },
55 | {
56 | minTime: { default: 10 },
57 | unit: 'minute'
58 | }
59 | ],
60 | 10,
61 | { now: 0, units: ['second', 'minute'] }
62 | ).unit).to.equal('minute')
63 |
64 | expect(getStep(
65 | [
66 | {
67 | id: 'seconds',
68 | unit: 'second'
69 | },
70 | {
71 | minTime: {
72 | seconds: 20,
73 | default: 10
74 | },
75 | unit: 'minute'
76 | }
77 | ],
78 | 10,
79 | { now: 0, units: ['second', 'minute'] }
80 | ).unit).to.equal('second')
81 | })
82 |
83 | it('should support legacy `threshold()` function', () => {
84 | expect(getStep(
85 | [
86 | { unit: 'second' },
87 | {
88 | threshold: () => 10,
89 | unit: 'minute'
90 | }
91 | ],
92 | 5,
93 | { now: 0, units: ['second', 'minute'] }
94 | ).unit).to.equal('second')
95 |
96 | expect(getStep(
97 | [
98 | { unit: 'second' },
99 | {
100 | threshold: () => 10,
101 | unit: 'minute'
102 | }
103 | ],
104 | 10,
105 | { now: 0, units: ['second', 'minute'] }
106 | ).unit).to.equal('minute')
107 | })
108 |
109 | it('should stop when reaching a step that has no "minTime" and for which "minTime" could not be deduced', () => {
110 | expect(getStep(
111 | [
112 | { formatAs: 'second' },
113 | { formatAs: 'unsupported-time-unit' }
114 | ],
115 | 10,
116 | { now: 0, units: ['second', 'unsupported-time-unit'] }
117 | ).formatAs).to.equal('second')
118 | })
119 | })
--------------------------------------------------------------------------------
/source/steps/getStepDenominator.js:
--------------------------------------------------------------------------------
1 | import { getSecondsInUnit } from './units.js'
2 |
3 | export default function getStepDenominator(step) {
4 | // `factor` is a legacy property.
5 | if (step.factor !== undefined) {
6 | return step.factor
7 | }
8 | // "unit" is now called "formatAs".
9 | return getSecondsInUnit(step.unit || step.formatAs) || 1
10 | }
--------------------------------------------------------------------------------
/source/steps/getStepDenominator.test.js:
--------------------------------------------------------------------------------
1 | import getStepDenominator from './getStepDenominator.js'
2 |
3 | describe('getStepDenominator', () => {
4 | it('should support the older "unit" name', () => {
5 | getStepDenominator({ unit: 'minute' }).should.equal(60)
6 | })
7 |
8 | it('should return 1 as a default "denominator"', () => {
9 | getStepDenominator({ formatAs: 'exotic' }).should.equal(1)
10 | })
11 | })
--------------------------------------------------------------------------------
/source/steps/getStepMinTime.js:
--------------------------------------------------------------------------------
1 | import { getSecondsInUnit } from './units.js'
2 | import { getDiffRatioToNextRoundedNumber } from '../round.js'
3 |
4 | export default function getStepMinTime(step, {
5 | prevStep,
6 | timestamp,
7 | // `now` argument is used in a deprecated `step.test()` function.
8 | now,
9 | future,
10 | round
11 | }) {
12 | let minTime
13 | // "threshold_for_xxx" is a legacy property.
14 | if (prevStep) {
15 | if (prevStep.id || prevStep.unit) {
16 | minTime = step[`threshold_for_${prevStep.id || prevStep.unit}`]
17 | }
18 | }
19 | if (minTime === undefined) {
20 | // "threshold" is a legacy property.
21 | if (step.threshold !== undefined) {
22 | // "threshold" is a legacy name for "minTime".
23 | minTime = step.threshold
24 | // "threshold" function is deprecated.
25 | if (typeof minTime === 'function') {
26 | minTime = minTime(now, future)
27 | }
28 | }
29 | }
30 | if (minTime === undefined) {
31 | minTime = step.minTime
32 | }
33 | // A deprecated way of specifying a different threshold
34 | // depending on the previous step's unit.
35 | if (typeof minTime === 'object') {
36 | if (prevStep && prevStep.id && minTime[prevStep.id] !== undefined) {
37 | minTime = minTime[prevStep.id]
38 | } else {
39 | minTime = minTime.default
40 | }
41 | }
42 | if (typeof minTime === 'function') {
43 | minTime = minTime(timestamp, {
44 | future,
45 | getMinTimeForUnit(toUnit, fromUnit) {
46 | return getMinTimeForUnit(
47 | toUnit,
48 | fromUnit || prevStep && prevStep.formatAs,
49 | { round }
50 | )
51 | }
52 | })
53 | }
54 | // Evaluate the `test()` function.
55 | // `test()` function is deprecated.
56 | if (minTime === undefined) {
57 | if (step.test) {
58 | if (step.test(timestamp, {
59 | now,
60 | future
61 | })) {
62 | // `0` threshold always passes.
63 | minTime = 0
64 | } else {
65 | // `MAX_SAFE_INTEGER` threshold won't ever pass in real life.
66 | minTime = 9007199254740991 // Number.MAX_SAFE_INTEGER
67 | }
68 | }
69 | }
70 | if (minTime === undefined) {
71 | if (prevStep) {
72 | if (step.formatAs && prevStep.formatAs) {
73 | minTime = getMinTimeForUnit(step.formatAs, prevStep.formatAs, { round })
74 | }
75 | } else {
76 | // The first step's `minTime` is `0` by default.
77 | minTime = 0
78 | }
79 | }
80 | // Warn if no `minTime` was defined or could be deduced.
81 | if (minTime === undefined) {
82 | console.warn('[javascript-time-ago] A step should specify `minTime`:\n' + JSON.stringify(step, null, 2))
83 | }
84 | return minTime
85 | }
86 |
87 | function getMinTimeForUnit(toUnit, fromUnit, { round }) {
88 | const toUnitAmount = getSecondsInUnit(toUnit)
89 | // if (!fromUnit) {
90 | // return toUnitAmount;
91 | // }
92 | // if (!fromUnit) {
93 | // fromUnit = getPreviousUnitFor(toUnit)
94 | // }
95 | let fromUnitAmount
96 | if (fromUnit === 'now') {
97 | fromUnitAmount = getSecondsInUnit(toUnit)
98 | } else {
99 | fromUnitAmount = getSecondsInUnit(fromUnit)
100 | }
101 | if (toUnitAmount !== undefined && fromUnitAmount !== undefined) {
102 | return toUnitAmount - fromUnitAmount * (1 - getDiffRatioToNextRoundedNumber(round))
103 | }
104 | }
--------------------------------------------------------------------------------
/source/steps/getStepMinTime.test.js:
--------------------------------------------------------------------------------
1 | import getStepMinTime from './getStepMinTime.js'
2 |
3 | describe('getStepMinTime', () => {
4 | it('should support `step.test()` function (returns true)', () => {
5 | getStepMinTime({
6 | test: () => true
7 | }, {
8 | prevStep: { minTime: 1 }
9 | }).should.equal(0)
10 | })
11 |
12 | it('should support `step.test()` function (returns false)', () => {
13 | getStepMinTime({
14 | test: () => false
15 | }, {
16 | prevStep: { minTime: 1 }
17 | }).should.equal(9007199254740991)
18 | })
19 | })
--------------------------------------------------------------------------------
/source/steps/getTimeToNextUpdate.js:
--------------------------------------------------------------------------------
1 | import _getTimeToNextUpdateForUnit from './getTimeToNextUpdateForUnit.js'
2 | import getStepMinTime from './getStepMinTime.js'
3 | import { getRoundFunction } from '../round.js'
4 |
5 | // A thousand years is practically a metaphor for "infinity".
6 | const YEAR = 365 * 24 * 60 * 60 * 1000
7 | export const INFINITY = 1000 * YEAR
8 |
9 | /**
10 | * Gets the time to next update for a date and a step.
11 | * @param {number} date — The date passed to `.format()`, converted to a timestamp.
12 | * @param {object} step
13 | * @param {object} [options.previousStep]
14 | * @param {object} [options.nextStep]
15 | * @param {number} options.now
16 | * @param {boolean} options.future
17 | * @param {string} [options.round] - (undocumented) Rounding mechanism.
18 | * @return {number} [timeToNextUpdate]
19 | */
20 | export default function getTimeToNextUpdate(date, step, { prevStep, nextStep, now, future, round }) {
21 | const timestamp = date.getTime ? date.getTime() : date
22 |
23 | const getTimeToNextUpdateForUnit = (unit) => _getTimeToNextUpdateForUnit(unit, timestamp, { now, round })
24 |
25 | // For future dates, steps move from the last one to the first one,
26 | // while for past dates, steps move from the first one to the last one,
27 | // due to the fact that time flows in one direction,
28 | // and future dates' interval naturally becomes smaller
29 | // while past dates' interval naturally grows larger.
30 | //
31 | // For future dates, it's the transition
32 | // from the current step to the previous step,
33 | // therefore check the `minTime` of the current step.
34 | //
35 | // For past dates, it's the transition
36 | // from the current step to the next step,
37 | // therefore check the `minTime` of the next step.
38 | //
39 | const timeToStepChange = getTimeToStepChange(future ? step : nextStep, timestamp, {
40 | future,
41 | now,
42 | round,
43 | prevStep: future ? prevStep : step,
44 | // isFirstStep: future && isFirstStep
45 | })
46 |
47 | if (timeToStepChange === undefined) {
48 | // Can't reliably determine "time to next update"
49 | // if not all of the steps provide `minTime`.
50 | return
51 | }
52 |
53 | let timeToNextUpdate
54 |
55 | if (step) {
56 | if (step.getTimeToNextUpdate) {
57 | timeToNextUpdate = step.getTimeToNextUpdate(timestamp, {
58 | getTimeToNextUpdateForUnit,
59 | getRoundFunction,
60 | now,
61 | future,
62 | round
63 | })
64 | }
65 |
66 | if (timeToNextUpdate === undefined) {
67 | // "unit" is now called "formatAs".
68 | const unit = step.unit || step.formatAs
69 | if (unit) {
70 | // For some units, like "now", there's no defined amount of seconds in them.
71 | // In such cases, `getTimeToNextUpdateForUnit()` returns `undefined`,
72 | // and the next step's `minTime` could be used to calculate the update interval:
73 | // it will just assume that the label never changes for this step.
74 | timeToNextUpdate = getTimeToNextUpdateForUnit(unit)
75 | }
76 | }
77 | }
78 |
79 | if (timeToNextUpdate === undefined) {
80 | return timeToStepChange
81 | }
82 |
83 | return Math.min(timeToNextUpdate, timeToStepChange)
84 | }
85 |
86 | export function getStepChangesAt(currentOrNextStep, timestamp, { now, future, round, prevStep }) {
87 | // The first step's `minTime` is `0` by default.
88 | // It doesn't "change" steps at zero point
89 | // but it does change the wording when switching
90 | // from "future" to "past": "in ..." -> "... ago".
91 | // Therefore, the label should be updated at zero-point too.
92 | const minTime = getStepMinTime(currentOrNextStep, { timestamp, now, future, round, prevStep })
93 | if (minTime === undefined) {
94 | return
95 | }
96 | if (future) {
97 | // The step changes to the previous step
98 | // as soon as `timestamp - now` becomes
99 | // less than the `minTime` of the current step:
100 | // `timestamp - now === minTime - 1`
101 | // => `now === timestamp - minTime + 1`.
102 | return timestamp - minTime * 1000 + 1
103 | } else {
104 | // The step changes to the next step
105 | // as soon as `now - timestamp` becomes
106 | // equal to `minTime` of the next step:
107 | // `now - timestamp === minTime`
108 | // => `now === timestamp + minTime`.
109 |
110 | // This is a special case when double-update could be skipped.
111 | if (minTime === 0 && timestamp === now) {
112 | return INFINITY
113 | }
114 |
115 | return timestamp + minTime * 1000
116 | }
117 | }
118 |
119 | export function getTimeToStepChange(step, timestamp, {
120 | now,
121 | future,
122 | round,
123 | prevStep
124 | }) {
125 | if (step) {
126 | const stepChangesAt = getStepChangesAt(step, timestamp, {
127 | now,
128 | future,
129 | round,
130 | prevStep
131 | })
132 | if (stepChangesAt === undefined) {
133 | return
134 | }
135 | return stepChangesAt - now
136 | } else {
137 | if (future) {
138 | // No step.
139 | // Update right after zero point, when it changes from "future" to "past".
140 | return timestamp - now + 1
141 | } else {
142 | // The last step doesn't ever change when `date` is in the past.
143 | return INFINITY
144 | }
145 | }
146 | }
--------------------------------------------------------------------------------
/source/steps/getTimeToNextUpdate.test.js:
--------------------------------------------------------------------------------
1 | import getTimeToNextUpdate, { INFINITY, getStepChangesAt, getTimeToStepChange } from './getTimeToNextUpdate.js'
2 |
3 | describe('getTimeToNextUpdate', () => {
4 | it('should return infinity when there are no more steps, and it does not format as a unit (past)', () => {
5 | expect(getTimeToNextUpdate(-4 * 60 * 1000, {
6 | minTime: 59.5,
7 | format: () => ''
8 | }, {
9 | now: 0,
10 | future: false,
11 | isFirstStep: true
12 | })).to.equal(INFINITY)
13 | })
14 |
15 | it('should support date argument', () => {
16 | expect(getTimeToNextUpdate(new Date(4 * 60 * 1000), {
17 | minTime: 60
18 | }, {
19 | now: 0,
20 | future: true,
21 | isFirstStep: true,
22 | nextStep: {}
23 | })).to.equal(3 * 60 * 1000 + 1)
24 | })
25 |
26 | it('should return this step\'s "minTime" timestamp (future)', () => {
27 | expect(getTimeToNextUpdate(4 * 60 * 1000, {
28 | minTime: 60,
29 | format: () => ''
30 | }, {
31 | now: 0,
32 | future: true,
33 | isFirstStep: true,
34 | nextStep: {
35 | format: () => ''
36 | }
37 | })).to.equal(3 * 60 * 1000 + 1)
38 | })
39 |
40 | it('should return undefined when there is a next step and time to next update can not be reliably determined (formatAs) (past)', () => {
41 | expect(getTimeToNextUpdate(-4 * 60 * 1000, {
42 | minTime: 60,
43 | formatAs: 'minute'
44 | }, {
45 | now: 0,
46 | future: false,
47 | isFirstStep: true,
48 | nextStep: {
49 | formatAs: 'unknown-time-unit'
50 | }
51 | })).to.be.undefined
52 | })
53 |
54 | it('should get time to next update (no next step) (past)', () => {
55 | getTimeToNextUpdate(-4 * 60 * 1000, {
56 | formatAs: 'minute',
57 | minTime: 59.5
58 | }, {
59 | now: 0,
60 | future: false,
61 | isFirstStep: true
62 | }).should.equal(0.5 * 60 * 1000)
63 | })
64 |
65 | it('should get time to next update (no next step) (future)', () => {
66 | getTimeToNextUpdate(4 * 60 * 1000, {
67 | formatAs: 'minute',
68 | minTime: 59.5
69 | }, {
70 | now: 0,
71 | future: true,
72 | isFirstStep: true
73 | }).should.equal(0.5 * 60 * 1000 + 1)
74 | })
75 |
76 | it('should get time to next update (has prev/next step without `minTime`) (future)', () => {
77 | getTimeToNextUpdate(4 * 60 * 1000, {
78 | formatAs: 'minute',
79 | minTime: 59.5
80 | }, {
81 | now: 0,
82 | future: true,
83 | isFirstStep: true,
84 | nextStep: {
85 | formatAs: 'hour',
86 | test: () => false
87 | }
88 | }).should.equal(0.5 * 60 * 1000 + 1)
89 | })
90 |
91 | it('should get time to next update (has `getTimeToNextUpdate`) (past)', () => {
92 | getTimeToNextUpdate(-4 * 60 * 1000, {
93 | formatAs: 'minute',
94 | minTime: 59.5,
95 | getTimeToNextUpdate: () => 0.25 * 60 * 1000
96 | }, {
97 | now: 0,
98 | future: false,
99 | isFirstStep: true
100 | }).should.equal(0.25 * 60 * 1000)
101 | })
102 |
103 | it('should get time to next update (has `getTimeToNextUpdate`) (future)', () => {
104 | getTimeToNextUpdate(4 * 60 * 1000, {
105 | formatAs: 'minute',
106 | minTime: 59.5,
107 | getTimeToNextUpdate: () => 0.25 * 60 * 1000
108 | }, {
109 | now: 0,
110 | future: true,
111 | isFirstStep: true
112 | }).should.equal(0.25 * 60 * 1000)
113 | })
114 |
115 | it('should get time to next update (has both unit and prev/next steps with `minTime`) (returns time to "minTime" of next step) (past)', () => {
116 | getTimeToNextUpdate(-59 * 60 * 1000, {
117 | formatAs: 'minute',
118 | minTime: 59.5
119 | }, {
120 | now: 0,
121 | future: false,
122 | isFirstStep: true,
123 | nextStep: {
124 | formatAs: 'hour',
125 | minTime: 59.5 * 60
126 | }
127 | }).should.equal(0.5 * 60 * 1000)
128 | })
129 |
130 | it('should get time to next update (has no unit but has prev/next step with `minTime`) (returns time to "minTime" of next step) (past)', () => {
131 | getTimeToNextUpdate(-59 * 60 * 1000, {
132 | format: () => {},
133 | minTime: 59.5
134 | }, {
135 | now: 0,
136 | future: false,
137 | isFirstStep: true,
138 | nextStep: {
139 | formatAs: 'hour',
140 | minTime: 59.5 * 60
141 | }
142 | }).should.equal(0.5 * 60 * 1000)
143 | })
144 |
145 | it('should get time to next update (will be outside of the first step) (future)', () => {
146 | getTimeToNextUpdate(60 * 60 * 1000, {
147 | formatAs: 'hour',
148 | minTime: 60 * 60
149 | }, {
150 | now: 0,
151 | future: true,
152 | isFirstStep: true
153 | }).should.equal(1)
154 | })
155 | })
156 |
157 | describe('getStepChangesAt', () => {
158 | it('should work for "round" steps', () => {
159 | // Future.
160 | // Is at zero point.
161 | // No next step.
162 | // No tickable unit.
163 | // Doesn't update.
164 | getStepChangesAt({
165 | unit: 'now'
166 | }, 0, {
167 | now: 0,
168 | future: false,
169 | prevStep: undefined
170 | }).should.equal(INFINITY)
171 |
172 | // Past.
173 | // Is at zero point.
174 | // The next step is seconds.
175 | // Updates at the next step.
176 | getStepChangesAt({
177 | unit: 'second',
178 | minTime: 1
179 | }, 0, {
180 | now: 0,
181 | future: false,
182 | prevStep: {}
183 | }).should.equal(1 * 1000)
184 |
185 | // Future.
186 | // Inside the first step.
187 | // Updates after zero point.
188 | getStepChangesAt({
189 | unit: 'now'
190 | }, 0.9 * 1000, {
191 | now: 0,
192 | future: true,
193 | prevStep: undefined
194 | }).should.equal(0.9 * 1000 + 1)
195 |
196 | // Future.
197 | // The first step doesn't start at 0.
198 | // Outside of the first step.
199 | // Updates right after zero point.
200 | getTimeToStepChange(undefined, 0.9 * 1000, {
201 | now: 0,
202 | future: true,
203 | prevStep: undefined
204 | }).should.equal(0.9 * 1000 + 1)
205 |
206 | // Past.
207 | // The current step is `undefined`.
208 | // The next step is the first step.
209 | // The first step doesn't start at 0.
210 | // Outside of the first step.
211 | // Updates at entering the first step.
212 | getStepChangesAt({
213 | minTime: 1,
214 | unit: 'second'
215 | }, -0.9 * 1000, {
216 | now: 0,
217 | future: false,
218 | prevStep: {}
219 | }).should.equal(0.1 * 1000)
220 |
221 | // Future.
222 | // The first step doesn't start at 0.
223 | // Will output empty string after it exits the current step.
224 | getStepChangesAt({
225 | minTime: 1,
226 | unit: 'second'
227 | }, 1.1 * 1000, {
228 | now: 0,
229 | future: true,
230 | prevStep: undefined
231 | }).should.equal(0.1 * 1000 + 1)
232 |
233 | // Past.
234 | // Next step is seconds.
235 | // The "next" step doesn't have `minTime`,
236 | // so "time to next update" couldn't be determined.
237 | expect(getStepChangesAt({
238 | unit: 'unknown-time-unit'
239 | }, 0, {
240 | now: 0,
241 | future: false,
242 | prevStep: {}
243 | })).to.be.undefined
244 |
245 | // Past.
246 | // No next step.
247 | // The last step never changes.
248 | getTimeToStepChange(undefined, 0, {
249 | now: 0,
250 | future: false,
251 | isFirstStep: undefined
252 | }).should.equal(INFINITY)
253 |
254 | // Future.
255 | // Current step is seconds.
256 | // Updates after zero point.
257 | getStepChangesAt({
258 | unit: 'second'
259 | }, 0, {
260 | now: 0,
261 | future: true,
262 | prevStep: undefined
263 | }).should.equal(1)
264 |
265 | // Past.
266 | // Next step is minutes.
267 | // Already at zero point, so no need to update at zero point.
268 | getStepChangesAt({
269 | minTime: 60,
270 | formatAs: 'minute'
271 | }, 0, {
272 | now: 0,
273 | future: false,
274 | prevStep: {}
275 | }).should.equal(60 * 1000)
276 | })
277 | })
--------------------------------------------------------------------------------
/source/steps/getTimeToNextUpdateForUnit.js:
--------------------------------------------------------------------------------
1 | import { getSecondsInUnit } from './units.js'
2 | import { getRoundFunction, getDiffRatioToNextRoundedNumber } from '../round.js'
3 |
4 | /**
5 | * Gets the time to next update for a step with a time unit defined.
6 | * @param {string} unit
7 | * @param {number} date — The date passed to `.format()`, converted to a timestamp.
8 | * @param {number} options.now
9 | * @param {string} [options.round] — (undocumented) Rounding mechanism.
10 | * @return {number} [timeToNextUpdate]
11 | */
12 | export default function getTimeToNextUpdateForUnit(unit, timestamp, { now, round }) {
13 | // For some units, like "now", there's no defined amount of seconds in them.
14 | if (!getSecondsInUnit(unit)) {
15 | // If there's no amount of seconds defined for this unit
16 | // then the update interval can't be determined reliably.
17 | return
18 | }
19 | const unitDenominator = getSecondsInUnit(unit) * 1000
20 | const future = timestamp > now
21 | const preciseAmount = Math.abs(timestamp - now)
22 | const roundedAmount = getRoundFunction(round)(preciseAmount / unitDenominator) * unitDenominator
23 | if (future) {
24 | if (roundedAmount > 0) {
25 | // Amount decreases with time.
26 | return (preciseAmount - roundedAmount) +
27 | getDiffToPreviousRoundedNumber(round, unitDenominator)
28 | } else {
29 | // Refresh right after the zero point,
30 | // when "future" changes to "past".
31 | return (preciseAmount - roundedAmount) + 1
32 | }
33 | }
34 | // Amount increases with time.
35 | return -(preciseAmount - roundedAmount) + getDiffToNextRoundedNumber(round, unitDenominator)
36 | }
37 |
38 | function getDiffToNextRoundedNumber(round, unitDenominator) {
39 | return getDiffRatioToNextRoundedNumber(round) * unitDenominator
40 | }
41 |
42 | function getDiffToPreviousRoundedNumber(round, unitDenominator) {
43 | return (1 - getDiffRatioToNextRoundedNumber(round)) * unitDenominator + 1
44 | }
--------------------------------------------------------------------------------
/source/steps/getTimeToNextUpdateForUnit.test.js:
--------------------------------------------------------------------------------
1 | import getTimeToNextUpdateForUnit from './getTimeToNextUpdateForUnit.js'
2 |
3 | describe('getTimeToNextUpdateForUnit', () => {
4 | it('should return undefined for unknown units', () => {
5 | expect(getTimeToNextUpdateForUnit('now', 0, {})).to.be.undefined
6 | })
7 |
8 | it('should support Date argument', () => {
9 | getTimeToNextUpdateForUnit('second', new Date(0), {
10 | // future: false,
11 | now: 0
12 | }).should.equal(500)
13 | })
14 |
15 | it('should get time to next update for unit (future)', () => {
16 | const test = (seconds, expected, addOneMs = true) => {
17 | getTimeToNextUpdateForUnit('second', seconds * 1000, {
18 | // future: true,
19 | now: 0
20 | }).should.equal(expected * 1000 + (addOneMs ? 1 : 0))
21 | }
22 |
23 | test(9, 0.5)
24 | test(9.1, 0.6)
25 | test(9.4, 0.9)
26 | test(9.5, 0)
27 | test(9.9, 0.4)
28 | test(10, 0.5)
29 |
30 | test(1.1, 0.6)
31 | test(1, 0.5)
32 | test(0.9, 0.4)
33 | test(0.5, 0)
34 | test(0.4, 0.4)
35 | test(0, 0.5, false)
36 | })
37 |
38 | it('should get time to next update for unit (past)', () => {
39 | const test = (seconds, expected, addOneMs = true) => {
40 | getTimeToNextUpdateForUnit('second', -1 * seconds * 1000, {
41 | // future: false,
42 | now: 0
43 | }).should.equal(expected * 1000)
44 | }
45 |
46 | test(10, 0.5)
47 | test(9.9, 0.6)
48 | test(9.5, 1)
49 | test(9.4, 0.1)
50 | test(9.1, 0.4)
51 | test(9, 0.5)
52 |
53 | test(0, 0.5, false)
54 | test(0.5, 1, false)
55 | test(0.9, 0.6, false)
56 | test(1, 0.5, false)
57 | test(1.1, 0.4, false)
58 | })
59 |
60 | it('should support "floor" rounding (future)', () => {
61 | const test = (seconds, expected, addOneMs = true) => {
62 | getTimeToNextUpdateForUnit('second', seconds * 1000, {
63 | // future: true,
64 | now: 0,
65 | round: 'floor'
66 | }).should.equal(expected * 1000 + (addOneMs ? 1 : 0))
67 | }
68 |
69 | test(9, 0)
70 | test(9.1, 0.1)
71 | test(9.4, 0.4)
72 | test(9.5, 0.5)
73 | test(9.9, 0.9)
74 | test(10, 0)
75 |
76 | test(1.1, 0.1)
77 | test(1, 0)
78 | test(0.9, 0.9)
79 | test(0.5, 0.5)
80 | test(0.1, 0.1)
81 | test(0, 1, false)
82 | })
83 |
84 | it('should support "floor" rounding (past)', () => {
85 | const test = (seconds, expected) => {
86 | getTimeToNextUpdateForUnit('second', -1 * seconds * 1000, {
87 | // future: false,
88 | now: 0,
89 | round: 'floor'
90 | }).should.equal(expected * 1000)
91 | }
92 |
93 | test(10, 1)
94 | test(9.9, 0.1)
95 | test(9.5, 0.5)
96 | test(9.4, 0.6)
97 | test(9.1, 0.9)
98 | test(9, 1)
99 |
100 | test(0, 1)
101 | test(0.5, 0.5)
102 | test(0.9, 0.1)
103 | test(1, 1)
104 | test(1.1, 0.9)
105 | })
106 | })
--------------------------------------------------------------------------------
/source/steps/helpers.js:
--------------------------------------------------------------------------------
1 | // Looks like this one's deprecated.
2 | // /**
3 | // * Returns a step corresponding to the unit.
4 | // * @param {Object[]} steps
5 | // * @param {string} unit
6 | // * @return {?Object}
7 | // */
8 | // export function getStepForUnit(steps, unit) {
9 | // for (const step of steps) {
10 | // if (step.unit === unit) {
11 | // return step
12 | // }
13 | // }
14 | // }
15 |
16 | // Looks like this one won't be used in the next major version.
17 | /**
18 | * Converts value to a `Date`
19 | * @param {(number|Date)} value
20 | * @return {Date}
21 | */
22 | export function getDate(value) {
23 | return value instanceof Date ? value : new Date(value)
24 | }
--------------------------------------------------------------------------------
/source/steps/helpers.test.js:
--------------------------------------------------------------------------------
1 | import { getDate } from './helpers.js'
2 |
3 | describe('steps/helpers', () => {
4 | it('should convert value to Date', () => {
5 | const today = new Date()
6 | getDate(today.getTime()).getTime().should.equal(today.getTime())
7 | getDate(today).getTime().should.equal(today.getTime())
8 | })
9 | })
--------------------------------------------------------------------------------
/source/steps/index.js:
--------------------------------------------------------------------------------
1 | // "convenient" is a legacy name of "approximate" steps.
2 | export { default as approximate, default as convenient } from './approximate.js'
3 |
4 | // "canonical" is a legacy name of "round" steps.
5 | export { default as round, default as canonical } from './round.js'
6 |
7 | export {
8 | minute,
9 | hour,
10 | day,
11 | week,
12 | month,
13 | year
14 | } from './units.js'
15 |
16 | export {
17 | getDate
18 | } from './helpers.js'
--------------------------------------------------------------------------------
/source/steps/renameLegacyProperties.js:
--------------------------------------------------------------------------------
1 | // This function is only used for backwards compatibility
2 | // with legacy code that uses the older versions of this library.
3 | export default function(step_) {
4 | const step = { ...step_ }
5 | if (step.minTime !== undefined) {
6 | if (typeof step.minTime === 'object') {
7 | for (const key of Object.keys(step.minTime)) {
8 | if (key === 'default') {
9 | step.threshold = step.minTime.default
10 | } else {
11 | step[`threshold_for_${key}`] = step.minTime[key]
12 | }
13 | }
14 | } else {
15 | step.threshold = step.minTime
16 | }
17 | delete step.minTime
18 | }
19 | if (step.formatAs) {
20 | step.unit = step.formatAs
21 | delete step.formatAs
22 | }
23 | return step
24 | }
--------------------------------------------------------------------------------
/source/steps/renameLegacyProperties.test.js:
--------------------------------------------------------------------------------
1 | import renameLegacyProperties from './renameLegacyProperties.js'
2 |
3 | describe('steps/renameLegacyProperties', () => {
4 | it('should rename legacy properties', () => {
5 | renameLegacyProperties({
6 | formatAs: 'now',
7 | minTime: 1
8 | }).should.deep.equal({
9 | unit: 'now',
10 | threshold: 1
11 | })
12 | })
13 |
14 | it('should rename legacy properties (minTime: undefined)', () => {
15 | renameLegacyProperties({
16 | formatAs: 'now'
17 | }).should.deep.equal({
18 | unit: 'now'
19 | })
20 | })
21 |
22 | it('should rename legacy properties (`minTime` is an object)', () => {
23 | renameLegacyProperties({
24 | formatAs: 'now',
25 | minTime: {
26 | week: 2,
27 | default: 1
28 | }
29 | }).should.deep.equal({
30 | unit: 'now',
31 | threshold: 1,
32 | threshold_for_week: 2
33 | })
34 | })
35 | })
--------------------------------------------------------------------------------
/source/steps/round.js:
--------------------------------------------------------------------------------
1 | // just now
2 | // 1 second ago
3 | // 2 seconds ago
4 | // …
5 | // 59 seconds ago
6 | // 1 minute ago
7 | // 2 minutes ago
8 | // …
9 | // 59 minutes ago
10 | // 1 hour ago
11 | // 2 hours ago
12 | // …
13 | // 24 hours ago
14 | // 1 day ago
15 | // 2 days ago
16 | // …
17 | // 6 days ago
18 | // 1 week ago
19 | // 2 weeks ago
20 | // …
21 | // 3 weeks ago
22 | // 1 month ago
23 | // 2 months ago
24 | // …
25 | // 11 months ago
26 | // 1 year ago
27 | // 2 years ago
28 | // …
29 | export default [
30 | {
31 | formatAs: 'now'
32 | },
33 | {
34 | formatAs: 'second'
35 | },
36 | {
37 | formatAs: 'minute'
38 | },
39 | {
40 | formatAs: 'hour'
41 | },
42 | {
43 | formatAs: 'day'
44 | },
45 | {
46 | formatAs: 'week'
47 | },
48 | {
49 | formatAs: 'month'
50 | },
51 | {
52 | formatAs: 'year'
53 | }
54 | ]
--------------------------------------------------------------------------------
/source/steps/round.test.js:
--------------------------------------------------------------------------------
1 | import getStep from './getStep.js'
2 | import steps from './round.js'
3 |
4 | describe('steps/round', () => {
5 | it('should get step correctly (round: "floor")', () => {
6 | const getStepFor = (secondsPassed) => getStep(steps, secondsPassed, {
7 | round: 'floor',
8 | units: [
9 | 'now',
10 | 'second',
11 | 'minute',
12 | 'hour',
13 | 'day',
14 | 'week',
15 | 'month',
16 | 'year'
17 | ]
18 | })
19 |
20 | expect(getStepFor(0).formatAs).to.equal('now')
21 | expect(getStepFor(0.9).formatAs).to.equal('now')
22 | expect(getStepFor(1).formatAs).to.equal('second')
23 | expect(getStepFor(59.9).formatAs).to.equal('second')
24 | expect(getStepFor(60).formatAs).to.equal('minute')
25 | expect(getStepFor(60 * 60 - 1).formatAs).to.equal('minute')
26 | expect(getStepFor(60 * 60).formatAs).to.equal('hour')
27 | expect(getStepFor(24 * 60 * 60).formatAs).to.equal('day')
28 | expect(getStepFor(7 * 24 * 60 * 60).formatAs).to.equal('week')
29 | })
30 |
31 | it('should get step correctly (round: "round")', () => {
32 | const getStepFor = (secondsPassed) => getStep(steps, secondsPassed, {
33 | round: 'round',
34 | units: [
35 | 'now',
36 | 'second',
37 | 'minute',
38 | 'hour',
39 | 'day',
40 | 'week',
41 | 'month',
42 | 'year'
43 | ]
44 | })
45 |
46 | expect(getStepFor(0).formatAs).to.equal('now')
47 | expect(getStepFor(0.49).formatAs).to.equal('now')
48 | expect(getStepFor(0.5).formatAs).to.equal('second')
49 | expect(getStepFor(1).formatAs).to.equal('second')
50 | expect(getStepFor(59.4).formatAs).to.equal('second')
51 | expect(getStepFor(60).formatAs).to.equal('minute')
52 | expect(getStepFor(59.4 * 60).formatAs).to.equal('minute')
53 | expect(getStepFor(60 * 60).formatAs).to.equal('hour')
54 | expect(getStepFor(23.49 * 60 * 60).formatAs).to.equal('hour')
55 | expect(getStepFor(23.5 * 60 * 60).formatAs).to.equal('day')
56 | expect(getStepFor(7 * 24 * 60 * 60).formatAs).to.equal('week')
57 | })
58 |
59 | it('should use "day"s when "week"s are not allowed', () => {
60 | const getStepFor = (secondsPassed) => getStep(steps, secondsPassed, {
61 | units: [
62 | 'now',
63 | 'second',
64 | 'minute',
65 | 'hour',
66 | 'day',
67 | 'month',
68 | 'year'
69 | ]
70 | })
71 |
72 | expect(getStepFor(7 * 24 * 60 * 60).formatAs).to.equal('day')
73 | })
74 | })
--------------------------------------------------------------------------------
/source/steps/units.js:
--------------------------------------------------------------------------------
1 | export const minute = 60 // in seconds
2 |
3 | export const hour = 60 * minute // in seconds
4 |
5 | export const day = 24 * hour // in seconds
6 |
7 | export const week = 7 * day // in seconds
8 |
9 | // https://www.quora.com/What-is-the-average-number-of-days-in-a-month
10 | export const month = 30.44 * day // in seconds
11 |
12 | // "400 years have 146097 days (taking into account leap year rules)"
13 | export const year = (146097 / 400) * day // in seconds
14 |
15 | export function getSecondsInUnit(unit) {
16 | switch (unit) {
17 | case 'second':
18 | return 1
19 | case 'minute':
20 | return minute
21 | case 'hour':
22 | return hour
23 | case 'day':
24 | return day
25 | case 'week':
26 | return week
27 | case 'month':
28 | return month
29 | case 'year':
30 | return year
31 | }
32 | }
33 |
34 | // export function getPreviousUnitFor(unit) {
35 | // switch (unit) {
36 | // case 'second':
37 | // return 'now'
38 | // case 'minute':
39 | // return 'second'
40 | // case 'hour':
41 | // return 'minute'
42 | // case 'day':
43 | // return 'hour'
44 | // case 'week':
45 | // return 'day'
46 | // case 'month':
47 | // return 'week'
48 | // case 'year':
49 | // return 'month'
50 | // }
51 | // }
--------------------------------------------------------------------------------
/source/style/approximate.js:
--------------------------------------------------------------------------------
1 | import approximate from '../steps/approximate.js'
2 |
3 | // "gradation" is a legacy name for "steps".
4 | // It's here just for legacy compatibility.
5 | // Use "steps" name instead.
6 |
7 | // "flavour" is a legacy name for "labels".
8 | // It's here just for legacy compatibility.
9 | // Use "labels" name instead.
10 |
11 | // "units" is a legacy property.
12 | // It's here just for legacy compatibility.
13 | // Developers shouldn't need to use it in their custom styles.
14 |
15 | export default {
16 | gradation: approximate,
17 | flavour: 'long',
18 | units: [
19 | 'now',
20 | 'minute',
21 | 'hour',
22 | 'day',
23 | 'week',
24 | 'month',
25 | 'year'
26 | ]
27 | }
--------------------------------------------------------------------------------
/source/style/approximateTime.js:
--------------------------------------------------------------------------------
1 | import approximate from '../steps/approximate.js'
2 |
3 | // "gradation" is a legacy name for "steps".
4 | // It's here just for legacy compatibility.
5 | // Use "steps" name instead.
6 |
7 | // "flavour" is a legacy name for "labels".
8 | // It's here just for legacy compatibility.
9 | // Use "labels" name instead.
10 |
11 | // "units" is a legacy property.
12 | // It's here just for legacy compatibility.
13 | // Developers shouldn't need to use it in their custom styles.
14 |
15 | // Similar to the default style but with "ago" omitted.
16 | //
17 | // just now
18 | // 5 minutes
19 | // 10 minutes
20 | // 15 minutes
21 | // 20 minutes
22 | // an hour
23 | // 2 hours
24 | // …
25 | // 20 hours
26 | // 1 day
27 | // 2 days
28 | // a week
29 | // 2 weeks
30 | // 3 weeks
31 | // a month
32 | // 2 months
33 | // 3 months
34 | // 4 months
35 | // a year
36 | // 2 years
37 | //
38 | export default {
39 | gradation: approximate,
40 | flavour: 'long-time',
41 | units: [
42 | 'now',
43 | 'minute',
44 | 'hour',
45 | 'day',
46 | 'week',
47 | 'month',
48 | 'year'
49 | ]
50 | }
--------------------------------------------------------------------------------
/source/style/approximateTime.test.js:
--------------------------------------------------------------------------------
1 | import TimeAgo from '../TimeAgo.js'
2 | import approximateTime from './approximateTime.js'
3 | import { day, month, year } from '../steps/units.js'
4 |
5 | describe('style/approximate-time', () => {
6 | it('should format relative time (English)', () => {
7 | approximateScaleStepsTest([
8 | 'just now',
9 | '1 minute',
10 | '2 minutes',
11 | '5 minutes',
12 | '10 minutes',
13 | '15 minutes',
14 | '20 minutes',
15 | '25 minutes',
16 | '30 minutes',
17 | '35 minutes',
18 | '40 minutes',
19 | '45 minutes',
20 | '50 minutes',
21 | '1 hour',
22 | '2 hours',
23 | '3 hours',
24 | '4 hours',
25 | '5 hours',
26 | '6 hours',
27 | '7 hours',
28 | '8 hours',
29 | '9 hours',
30 | '10 hours',
31 | '11 hours',
32 | '12 hours',
33 | '13 hours',
34 | '14 hours',
35 | '15 hours',
36 | '16 hours',
37 | '17 hours',
38 | '18 hours',
39 | '19 hours',
40 | '20 hours',
41 | '1 day',
42 | '2 days',
43 | '3 days',
44 | '4 days',
45 | '5 days',
46 | '1 week',
47 | '2 weeks',
48 | '3 weeks',
49 | '1 month',
50 | '2 months',
51 | '3 months',
52 | '4 months',
53 | '5 months',
54 | '6 months',
55 | '7 months',
56 | '8 months',
57 | '9 months',
58 | '9 months',
59 | '10 months',
60 | '1 year',
61 | '2 years',
62 | '3 years',
63 | '100 years'
64 | ],
65 | 'en-US')
66 | })
67 |
68 | it('should format relative time (Russian)', () => {
69 | approximateScaleStepsTest([
70 | 'только что',
71 | '1 минута',
72 | '2 минуты',
73 | '5 минут',
74 | '10 минут',
75 | '15 минут',
76 | '20 минут',
77 | '25 минут',
78 | '30 минут',
79 | '35 минут',
80 | '40 минут',
81 | '45 минут',
82 | '50 минут',
83 | '1 час',
84 | '2 часа',
85 | '3 часа',
86 | '4 часа',
87 | '5 часов',
88 | '6 часов',
89 | '7 часов',
90 | '8 часов',
91 | '9 часов',
92 | '10 часов',
93 | '11 часов',
94 | '12 часов',
95 | '13 часов',
96 | '14 часов',
97 | '15 часов',
98 | '16 часов',
99 | '17 часов',
100 | '18 часов',
101 | '19 часов',
102 | '20 часов',
103 | '1 день',
104 | '2 дня',
105 | '3 дня',
106 | '4 дня',
107 | '5 дней',
108 | '1 неделю',
109 | '2 недели',
110 | '3 недели',
111 | '1 месяц',
112 | '2 месяца',
113 | '3 месяца',
114 | '4 месяца',
115 | '5 месяцев',
116 | '6 месяцев',
117 | '7 месяцев',
118 | '8 месяцев',
119 | '9 месяцев',
120 | '9 месяцев',
121 | '10 месяцев',
122 | '1 год',
123 | '2 года',
124 | '3 года',
125 | '100 лет'
126 | ],
127 | 'ru-RU')
128 | })
129 | })
130 |
131 | function approximateScaleStepsTest(labels, timeAgo) {
132 | if (typeof timeAgo === 'string') {
133 | timeAgo = new TimeAgo(timeAgo)
134 | }
135 |
136 | const now = Date.now()
137 | const elapsed = time => timeAgo.format(now - time * 1000, 'approximate-time', { now })
138 |
139 | if (approximateScaleSteps.length !== labels.length) {
140 | throw new Error(`Array length mismatch. Steps: ${approximateScaleSteps.length}, labels: ${labels.length}`)
141 | }
142 |
143 | let i = 0
144 | while (i < approximateScaleSteps.length) {
145 | for (let time of approximateScaleSteps[i]) {
146 | elapsed(time).should.equal(labels[i])
147 | }
148 | i++
149 | }
150 | }
151 |
152 | const approximateScaleSteps =
153 | [
154 | // 'just now':
155 | [
156 | 0,
157 | 40.49
158 | ],
159 | // '1 minute ago':
160 | [
161 | 45.5,
162 | 1.49 * 60
163 | ],
164 | // '2 minutes ago':
165 | [
166 | 1.51 * 60,
167 | 2.49 * 60
168 | ],
169 | // '5 minutes ago':
170 | [
171 | 2.51 * 60,
172 | 7.49 * 60
173 | ],
174 | // '10 minutes ago':
175 | [
176 | 7.51 * 60,
177 | 12.49 * 60
178 | ],
179 | // '15 minutes ago':
180 | [
181 | 12.51 * 60,
182 | 17.49 * 60
183 | ],
184 | // '20 minutes ago':
185 | [
186 | 17.51 * 60,
187 | 22.49 * 60
188 | ],
189 | // '25 minutes ago':
190 | [
191 | 22.51 * 60,
192 | 27.49 * 60
193 | ],
194 | // '30 minutes ago':
195 | [
196 | 27.51 * 60,
197 | 32.49 * 60
198 | ],
199 | // '35 minutes ago':
200 | [
201 | 32.51 * 60,
202 | 37.49 * 60
203 | ],
204 | // '40 minutes ago':
205 | [
206 | 37.51 * 60,
207 | 42.49 * 60
208 | ],
209 | // '45 minutes ago':
210 | [
211 | 42.51 * 60,
212 | 47.49 * 60
213 | ],
214 | // '50 minutes ago':
215 | [
216 | 47.51 * 60,
217 | 52.49 * 60
218 | ],
219 | // '1 hour ago':
220 | [
221 | 55.01 * 60,
222 | 1.49 * 60 * 60
223 | ],
224 | // '2 hours ago':
225 | [
226 | 1.51 * 60 * 60,
227 | 2.49 * 60 * 60
228 | ],
229 | // '3 hours ago':
230 | [
231 | 2.51 * 60 * 60,
232 | 3.49 * 60 * 60
233 | ],
234 | // '4 hours ago':
235 | [
236 | 3.51 * 60 * 60,
237 | 4.49 * 60 * 60
238 | ],
239 | // '5 hours ago':
240 | [
241 | 4.51 * 60 * 60,
242 | 5.49 * 60 * 60
243 | ],
244 | // '6 hours ago':
245 | [
246 | 5.51 * 60 * 60,
247 | 6.49 * 60 * 60
248 | ],
249 | // '7 hours ago':
250 | [
251 | 6.51 * 60 * 60,
252 | 7.49 * 60 * 60
253 | ],
254 | // '8 hours ago':
255 | [
256 | 7.51 * 60 * 60,
257 | 8.49 * 60 * 60
258 | ],
259 | // '9 hours ago':
260 | [
261 | 8.51 * 60 * 60,
262 | 9.49 * 60 * 60
263 | ],
264 | // '10 hours ago':
265 | [
266 | 9.51 * 60 * 60,
267 | 10.49 * 60 * 60
268 | ],
269 | // '11 hours ago':
270 | [
271 | 10.51 * 60 * 60,
272 | 11.49 * 60 * 60
273 | ],
274 | // '12 hours ago':
275 | [
276 | 11.51 * 60 * 60,
277 | 12.49 * 60 * 60
278 | ],
279 | // '13 hours ago':
280 | [
281 | 12.51 * 60 * 60,
282 | 13.49 * 60 * 60
283 | ],
284 | // '14 hours ago':
285 | [
286 | 13.51 * 60 * 60,
287 | 14.49 * 60 * 60
288 | ],
289 | // '15 hours ago':
290 | [
291 | 14.51 * 60 * 60,
292 | 15.49 * 60 * 60
293 | ],
294 | // '16 hours ago':
295 | [
296 | 15.51 * 60 * 60,
297 | 16.49 * 60 * 60
298 | ],
299 | // '17 hours ago':
300 | [
301 | 16.51 * 60 * 60,
302 | 17.49 * 60 * 60
303 | ],
304 | // '18 hours ago':
305 | [
306 | 17.51 * 60 * 60,
307 | 18.49 * 60 * 60
308 | ],
309 | // '19 hours ago':
310 | [
311 | 18.51 * 60 * 60,
312 | 19.49 * 60 * 60
313 | ],
314 | // '20 hours ago':
315 | [
316 | 19.51 * 60 * 60,
317 | 20.49 * 60 * 60
318 | ],
319 | // '1 day ago':
320 | [
321 | 20.51 * 60 * 60,
322 | 1.49 * day
323 | ],
324 | // '2 days ago':
325 | [
326 | 1.51 * day,
327 | 2.49 * day
328 | ],
329 | // '3 days ago':
330 | [
331 | 2.51 * day,
332 | 3.49 * day
333 | ],
334 | // '4 days ago':
335 | [
336 | 3.51 * day,
337 | 4.49 * day
338 | ],
339 | // '5 days ago':
340 | [
341 | 4.51 * day,
342 | 5.49 * day
343 | ],
344 | // '1 week ago':
345 | [
346 | 5.51 * day,
347 | 1.49 * 7 * day
348 | ],
349 | // '2 weeks ago':
350 | [
351 | 1.51 * 7 * day,
352 | 2.49 * 7 * day
353 | ],
354 | // '3 weeks ago':
355 | [
356 | 2.51 * 7 * day,
357 | 3.49 * 7 * day
358 | ],
359 | // '1 month ago':
360 | [
361 | 3.51 * 7 * day,
362 | 1.49 * month
363 | ],
364 | // '2 months ago':
365 | [
366 | 1.51 * month,
367 | 2.49 * month
368 | ],
369 | // '3 months ago':
370 | [
371 | 2.51 * month,
372 | 3.49 * month
373 | ],
374 | // '4 months ago':
375 | [
376 | 3.51 * month,
377 | 4.49 * month
378 | ],
379 | // '5 months ago':
380 | [
381 | 4.51 * month,
382 | 5.49 * month
383 | ],
384 | // '6 months ago':
385 | [
386 | 5.51 * month,
387 | 6.49 * month
388 | ],
389 | // '7 months ago':
390 | [
391 | 6.51 * month,
392 | 7.49 * month
393 | ],
394 | // '8 months ago':
395 | [
396 | 7.51 * month,
397 | 8.49 * month
398 | ],
399 | // '9 months ago':
400 | [
401 | 8.51 * month,
402 | 8.99 * month
403 | ],
404 | // '9 months ago':
405 | [
406 | 9.01 * month,
407 | 9.49 * month
408 | ],
409 | // '10 months ago':
410 | [
411 | 9.51 * month,
412 | 10.49 * month
413 | ],
414 | // '1 year ago':
415 | [
416 | 10.51 * month,
417 | 1.49 * year
418 | ],
419 | // '2 years ago':
420 | [
421 | 1.51 * year,
422 | 2.49 * year
423 | ],
424 | // '3 years ago':
425 | [
426 | 2.51 * year,
427 | 3.49 * year
428 | ],
429 | // '100 years ago':
430 | [
431 | 99.51 * year,
432 | 100.49 * year
433 | ]
434 | ]
--------------------------------------------------------------------------------
/source/style/getStyleByName.js:
--------------------------------------------------------------------------------
1 | import round from './round.js'
2 | import roundMinute from './roundMinute.js'
3 | // `approximate` style is deprecated.
4 | import approximate from './approximate.js'
5 | // `approximateTime` style is deprecated.
6 | import approximateTime from './approximateTime.js'
7 | import twitter from './twitter.js'
8 | import twitterNow from './twitterNow.js'
9 | import twitterMinute from './twitterMinute.js'
10 | import twitterMinuteNow from './twitterMinuteNow.js'
11 | import twitterFirstMinute from './twitterFirstMinute.js'
12 | import mini from './mini.js'
13 | import miniNow from './miniNow.js'
14 | import miniMinute from './miniMinute.js'
15 | import miniMinuteNow from './miniMinuteNow.js'
16 |
17 | export default function getStyleByName(style) {
18 | switch (style) {
19 | // "default" style name is deprecated.
20 | case 'default':
21 | case 'round':
22 | return round
23 | case 'round-minute':
24 | return roundMinute
25 | case 'approximate':
26 | return approximate
27 | // "time" style name is deprecated.
28 | case 'time':
29 | case 'approximate-time':
30 | return approximateTime
31 | case 'mini':
32 | return mini
33 | case 'mini-now':
34 | return miniNow
35 | case 'mini-minute':
36 | return miniMinute
37 | case 'mini-minute-now':
38 | return miniMinuteNow
39 | case 'twitter':
40 | return twitter
41 | case 'twitter-now':
42 | return twitterNow
43 | case 'twitter-minute':
44 | return twitterMinute
45 | case 'twitter-minute-now':
46 | return twitterMinuteNow
47 | case 'twitter-first-minute':
48 | return twitterFirstMinute
49 | default:
50 | // For historical reasons, the default style is "approximate".
51 | return approximate
52 | }
53 | }
--------------------------------------------------------------------------------
/source/style/mini.js:
--------------------------------------------------------------------------------
1 | export default {
2 | steps: [
3 | {
4 | formatAs: 'second'
5 | },
6 | {
7 | formatAs: 'minute'
8 | },
9 | {
10 | formatAs: 'hour'
11 | },
12 | {
13 | formatAs: 'day'
14 | },
15 | {
16 | formatAs: 'month'
17 | },
18 | {
19 | formatAs: 'year'
20 | }
21 | ],
22 | labels: [
23 | // "mini" labels are only defined for a few languages.
24 | 'mini',
25 | // "short-time" labels are only defined for a few languages.
26 | 'short-time',
27 | // "narrow" and "short" labels are defined for all languages.
28 | // "narrow" labels can sometimes be weird (like "+5d."),
29 | // but "short" labels have the " ago" part, so "narrow" seem
30 | // more appropriate.
31 | // "short" labels would have been more appropriate if they
32 | // didn't have the " ago" part, hence the "short-time" above.
33 | 'narrow',
34 | // Since "narrow" labels are always present, "short" element
35 | // of this array can be removed.
36 | 'short'
37 | ]
38 | }
--------------------------------------------------------------------------------
/source/style/mini.test.js:
--------------------------------------------------------------------------------
1 | import style from './mini.js'
2 | import TimeAgo from '../TimeAgo.js'
3 | import { hour, minute, day, month, year } from '../steps/index.js'
4 |
5 | describe('style/mini', () => {
6 | it('should format relative date/time (round: "floor")', () => {
7 | const timeAgo = new TimeAgo('en')
8 | const formatInterval = (secondsPassed) => timeAgo.format(-secondsPassed * 1000, { now: 0, ...style, round: 'floor' })
9 |
10 | formatInterval(0).should.equal('0s')
11 | formatInterval(0.9).should.equal('0s')
12 | formatInterval(1).should.equal('1s')
13 | formatInterval(59.9).should.equal('59s')
14 | formatInterval(60).should.equal('1m')
15 | formatInterval(1.9 * minute).should.equal('1m')
16 | formatInterval(2 * minute).should.equal('2m')
17 | formatInterval(2.9 * minute).should.equal('2m')
18 | formatInterval(3 * minute).should.equal('3m')
19 | // …
20 | formatInterval(59.9 * minute).should.equal('59m')
21 | formatInterval(60 * minute).should.equal('1h')
22 | formatInterval(1.9 * hour).should.equal('1h')
23 | formatInterval(2 * hour).should.equal('2h')
24 | formatInterval(2.9 * hour).should.equal('2h')
25 | formatInterval(3 * hour).should.equal('3h')
26 | // …
27 | formatInterval(23.9 * hour).should.equal('23h')
28 | formatInterval(24 * hour).should.equal('1d')
29 | formatInterval(2 * day).should.equal('2d')
30 | formatInterval(7 * day).should.equal('7d')
31 | formatInterval(30 * day).should.equal('30d')
32 | formatInterval(month).should.equal('1mo')
33 | formatInterval(360 * day).should.equal('11mo')
34 | formatInterval(366 * day).should.equal('1yr')
35 | })
36 |
37 | it('should format relative date/time (round: "round")', () => {
38 | const timeAgo = new TimeAgo('en')
39 | const formatInterval = (secondsPassed) => timeAgo.format(-secondsPassed * 1000, { now: 0, ...style })
40 |
41 | formatInterval(0).should.equal('0s')
42 | formatInterval(0.49).should.equal('0s')
43 | formatInterval(0.5).should.equal('1s')
44 | formatInterval(59.49).should.equal('59s')
45 | formatInterval(59.5).should.equal('1m')
46 | formatInterval(1.49 * minute).should.equal('1m')
47 | formatInterval(1.5 * minute).should.equal('2m')
48 | formatInterval(2.49 * minute).should.equal('2m')
49 | formatInterval(2.5 * minute).should.equal('3m')
50 | // …
51 | formatInterval(59.49 * minute).should.equal('59m')
52 | formatInterval(59.5 * minute).should.equal('1h')
53 | formatInterval(1.49 * hour).should.equal('1h')
54 | formatInterval(1.5 * hour).should.equal('2h')
55 | formatInterval(2.49 * hour).should.equal('2h')
56 | formatInterval(2.5 * hour).should.equal('3h')
57 | // …
58 | formatInterval(23.49 * hour).should.equal('23h')
59 | formatInterval(23.5 * hour).should.equal('1d')
60 | formatInterval(2 * day).should.equal('2d')
61 | formatInterval(7 * day).should.equal('7d')
62 | formatInterval(29 * day).should.equal('29d')
63 | formatInterval(30 * day).should.equal('1mo')
64 | formatInterval(month).should.equal('1mo')
65 | formatInterval(350 * day).should.equal('11mo')
66 | formatInterval(360 * day).should.equal('1yr')
67 | formatInterval(366 * day).should.equal('1yr')
68 | })
69 |
70 | it('should format relative date/time (Russian)', () => {
71 | const timeAgo = new TimeAgo('ru')
72 | const formatInterval = (secondsPassed) => timeAgo.format(-secondsPassed * 1000, { now: 0, ...style })
73 |
74 | formatInterval(0).should.equal('0 с')
75 | formatInterval(1).should.equal('1 с')
76 | formatInterval(minute).should.equal('1 мин')
77 | formatInterval(hour).should.equal('1 ч')
78 | formatInterval(day).should.equal('1 д')
79 | formatInterval(month).should.equal('1 мес')
80 | formatInterval(year).should.equal('1 г')
81 | formatInterval(5 * year).should.equal('5 л')
82 | })
83 | })
--------------------------------------------------------------------------------
/source/style/miniMinute.js:
--------------------------------------------------------------------------------
1 | import mini from './mini.js'
2 |
3 | export default {
4 | ...mini,
5 | // Skip "seconds".
6 | steps: mini.steps.filter(step => step.formatAs !== 'second')
7 | }
--------------------------------------------------------------------------------
/source/style/miniMinute.test.js:
--------------------------------------------------------------------------------
1 | import style from './miniMinute.js'
2 | import TimeAgo from '../TimeAgo.js'
3 | import { hour, minute, day, month, year } from '../steps/index.js'
4 |
5 | describe('style/mini-now', () => {
6 | it('should format relative date/time (round: "floor")', () => {
7 | const timeAgo = new TimeAgo('en')
8 | const formatInterval = (secondsPassed) => timeAgo.format(-secondsPassed * 1000, { now: 0, ...style, round: 'floor' })
9 |
10 | formatInterval(0).should.equal('0m')
11 | formatInterval(0.9).should.equal('0m')
12 | formatInterval(1).should.equal('0m')
13 | formatInterval(59.9).should.equal('0m')
14 | formatInterval(60).should.equal('1m')
15 | formatInterval(1.9 * minute).should.equal('1m')
16 | formatInterval(2 * minute).should.equal('2m')
17 | formatInterval(2.9 * minute).should.equal('2m')
18 | formatInterval(3 * minute).should.equal('3m')
19 | // …
20 | formatInterval(59.9 * minute).should.equal('59m')
21 | formatInterval(60 * minute).should.equal('1h')
22 | formatInterval(1.9 * hour).should.equal('1h')
23 | formatInterval(2 * hour).should.equal('2h')
24 | formatInterval(2.9 * hour).should.equal('2h')
25 | formatInterval(3 * hour).should.equal('3h')
26 | // …
27 | formatInterval(23.9 * hour).should.equal('23h')
28 | formatInterval(24 * hour).should.equal('1d')
29 | formatInterval(2 * day).should.equal('2d')
30 | formatInterval(7 * day).should.equal('7d')
31 | formatInterval(30 * day).should.equal('30d')
32 | formatInterval(month).should.equal('1mo')
33 | formatInterval(360 * day).should.equal('11mo')
34 | formatInterval(366 * day).should.equal('1yr')
35 | })
36 | })
--------------------------------------------------------------------------------
/source/style/miniMinuteNow.js:
--------------------------------------------------------------------------------
1 | import miniMinute from './miniMinute.js'
2 |
3 | export default {
4 | ...miniMinute,
5 | // Add "now".
6 | steps: [{ formatAs: 'now' }].concat(miniMinute.steps)
7 | }
--------------------------------------------------------------------------------
/source/style/miniMinuteNow.test.js:
--------------------------------------------------------------------------------
1 | import style from './miniMinuteNow.js'
2 | import TimeAgo from '../TimeAgo.js'
3 | import { hour, minute, day, month, year } from '../steps/index.js'
4 |
5 | describe('style/mini-minute-now', () => {
6 | it('should format relative date/time (round: "floor")', () => {
7 | const timeAgo = new TimeAgo('en')
8 | const formatInterval = (secondsPassed) => timeAgo.format(-secondsPassed * 1000, { now: 0, ...style, round: 'floor' })
9 |
10 | formatInterval(0).should.equal('now')
11 | formatInterval(0.9).should.equal('now')
12 | formatInterval(1).should.equal('now')
13 | formatInterval(59.9).should.equal('now')
14 | formatInterval(60).should.equal('1m')
15 | formatInterval(1.9 * minute).should.equal('1m')
16 | formatInterval(2 * minute).should.equal('2m')
17 | formatInterval(2.9 * minute).should.equal('2m')
18 | formatInterval(3 * minute).should.equal('3m')
19 | // …
20 | formatInterval(59.9 * minute).should.equal('59m')
21 | formatInterval(60 * minute).should.equal('1h')
22 | formatInterval(1.9 * hour).should.equal('1h')
23 | formatInterval(2 * hour).should.equal('2h')
24 | formatInterval(2.9 * hour).should.equal('2h')
25 | formatInterval(3 * hour).should.equal('3h')
26 | // …
27 | formatInterval(23.9 * hour).should.equal('23h')
28 | formatInterval(24 * hour).should.equal('1d')
29 | formatInterval(2 * day).should.equal('2d')
30 | formatInterval(7 * day).should.equal('7d')
31 | formatInterval(30 * day).should.equal('30d')
32 | formatInterval(month).should.equal('1mo')
33 | formatInterval(360 * day).should.equal('11mo')
34 | formatInterval(366 * day).should.equal('1yr')
35 | })
36 | })
--------------------------------------------------------------------------------
/source/style/miniNow.js:
--------------------------------------------------------------------------------
1 | import mini from './mini.js'
2 |
3 | export default {
4 | ...mini,
5 | // Add "now".
6 | steps: [{ formatAs: 'now' }].concat(mini.steps)
7 | }
--------------------------------------------------------------------------------
/source/style/miniNow.test.js:
--------------------------------------------------------------------------------
1 | import style from './miniNow.js'
2 | import TimeAgo from '../TimeAgo.js'
3 | import { hour, minute, day, month, year } from '../steps/index.js'
4 |
5 | describe('style/mini-now', () => {
6 | it('should format relative date/time (round: "floor")', () => {
7 | const timeAgo = new TimeAgo('en')
8 | const formatInterval = (secondsPassed) => timeAgo.format(-secondsPassed * 1000, { now: 0, ...style, round: 'floor' })
9 |
10 | formatInterval(0).should.equal('now')
11 | formatInterval(0.9).should.equal('now')
12 | formatInterval(1).should.equal('1s')
13 | formatInterval(59.9).should.equal('59s')
14 | formatInterval(60).should.equal('1m')
15 | formatInterval(1.9 * minute).should.equal('1m')
16 | formatInterval(2 * minute).should.equal('2m')
17 | formatInterval(2.9 * minute).should.equal('2m')
18 | formatInterval(3 * minute).should.equal('3m')
19 | // …
20 | formatInterval(59.9 * minute).should.equal('59m')
21 | formatInterval(60 * minute).should.equal('1h')
22 | formatInterval(1.9 * hour).should.equal('1h')
23 | formatInterval(2 * hour).should.equal('2h')
24 | formatInterval(2.9 * hour).should.equal('2h')
25 | formatInterval(3 * hour).should.equal('3h')
26 | // …
27 | formatInterval(23.9 * hour).should.equal('23h')
28 | formatInterval(24 * hour).should.equal('1d')
29 | formatInterval(2 * day).should.equal('2d')
30 | formatInterval(7 * day).should.equal('7d')
31 | formatInterval(30 * day).should.equal('30d')
32 | formatInterval(month).should.equal('1mo')
33 | formatInterval(360 * day).should.equal('11mo')
34 | formatInterval(366 * day).should.equal('1yr')
35 | })
36 | })
--------------------------------------------------------------------------------
/source/style/renameLegacyProperties.js:
--------------------------------------------------------------------------------
1 | import renameLegacyProperties from '../steps/renameLegacyProperties.js'
2 |
3 | // This function is only used for backwards compatibility
4 | // with legacy code that uses the older versions of this library.
5 | export default function(style_) {
6 | const style = { ...style_ }
7 | if (style.steps) {
8 | style.gradation = style.steps.map(renameLegacyProperties)
9 | delete style.steps
10 | }
11 | if (style.labels) {
12 | style.flavour = style.labels
13 | delete style.labels
14 | }
15 | return style
16 | }
--------------------------------------------------------------------------------
/source/style/renameLegacyProperties.test.js:
--------------------------------------------------------------------------------
1 | import renameLegacyProperties from './renameLegacyProperties.js'
2 |
3 | describe('style/renameLegacyProperties', () => {
4 | it('should rename legacy properties', () => {
5 | renameLegacyProperties({
6 | steps: [{
7 | unit: 'now',
8 | minTime: {
9 | week: 2,
10 | default: 1
11 | }
12 | }],
13 | labels: 'long'
14 | }).should.deep.equal({
15 | gradation: [{
16 | unit: 'now',
17 | threshold: 1,
18 | threshold_for_week: 2
19 | }],
20 | flavour: 'long'
21 | })
22 | })
23 |
24 | it('should cover edge cases', () => {
25 | const custom = () => {}
26 | renameLegacyProperties({
27 | custom
28 | }).should.deep.equal({
29 | custom
30 | })
31 | })
32 | })
--------------------------------------------------------------------------------
/source/style/round.js:
--------------------------------------------------------------------------------
1 | import round from '../steps/round.js'
2 |
3 | // just now
4 | // 1 second ago
5 | // 2 seconds ago
6 | // …
7 | // 59 seconds ago
8 | // 1 minute ago
9 | // 2 minutes ago
10 | // …
11 | // 59 minutes ago
12 | // 1 minute ago
13 | // 2 minutes ago
14 | // …
15 | // 59 minutes ago
16 | // 1 hour ago
17 | // 2 hours ago
18 | // …
19 | // 24 hours ago
20 | // 1 day ago
21 | // 2 days ago
22 | // …
23 | // 6 days ago
24 | // 1 week ago
25 | // 2 weeks ago
26 | // 3 weeks ago
27 | // 4 weeks ago
28 | // 1 month ago
29 | // 2 months ago
30 | // …
31 | // 11 months ago
32 | // 1 year ago
33 | // 2 years ago
34 | // …
35 | //
36 | export default {
37 | steps: round,
38 | labels: 'long'
39 | }
--------------------------------------------------------------------------------
/source/style/round.test.js:
--------------------------------------------------------------------------------
1 | import round from './round.js'
2 | import TimeAgo from '../TimeAgo.js'
3 | import { day, month, year } from '../steps/index.js'
4 |
5 | describe('style/round', () => {
6 | it('should format relative time (English) (round: "floor")', () => {
7 | const timeAgo = new TimeAgo('en')
8 |
9 | const now = new Date(2016, 3, 10, 22, 59).getTime()
10 | const formatInterval = (secondsPassed) => timeAgo.format(now - secondsPassed * 1000, { now, ...round, round: 'floor' })
11 |
12 | formatInterval(0).should.equal('just now')
13 | formatInterval(0.9).should.equal('just now')
14 | formatInterval(1).should.equal('1 second ago')
15 | formatInterval(59.9).should.equal('59 seconds ago')
16 | formatInterval(60).should.equal('1 minute ago')
17 | formatInterval(1.9 * 60).should.equal('1 minute ago')
18 | formatInterval(2 * 60).should.equal('2 minutes ago')
19 | formatInterval(2.9 * 60).should.equal('2 minutes ago')
20 | formatInterval(3 * 60).should.equal('3 minutes ago')
21 | // …
22 | formatInterval(59.9 * 60).should.equal('59 minutes ago')
23 | formatInterval(60 * 60).should.equal('1 hour ago')
24 | formatInterval(1.9 * 60 * 60).should.equal('1 hour ago')
25 | formatInterval(2 * 60 * 60).should.equal('2 hours ago')
26 | formatInterval(2.9 * 60 * 60).should.equal('2 hours ago')
27 | formatInterval(3 * 60 * 60).should.equal('3 hours ago')
28 | // …
29 | formatInterval(23.9 * 60 * 60).should.equal('23 hours ago')
30 | formatInterval(24 * 60 * 60).should.equal('1 day ago')
31 | formatInterval(1.9 * day).should.equal('1 day ago')
32 | formatInterval(2 * day).should.equal('2 days ago')
33 | formatInterval(2.9 * day).should.equal('2 days ago')
34 | formatInterval(3 * day).should.equal('3 days ago')
35 | // …
36 | formatInterval(6.9 * day).should.equal('6 days ago')
37 | formatInterval(7 * day).should.equal('1 week ago')
38 | // …
39 | formatInterval(3.9 * 7 * day).should.equal('3 weeks ago')
40 | formatInterval(4 * 7 * day).should.equal('4 weeks ago')
41 | formatInterval(30.51 * day).should.equal('1 month ago')
42 | formatInterval(1.9 * month).should.equal('1 month ago')
43 | formatInterval(2 * month).should.equal('2 months ago')
44 | formatInterval(2.9 * month).should.equal('2 months ago')
45 | formatInterval(3 * month).should.equal('3 months ago')
46 | // …
47 | formatInterval(11.9 * month).should.equal('11 months ago')
48 | formatInterval(12 * month).should.equal('1 year ago')
49 | formatInterval(1.99 * year).should.equal('1 year ago')
50 | formatInterval(2 * year).should.equal('2 years ago')
51 | // …
52 |
53 | // Test future dates.
54 | formatInterval(-1 * 3).should.equal('in 3 seconds')
55 | formatInterval(-1 * month * 8).should.equal('in 8 months')
56 | })
57 |
58 | it('should format relative time (English) (round: "round")', () => {
59 | const timeAgo = new TimeAgo('en')
60 |
61 | const now = new Date(2016, 3, 10, 22, 59).getTime()
62 | const formatInterval = (secondsPassed) => timeAgo.format(now - secondsPassed * 1000, { now, ...round, round: 'round' })
63 |
64 | formatInterval(0).should.equal('just now')
65 | formatInterval(0.49).should.equal('just now')
66 | formatInterval(0.5).should.equal('1 second ago')
67 | formatInterval(59.49).should.equal('59 seconds ago')
68 | formatInterval(59.5).should.equal('1 minute ago')
69 | formatInterval(1.49 * 60).should.equal('1 minute ago')
70 | formatInterval(1.5 * 60).should.equal('2 minutes ago')
71 | formatInterval(2.49 * 60).should.equal('2 minutes ago')
72 | formatInterval(2.5 * 60).should.equal('3 minutes ago')
73 | // …
74 | formatInterval(59.49 * 60).should.equal('59 minutes ago')
75 | formatInterval(59.5 * 60).should.equal('1 hour ago')
76 | formatInterval(1.49 * 60 * 60).should.equal('1 hour ago')
77 | formatInterval(1.5 * 60 * 60).should.equal('2 hours ago')
78 | formatInterval(2.49 * 60 * 60).should.equal('2 hours ago')
79 | formatInterval(2.5 * 60 * 60).should.equal('3 hours ago')
80 | // …
81 | formatInterval(23.49 * 60 * 60).should.equal('23 hours ago')
82 | formatInterval(23.5 * 60 * 60).should.equal('1 day ago')
83 | formatInterval(1.49 * day).should.equal('1 day ago')
84 | formatInterval(1.5 * day).should.equal('2 days ago')
85 | formatInterval(2.49 * day).should.equal('2 days ago')
86 | formatInterval(2.5 * day).should.equal('3 days ago')
87 | // …
88 | formatInterval(6.49 * day).should.equal('6 days ago')
89 | formatInterval(6.5 * day).should.equal('1 week ago')
90 | // …
91 | formatInterval(3.49 * 7 * day).should.equal('3 weeks ago')
92 | formatInterval(3.5 * 7 * day).should.equal('4 weeks ago')
93 | formatInterval(30.51 * day).should.equal('1 month ago')
94 | formatInterval(1.49 * month).should.equal('1 month ago')
95 | formatInterval(1.5 * month).should.equal('2 months ago')
96 | formatInterval(2.49 * month).should.equal('2 months ago')
97 | formatInterval(2.5 * month).should.equal('3 months ago')
98 | // …
99 | formatInterval(11.49 * month).should.equal('11 months ago')
100 | formatInterval(11.5 * month).should.equal('1 year ago')
101 | formatInterval(1.49 * year).should.equal('1 year ago')
102 | formatInterval(1.5 * year).should.equal('2 years ago')
103 | // …
104 |
105 | // Test future dates.
106 | formatInterval(-1 * 3).should.equal('in 3 seconds')
107 | formatInterval(-1 * month * 8).should.equal('in 8 months')
108 | })
109 |
110 | it('should return correct update interval (round: "floor")', () => {
111 | const timeAgo = new TimeAgo('en')
112 | const [
113 | formattedDate,
114 | timeToNextUpdate
115 | ] = timeAgo.format(0, round, {
116 | now: 0,
117 | getTimeToNextUpdate: true,
118 | round: 'floor'
119 | })
120 | timeToNextUpdate.should.equal(1000)
121 | })
122 |
123 | it('should return correct update interval (round: "round")', () => {
124 | const timeAgo = new TimeAgo('en')
125 | const [
126 | formattedDate,
127 | timeToNextUpdate
128 | ] = timeAgo.format(0, round, {
129 | now: 0,
130 | getTimeToNextUpdate: true
131 | })
132 | timeToNextUpdate.should.equal(500)
133 | })
134 | })
--------------------------------------------------------------------------------
/source/style/roundMinute.js:
--------------------------------------------------------------------------------
1 | import round from './round.js'
2 |
3 | // just now
4 | // 1 minute ago
5 | // 2 minutes ago
6 | // …
7 | // 59 minutes ago
8 | // 1 minute ago
9 | // 2 minutes ago
10 | // …
11 | // 59 minutes ago
12 | // 1 hour ago
13 | // 2 hours ago
14 | // …
15 | // 24 hours ago
16 | // 1 day ago
17 | // 2 days ago
18 | // …
19 | // 6 days ago
20 | // 1 week ago
21 | // 2 weeks ago
22 | // 3 weeks ago
23 | // 4 weeks ago
24 | // 1 month ago
25 | // 2 months ago
26 | // …
27 | // 11 months ago
28 | // 1 year ago
29 | // 2 years ago
30 | // …
31 | //
32 | export default {
33 | ...round,
34 | // Skip "seconds".
35 | steps: round.steps.filter(step => step.formatAs !== 'second')
36 | }
--------------------------------------------------------------------------------
/source/style/roundMinute.test.js:
--------------------------------------------------------------------------------
1 | import roundMinute from './roundMinute.js'
2 | import TimeAgo from '../TimeAgo.js'
3 | import { day, month, year } from '../steps/index.js'
4 |
5 | describe('style/round-minute', () => {
6 | it('should format relative time (English) (round: "floor")', () => {
7 | const timeAgo = new TimeAgo('en')
8 |
9 | const now = new Date(2016, 3, 10, 22, 59).getTime()
10 | const formatInterval = (secondsPassed) => timeAgo.format(now - secondsPassed * 1000, { now, ...roundMinute, round: 'floor' })
11 |
12 | formatInterval(0).should.equal('just now')
13 | formatInterval(0.9).should.equal('just now')
14 | formatInterval(1).should.equal('just now')
15 | formatInterval(59.9).should.equal('just now')
16 | formatInterval(60).should.equal('1 minute ago')
17 | formatInterval(1.9 * 60).should.equal('1 minute ago')
18 | formatInterval(2 * 60).should.equal('2 minutes ago')
19 | formatInterval(2.9 * 60).should.equal('2 minutes ago')
20 | formatInterval(3 * 60).should.equal('3 minutes ago')
21 | // …
22 | formatInterval(59.9 * 60).should.equal('59 minutes ago')
23 | formatInterval(60 * 60).should.equal('1 hour ago')
24 | formatInterval(1.9 * 60 * 60).should.equal('1 hour ago')
25 | formatInterval(2 * 60 * 60).should.equal('2 hours ago')
26 | formatInterval(2.9 * 60 * 60).should.equal('2 hours ago')
27 | formatInterval(3 * 60 * 60).should.equal('3 hours ago')
28 | // …
29 | formatInterval(23.9 * 60 * 60).should.equal('23 hours ago')
30 | formatInterval(24 * 60 * 60).should.equal('1 day ago')
31 | formatInterval(1.9 * day).should.equal('1 day ago')
32 | formatInterval(2 * day).should.equal('2 days ago')
33 | formatInterval(2.9 * day).should.equal('2 days ago')
34 | formatInterval(3 * day).should.equal('3 days ago')
35 | // …
36 | formatInterval(6.9 * day).should.equal('6 days ago')
37 | formatInterval(7 * day).should.equal('1 week ago')
38 | // …
39 | formatInterval(3.9 * 7 * day).should.equal('3 weeks ago')
40 | formatInterval(4 * 7 * day).should.equal('4 weeks ago')
41 | formatInterval(30.51 * day).should.equal('1 month ago')
42 | formatInterval(1.9 * month).should.equal('1 month ago')
43 | formatInterval(2 * month).should.equal('2 months ago')
44 | formatInterval(2.9 * month).should.equal('2 months ago')
45 | formatInterval(3 * month).should.equal('3 months ago')
46 | // …
47 | formatInterval(11.9 * month).should.equal('11 months ago')
48 | formatInterval(12 * month).should.equal('1 year ago')
49 | formatInterval(1.99 * year).should.equal('1 year ago')
50 | formatInterval(2 * year).should.equal('2 years ago')
51 | // …
52 |
53 | // Test future dates.
54 | formatInterval(-1 * 3 * 60).should.equal('in 3 minutes')
55 | formatInterval(-1 * month * 8).should.equal('in 8 months')
56 | })
57 |
58 | it('should format relative time (English)', () => {
59 | const timeAgo = new TimeAgo('en')
60 |
61 | const now = new Date(2016, 3, 10, 22, 59).getTime()
62 | const formatInterval = (secondsPassed) => timeAgo.format(now - secondsPassed * 1000, { now, ...roundMinute })
63 |
64 | formatInterval(0).should.equal('just now')
65 | formatInterval(0.49).should.equal('just now')
66 | formatInterval(0.5).should.equal('just now')
67 | formatInterval(29.9).should.equal('just now')
68 | formatInterval(30).should.equal('1 minute ago')
69 | formatInterval(1.49 * 60).should.equal('1 minute ago')
70 | formatInterval(1.5 * 60).should.equal('2 minutes ago')
71 | formatInterval(2.49 * 60).should.equal('2 minutes ago')
72 | formatInterval(2.5 * 60).should.equal('3 minutes ago')
73 | })
74 | })
--------------------------------------------------------------------------------
/source/style/twitter.js:
--------------------------------------------------------------------------------
1 | import { day, getDate } from '../steps/index.js'
2 | import { intlDateTimeFormatSupported } from '../locale.js'
3 |
4 | // For compatibility with the old versions of this library.
5 | import renameLegacyProperties from './renameLegacyProperties.js'
6 |
7 | // Twitter-style relative date/time formatting.
8 | // ("1m", "2h", "Mar 3", "Apr 4, 2012").
9 | //
10 | // Seconds, minutes or hours are shown for shorter intervals,
11 | // and longer intervals are formatted using full date format.
12 |
13 | const steps = [
14 | {
15 | formatAs: 'second'
16 | },
17 | {
18 | formatAs: 'minute'
19 | },
20 | {
21 | formatAs: 'hour'
22 | }
23 | ]
24 |
25 | // A cache for `Intl.DateTimeFormat` formatters
26 | // for various locales (is a global variable).
27 | const formatters = {}
28 |
29 | // Starting from day intervals, output month and day.
30 | const monthAndDay = {
31 | minTime(timestamp, { future, getMinTimeForUnit }) {
32 | // Returns `23.5 * 60 * 60` when `round` is "round",
33 | // and `24 * 60 * 60` when `round` is "floor".
34 | return getMinTimeForUnit('day')
35 | },
36 | format(value, locale) {
37 | /* istanbul ignore else */
38 | if (!formatters[locale]) {
39 | formatters[locale] = {}
40 | }
41 | /* istanbul ignore else */
42 | if (!formatters[locale].dayMonth) {
43 | // "Apr 11" (MMMd)
44 | formatters[locale].dayMonth = new Intl.DateTimeFormat(locale, {
45 | month: 'short',
46 | day: 'numeric'
47 | })
48 | }
49 | // Output month and day.
50 | return formatters[locale].dayMonth.format(getDate(value))
51 | }
52 | }
53 |
54 | // If the `date` happened/happens outside of current year,
55 | // then output day, month and year.
56 | // The interval should be such that the `date` lies outside of the current year.
57 | const yearMonthAndDay = {
58 | minTime(timestamp, { future }) {
59 | if (future) {
60 | // January 1, 00:00, of the `date`'s year is right after
61 | // the maximum `now` for formatting a future date:
62 | // When `now` is before that date, the `date` is formatted as "day/month/year" (this step),
63 | // When `now` is equal to or after that date, the `date` is formatted as "day/month" (another step).
64 | // After that, it's hours, minutes, seconds, and after that it's no longer `future`.
65 | // The date is right after the maximum `now` for formatting a future date,
66 | // so subtract 1 millisecond from it.
67 | const maxFittingNow = new Date(new Date(timestamp).getFullYear(), 0).getTime() - 1
68 | // Return `minTime` (in seconds).
69 | return (timestamp - maxFittingNow) / 1000
70 | } else {
71 | // January 1, 00:00, of the year following the `date`'s year
72 | // is the minimum `now` for formatting a past date:
73 | // When `now` is before that date, the `date` is formatted as "day/month" (another step),
74 | // When `now` is equal to or after that date, the `date` is formatted as "day/month/year" (this step).
75 | // After that, it's hours, minutes, seconds, and after that it's no longer `future`.
76 | const minFittingNow = new Date(new Date(timestamp).getFullYear() + 1, 0).getTime()
77 | // Return `minTime` (in seconds).
78 | return (minFittingNow - timestamp) / 1000
79 | }
80 | },
81 | format(value, locale) {
82 | /* istanbul ignore if */
83 | if (!formatters[locale]) {
84 | formatters[locale] = {}
85 | }
86 | /* istanbul ignore else */
87 | if (!formatters[locale].dayMonthYear) {
88 | // "Apr 11, 2017" (yMMMd)
89 | formatters[locale].dayMonthYear = new Intl.DateTimeFormat(locale, {
90 | year: 'numeric',
91 | month: 'short',
92 | day: 'numeric'
93 | })
94 | }
95 | // Output day, month and year.
96 | return formatters[locale].dayMonthYear.format(getDate(value))
97 | }
98 | }
99 |
100 | // If `Intl.DateTimeFormat` is supported,
101 | // then longer time intervals will be formatted as dates.
102 | /* istanbul ignore else */
103 | if (intlDateTimeFormatSupported()) {
104 | steps.push(monthAndDay, yearMonthAndDay)
105 | }
106 | // Otherwise, if `Intl.DateTimeFormat` is not supported,
107 | // which could be the case when using Internet Explorer,
108 | // then simply mimick "round" steps.
109 | else {
110 | steps.push(
111 | {
112 | formatAs: 'day'
113 | },
114 | {
115 | formatAs: 'week'
116 | },
117 | {
118 | formatAs: 'month'
119 | },
120 | {
121 | formatAs: 'year'
122 | }
123 | )
124 | }
125 |
126 | export default {
127 | steps,
128 | labels: [
129 | // "mini" labels are only defined for a few languages.
130 | 'mini',
131 | // "short-time" labels are only defined for a few languages.
132 | 'short-time',
133 | // "narrow" and "short" labels are defined for all languages.
134 | // "narrow" labels can sometimes be weird (like "+5d."),
135 | // but "short" labels have the " ago" part, so "narrow" seem
136 | // more appropriate.
137 | // "short" labels would have been more appropriate if they
138 | // didn't have the " ago" part, hence the "short-time" above.
139 | 'narrow',
140 | // Since "narrow" labels are always present, "short" element
141 | // of this array can be removed.
142 | 'short'
143 | ]
144 | }
--------------------------------------------------------------------------------
/source/style/twitter.test.js:
--------------------------------------------------------------------------------
1 | import twitter from './twitter.js'
2 | import TimeAgo from '../TimeAgo.js'
3 | import { hour, minute, day, month, year } from '../steps/index.js'
4 |
5 | describe('style/twitter', () => {
6 | it('should fallback from "mini" to "narrow"', () => {
7 | const timeAgo = new TimeAgo('ccp')
8 | timeAgo.format(Date.now() - 3 * hour * 1000, 'twitter').should.include(' 𑄊𑄮𑄚𑄴𑄓 𑄃𑄉𑄬')
9 | })
10 |
11 | it('should format Twitter style relative time (English) (round: "round")', () => {
12 | const timeAgo = new TimeAgo('en')
13 |
14 | const formatDatePastBy = (secondsPassed) => timeAgo.format(-secondsPassed * 1000, { now: 0, ...twitter })
15 |
16 | formatDatePastBy(0.49).should.equal('0s')
17 | formatDatePastBy(0.5).should.equal('1s')
18 | formatDatePastBy(59.49).should.equal('59s')
19 | formatDatePastBy(59.5).should.equal('1m')
20 | formatDatePastBy(1.49 * minute).should.equal('1m')
21 | formatDatePastBy(1.5 * minute).should.equal('2m')
22 | // …
23 | formatDatePastBy(59.49 * minute).should.equal('59m')
24 | formatDatePastBy(59.5 * minute).should.equal('1h')
25 | formatDatePastBy(1.49 * hour).should.equal('1h')
26 | formatDatePastBy(1.5 * hour).should.equal('2h')
27 | // …
28 | formatDatePastBy(23.49 * hour).should.equal('23h')
29 | })
30 |
31 | it('should format Twitter style relative time (English) (round: "floor")', () => {
32 | const timeAgo = new TimeAgo('en')
33 |
34 | const formatDatePastBy = (secondsPassed) => timeAgo.format(-secondsPassed * 1000, { now: 0, ...twitter, round: 'floor' })
35 |
36 | formatDatePastBy(0).should.equal('0s')
37 | formatDatePastBy(0.9).should.equal('0s')
38 | formatDatePastBy(1).should.equal('1s')
39 | formatDatePastBy(59.9).should.equal('59s')
40 | formatDatePastBy(60).should.equal('1m')
41 | formatDatePastBy(1.9 * minute).should.equal('1m')
42 | formatDatePastBy(2 * minute).should.equal('2m')
43 | formatDatePastBy(2.9 * minute).should.equal('2m')
44 | formatDatePastBy(3 * minute).should.equal('3m')
45 | // …
46 | formatDatePastBy(59.9 * minute).should.equal('59m')
47 | formatDatePastBy(60 * minute).should.equal('1h')
48 | formatDatePastBy(1.9 * hour).should.equal('1h')
49 | formatDatePastBy(2 * hour).should.equal('2h')
50 | formatDatePastBy(2.9 * hour).should.equal('2h')
51 | formatDatePastBy(3 * hour).should.equal('3h')
52 | // …
53 | formatDatePastBy(23.9 * hour).should.equal('23h')
54 | })
55 |
56 | it('should format Twitter style relative time (English) (absolute dates)', () => {
57 | const timeAgo = new TimeAgo('en')
58 |
59 | // April 10th, 2016, 12:00.
60 | const now = new Date(2016, 3, 10, 12, 0).getTime()
61 | const formatDatePastBy = (secondsPassed) => timeAgo.format(now - secondsPassed * 1000, { now, ...twitter })
62 |
63 | formatDatePastBy(day + 2 * minute + hour).should.equal('Apr 9')
64 | // …
65 | // `month` is about 30.5 days.
66 | formatDatePastBy(month * 3).should.equal('Jan 10')
67 | formatDatePastBy(month * 4).should.equal('Dec 10, 2015')
68 | formatDatePastBy(year).should.equal('Apr 11, 2015')
69 |
70 | // Test future dates.
71 | // `month` is about 30.5 days.
72 | formatDatePastBy(-1 * month * 8).should.equal('Dec 10')
73 | formatDatePastBy(-1 * month * 9).should.equal('Jan 9, 2017')
74 | })
75 |
76 | it('should format Twitter style relative time (Russian)', () => {
77 | const timeAgo = new TimeAgo('ru')
78 |
79 | const now = new Date(2016, 3, 10, 22, 59).getTime()
80 | const formatDatePastBy = (secondsPassed) => timeAgo.format(now - secondsPassed * 1000, { now, ...twitter })
81 |
82 | formatDatePastBy(0).should.equal('0 с')
83 | formatDatePastBy(1).should.equal('1 с')
84 | formatDatePastBy(minute).should.equal('1 мин')
85 | formatDatePastBy(hour).should.equal('1 ч')
86 | formatDatePastBy(day + 62 * minute).should.equal('9 апр.')
87 | formatDatePastBy(year).should.equal('11 апр. 2015 г.')
88 | })
89 |
90 | it('should format Twitter style relative time (Korean)', () => {
91 | const timeAgo = new TimeAgo('ko')
92 |
93 | const now = new Date(2016, 3, 10, 22, 59).getTime()
94 | const formatDatePastBy = (secondsPassed) => timeAgo.format(now - secondsPassed * 1000, { now, ...twitter })
95 |
96 | formatDatePastBy(minute).should.equal('1분')
97 | formatDatePastBy(hour).should.equal('1시간')
98 | formatDatePastBy(day + 62 * minute).should.equal('4월 9일')
99 | formatDatePastBy(year).should.equal('2015년 4월 11일')
100 | })
101 |
102 | it('should format Twitter style relative time (German)', () => {
103 | const timeAgo = new TimeAgo('de')
104 |
105 | const now = new Date(2016, 3, 10, 22, 59).getTime()
106 | const formatDatePastBy = (secondsPassed) => timeAgo.format(now - secondsPassed * 1000, { now, ...twitter })
107 |
108 | formatDatePastBy(minute).should.equal('1 Min.')
109 | formatDatePastBy(hour).should.equal('1 Std.')
110 | formatDatePastBy(day + 62 * minute).should.equal('9. Apr.')
111 | formatDatePastBy(year).should.equal('11. Apr. 2015')
112 | })
113 |
114 | it('should format Twitter style relative time (French)', () => {
115 | const timeAgo = new TimeAgo('fr')
116 |
117 | const now = new Date(2016, 3, 10, 22, 59).getTime()
118 | const formatDatePastBy = (secondsPassed) => timeAgo.format(now - secondsPassed * 1000, { now, ...twitter })
119 |
120 | formatDatePastBy(minute).should.equal('1 min.')
121 | formatDatePastBy(hour).should.equal('1 h')
122 | formatDatePastBy(day + 62 * minute).should.equal('9 avr.')
123 | formatDatePastBy(year).should.equal('11 avr. 2015')
124 | })
125 |
126 | it('should format Twitter style relative time (Chinese)', () => {
127 | const timeAgo = new TimeAgo('zh')
128 |
129 | const now = new Date(2016, 3, 10, 22, 59).getTime()
130 | const formatDatePastBy = (secondsPassed) => timeAgo.format(now - secondsPassed * 1000, { now, ...twitter })
131 |
132 | formatDatePastBy(minute).should.equal('1分钟')
133 | formatDatePastBy(hour).should.equal('1小时')
134 | formatDatePastBy(day + 62 * minute).should.equal('4月9日')
135 | formatDatePastBy(year).should.equal('2015年4月11日')
136 | })
137 |
138 | // This test won't pass because `Intl.DateTimeFormat` is read at
139 | // initialization time, not at run time.
140 | // it('should fall back to generic style when Intl.DateTimeFormat is not available', () => {
141 | // const DateTimeFormat = Intl.DateTimeFormat
142 | // Intl.DateTimeFormat = undefined
143 | //
144 | // const timeAgo = new TimeAgo('en')
145 | // timeAgo.format(Date.now() - 365 * 24 * hour * 1000, 'twitter').should.equal('1yr')
146 | //
147 | // Intl.DateTimeFormat = DateTimeFormat
148 | // })
149 |
150 | it('should support timestamp argument on `yearMonthAndDay.test()`', () => {
151 | const timeAgo = new TimeAgo('en')
152 | timeAgo.format(0, 'twitter').should.equal('Jan 1, 1970')
153 | })
154 |
155 | it('should round as "floor"', () => {
156 | const timeAgo = new TimeAgo('en')
157 | const test = (time, result) => timeAgo.format(time, 'twitter', {
158 | round: 'floor',
159 | now: 0
160 | }).should.equal(result)
161 | test(2001, '2s')
162 | test(2000, '2s')
163 | test(1999, '1s')
164 | test(1001, '1s')
165 | test(1000, '1s')
166 | test(999, '0s')
167 | test(0, '0s')
168 | test(-999, '0s')
169 | test(-1000, '1s')
170 | test(-1001, '1s')
171 | test(-1999, '1s')
172 | test(-2000, '2s')
173 | test(-2001, '2s')
174 | })
175 |
176 | it('should get time to next update (round: "floor")', () => {
177 | const timeAgo = new TimeAgo('en')
178 |
179 | // April 10th, 2018, 12:00.
180 | const date = new Date(2018, 3, 10, 12, 0)
181 |
182 | // April 10th, 2016, 12:00 (two years earlier).
183 | let now = new Date(2016, 3, 10, 12, 0).getTime()
184 |
185 | timeAgo.format(
186 | date,
187 | 'twitter',
188 | {
189 | now,
190 | getTimeToNextUpdate: true
191 | }
192 | ).should.deep.equal([
193 | 'Apr 10, 2018',
194 | // Updates on Jan 1st, 2018, 00:00.
195 | new Date(2018, 0, 1).getTime() - now
196 | ])
197 |
198 | // 1st, 2018, 00:00.
199 | now = new Date(2018, 0, 1).getTime()
200 |
201 | timeAgo.format(
202 | date,
203 | 'twitter',
204 | {
205 | now,
206 | getTimeToNextUpdate: true,
207 | round: 'floor'
208 | }
209 | ).should.deep.equal([
210 | 'Apr 10',
211 | // Updates after April 9th, 2018, 12:00.
212 | (new Date(2018, 3, 9, 12, 0).getTime() + 1) - now
213 | ])
214 |
215 | // After April 9th, 2018, 12:00.
216 | now = new Date(2018, 3, 9, 12, 0).getTime() + 1
217 |
218 | timeAgo.format(
219 | date,
220 | 'twitter',
221 | {
222 | now,
223 | getTimeToNextUpdate: true,
224 | round: 'floor'
225 | }
226 | ).should.deep.equal([
227 | '23h',
228 | // Updates in an hour.
229 | 60 * 60 * 1000
230 | ])
231 | })
232 |
233 | it('should get time to next update (round: "round")', () => {
234 | const timeAgo = new TimeAgo('en')
235 |
236 | // April 10th, 2018, 12:00.
237 | const date = new Date(2018, 3, 10, 12, 0)
238 |
239 | let now
240 |
241 | // 1st, 2018, 00:00.
242 | now = new Date(2018, 0, 1).getTime()
243 |
244 | timeAgo.format(
245 | date,
246 | 'twitter',
247 | {
248 | now,
249 | getTimeToNextUpdate: true
250 | }
251 | ).should.deep.equal([
252 | 'Apr 10',
253 | // Updates after April 9th, 2018, 11:30.
254 | (new Date(2018, 3, 9, 12, 0).getTime() + 30 * 60 * 1000 + 1) - now
255 | ])
256 |
257 | // After April 9th, 2018, 12:00.
258 | now = new Date(2018, 3, 9, 12, 0).getTime() + 30 * 60 * 1000 + 1
259 |
260 | timeAgo.format(
261 | date,
262 | 'twitter',
263 | {
264 | now,
265 | getTimeToNextUpdate: true
266 | }
267 | ).should.deep.equal([
268 | '23h',
269 | // Updates in an hour.
270 | 60 * 60 * 1000
271 | ])
272 | })
273 | })
--------------------------------------------------------------------------------
/source/style/twitterFirstMinute.js:
--------------------------------------------------------------------------------
1 | import { minute } from '../steps/units.js'
2 | import twitter from './twitter.js'
3 |
4 | export default {
5 | ...twitter,
6 | // Skip "seconds".
7 | steps: twitter.steps.filter(step => step.formatAs !== 'second')
8 | // Start showing `1m` from the first minute.
9 | .map(step => step.formatAs === 'minute' ? { ...step, minTime: minute } : step)
10 | }
--------------------------------------------------------------------------------
/source/style/twitterFirstMinute.test.js:
--------------------------------------------------------------------------------
1 | import twitter from './twitterFirstMinute.js'
2 | import TimeAgo from '../TimeAgo.js'
3 | import { hour, minute, day, month, year } from '../steps/index.js'
4 |
5 | describe('style/twitterFirstMinute', () => {
6 | it('should work with string name of the style', () => {
7 | const timeAgo = new TimeAgo('en')
8 | timeAgo.format(Date.now() - 3 * 60 * 60 * 1000, 'twitter-first-minute').should.equal('3h')
9 | })
10 |
11 | it('should format Twitter style relative time (English) (round: "floor")', () => {
12 | const timeAgo = new TimeAgo('en')
13 |
14 | const now = new Date(2016, 3, 10, 22, 59).getTime()
15 | const formatInterval = (secondsPassed) => timeAgo.format(now - secondsPassed * 1000, { now, ...twitter, round: 'floor' })
16 |
17 | formatInterval(0).should.equal('')
18 | formatInterval(0.9).should.equal('')
19 | formatInterval(59.9).should.equal('')
20 | formatInterval(60).should.equal('1m')
21 | formatInterval(1.9 * minute).should.equal('1m')
22 | formatInterval(2 * minute).should.equal('2m')
23 | formatInterval(2.9 * minute).should.equal('2m')
24 | formatInterval(3 * minute).should.equal('3m')
25 | // …
26 | formatInterval(59.9 * minute).should.equal('59m')
27 | formatInterval(60 * minute).should.equal('1h')
28 | formatInterval(1.9 * hour).should.equal('1h')
29 | formatInterval(2 * hour).should.equal('2h')
30 | formatInterval(2.9 * hour).should.equal('2h')
31 | formatInterval(3 * hour).should.equal('3h')
32 | // …
33 | formatInterval(23.9 * hour).should.equal('23h')
34 | formatInterval(day + 2 * minute + hour).should.equal('Apr 9')
35 | // …
36 | // `month` is about 30.5 days.
37 | formatInterval(month * 3).should.equal('Jan 10')
38 | formatInterval(month * 4).should.equal('Dec 11, 2015')
39 | formatInterval(year).should.equal('Apr 11, 2015')
40 |
41 | // Test future dates.
42 | // `month` is about 30.5 days.
43 | formatInterval(-1 * month * 8).should.equal('Dec 10')
44 | formatInterval(-1 * month * 9).should.equal('Jan 9, 2017')
45 | })
46 |
47 | it('should format Twitter style relative time (English) (round: "round")', () => {
48 | const timeAgo = new TimeAgo('en')
49 |
50 | const now = new Date(2016, 3, 10, 22, 59).getTime()
51 | const formatInterval = (secondsPassed) => timeAgo.format(now - secondsPassed * 1000, { now, ...twitter, round: 'round' })
52 |
53 | formatInterval(0).should.equal('')
54 | formatInterval(59.9).should.equal('')
55 | formatInterval(60).should.equal('1m')
56 | formatInterval(1.49 * minute).should.equal('1m')
57 | formatInterval(1.5 * minute).should.equal('2m')
58 | })
59 | })
--------------------------------------------------------------------------------
/source/style/twitterMinute.js:
--------------------------------------------------------------------------------
1 | import twitter from './twitter.js'
2 |
3 | export default {
4 | ...twitter,
5 | // Skip "seconds".
6 | steps: twitter.steps.filter(step => step.formatAs !== 'second')
7 | }
--------------------------------------------------------------------------------
/source/style/twitterMinute.test.js:
--------------------------------------------------------------------------------
1 | import twitter from './twitterMinute.js'
2 | import TimeAgo from '../TimeAgo.js'
3 | import { hour, minute, day, month, year } from '../steps/index.js'
4 |
5 | describe('style/twitterMinute', () => {
6 | it('should format Twitter style relative time (English) (round: "floor")', () => {
7 | const timeAgo = new TimeAgo('en')
8 |
9 | // April 10th, 2016.
10 | const now = new Date(2016, 3, 10, 22, 59).getTime()
11 | const formatInterval = (secondsPassed) => timeAgo.format(now - secondsPassed * 1000, { now, ...twitter, round: 'floor' })
12 |
13 | formatInterval(0).should.equal('0m')
14 | formatInterval(59.9).should.equal('0m')
15 | formatInterval(60).should.equal('1m')
16 | formatInterval(1.9 * minute).should.equal('1m')
17 | formatInterval(2 * minute).should.equal('2m')
18 | formatInterval(2.9 * minute).should.equal('2m')
19 | formatInterval(3 * minute).should.equal('3m')
20 | // …
21 | formatInterval(59.9 * minute).should.equal('59m')
22 | formatInterval(60 * minute).should.equal('1h')
23 | formatInterval(1.9 * hour).should.equal('1h')
24 | formatInterval(2 * hour).should.equal('2h')
25 | formatInterval(2.9 * hour).should.equal('2h')
26 | formatInterval(3 * hour).should.equal('3h')
27 | // …
28 | formatInterval(23.9 * hour).should.equal('23h')
29 | formatInterval(day + 2 * minute + hour).should.equal('Apr 9')
30 | // …
31 | // `month` is about 30.5 days.
32 | formatInterval(month * 3).should.equal('Jan 10')
33 | formatInterval(month * 4).should.equal('Dec 11, 2015')
34 | formatInterval(year).should.equal('Apr 11, 2015')
35 |
36 | // Test future dates.
37 | // `month` is about 30.5 days.
38 | formatInterval(-1 * month * 8).should.equal('Dec 10')
39 | formatInterval(-1 * month * 9).should.equal('Jan 9, 2017')
40 | })
41 |
42 | it('should format Twitter style relative time (English) (round: "round")', () => {
43 | const timeAgo = new TimeAgo('en')
44 |
45 | // April 10th, 2016.
46 | const now = new Date(2016, 3, 10, 22, 59).getTime()
47 | const formatInterval = (secondsPassed) => timeAgo.format(now - secondsPassed * 1000, { now, ...twitter, round: 'round' })
48 |
49 | formatInterval(0).should.equal('0m')
50 | formatInterval(29.9).should.equal('0m')
51 | formatInterval(30).should.equal('1m')
52 | formatInterval(1.49 * minute).should.equal('1m')
53 | formatInterval(1.5 * minute).should.equal('2m')
54 | formatInterval(2.49 * minute).should.equal('2m')
55 | formatInterval(2.5 * minute).should.equal('3m')
56 | })
57 | })
--------------------------------------------------------------------------------
/source/style/twitterMinuteNow.js:
--------------------------------------------------------------------------------
1 | import twitterMinute from './twitterMinute.js'
2 |
3 | export default {
4 | ...twitterMinute,
5 | // Add "now".
6 | steps: [{ formatAs: 'now' }].concat(twitterMinute.steps)
7 | }
--------------------------------------------------------------------------------
/source/style/twitterMinuteNow.test.js:
--------------------------------------------------------------------------------
1 | import twitter from './twitterMinuteNow.js'
2 | import TimeAgo from '../TimeAgo.js'
3 | import { hour, minute, day, month, year } from '../steps/index.js'
4 |
5 | describe('style/twitterNow', () => {
6 | it('should format Twitter style relative time (English) (round: "floor")', () => {
7 | const timeAgo = new TimeAgo('en')
8 |
9 | // April 10th, 2016.
10 | const now = new Date(2016, 3, 10, 22, 59).getTime()
11 | const formatInterval = (secondsPassed) => timeAgo.format(now - secondsPassed * 1000, { now, ...twitter, round: 'floor' })
12 |
13 | formatInterval(0).should.equal('now')
14 | formatInterval(59.9).should.equal('now')
15 | formatInterval(60).should.equal('1m')
16 | formatInterval(1.9 * minute).should.equal('1m')
17 | formatInterval(2 * minute).should.equal('2m')
18 | formatInterval(2.9 * minute).should.equal('2m')
19 | formatInterval(3 * minute).should.equal('3m')
20 | // …
21 | formatInterval(59.9 * minute).should.equal('59m')
22 | formatInterval(60 * minute).should.equal('1h')
23 | formatInterval(1.9 * hour).should.equal('1h')
24 | formatInterval(2 * hour).should.equal('2h')
25 | formatInterval(2.9 * hour).should.equal('2h')
26 | formatInterval(3 * hour).should.equal('3h')
27 | // …
28 | formatInterval(23.9 * hour).should.equal('23h')
29 | formatInterval(day + 2 * minute + hour).should.equal('Apr 9')
30 | // …
31 | // `month` is about 30.5 days.
32 | formatInterval(month * 3).should.equal('Jan 10')
33 | formatInterval(month * 4).should.equal('Dec 11, 2015')
34 | formatInterval(year).should.equal('Apr 11, 2015')
35 |
36 | // Test future dates.
37 | // `month` is about 30.5 days.
38 | formatInterval(-1 * month * 8).should.equal('Dec 10')
39 | formatInterval(-1 * month * 9).should.equal('Jan 9, 2017')
40 | })
41 |
42 | it('should format Twitter style relative time (English) (round: "round")', () => {
43 | const timeAgo = new TimeAgo('en')
44 |
45 | // April 10th, 2016.
46 | const now = new Date(2016, 3, 10, 22, 59).getTime()
47 | const formatInterval = (secondsPassed) => timeAgo.format(now - secondsPassed * 1000, { now, ...twitter, round: 'round' })
48 |
49 | formatInterval(0).should.equal('now')
50 | formatInterval(29.9).should.equal('now')
51 | formatInterval(30).should.equal('1m')
52 | formatInterval(1.49 * minute).should.equal('1m')
53 | formatInterval(1.5 * minute).should.equal('2m')
54 | formatInterval(2.49 * minute).should.equal('2m')
55 | formatInterval(2.5 * minute).should.equal('3m')
56 | })
57 | })
--------------------------------------------------------------------------------
/source/style/twitterNow.js:
--------------------------------------------------------------------------------
1 | import twitter from './twitter.js'
2 |
3 | export default {
4 | ...twitter,
5 | // Add "now".
6 | steps: [{ formatAs: 'now' }].concat(twitter.steps)
7 | }
--------------------------------------------------------------------------------
/source/style/twitterNow.test.js:
--------------------------------------------------------------------------------
1 | import twitter from './twitterNow.js'
2 | import TimeAgo from '../TimeAgo.js'
3 | import { hour, minute, day, month, year } from '../steps/index.js'
4 |
5 | describe('style/twitterNow', () => {
6 | it('should format Twitter style relative time (English) (round: "floor")', () => {
7 | const timeAgo = new TimeAgo('en')
8 |
9 | // April 10th, 2016.
10 | const now = new Date(2016, 3, 10, 22, 59).getTime()
11 | const formatInterval = (secondsPassed) => timeAgo.format(now - secondsPassed * 1000, { now, ...twitter, round: 'floor' })
12 |
13 | formatInterval(0).should.equal('now')
14 | formatInterval(0.9).should.equal('now')
15 | formatInterval(1).should.equal('1s')
16 | formatInterval(59.9).should.equal('59s')
17 | formatInterval(60).should.equal('1m')
18 | formatInterval(1.9 * minute).should.equal('1m')
19 | formatInterval(2 * minute).should.equal('2m')
20 | formatInterval(2.9 * minute).should.equal('2m')
21 | formatInterval(3 * minute).should.equal('3m')
22 | // …
23 | formatInterval(59.9 * minute).should.equal('59m')
24 | formatInterval(60 * minute).should.equal('1h')
25 | formatInterval(1.9 * hour).should.equal('1h')
26 | formatInterval(2 * hour).should.equal('2h')
27 | formatInterval(2.9 * hour).should.equal('2h')
28 | formatInterval(3 * hour).should.equal('3h')
29 | // …
30 | formatInterval(23.9 * hour).should.equal('23h')
31 | formatInterval(day + 2 * minute + hour).should.equal('Apr 9')
32 | // …
33 | // `month` is about 30.5 days.
34 | formatInterval(month * 3).should.equal('Jan 10')
35 | formatInterval(month * 4).should.equal('Dec 11, 2015')
36 | formatInterval(year).should.equal('Apr 11, 2015')
37 |
38 | // Test future dates.
39 | // `month` is about 30.5 days.
40 | formatInterval(-1 * month * 8).should.equal('Dec 10')
41 | formatInterval(-1 * month * 9).should.equal('Jan 9, 2017')
42 | })
43 |
44 | it('should format Twitter style relative time (Russian) (round: "floor")', () => {
45 | const timeAgo = new TimeAgo('ru')
46 |
47 | const now = new Date(2016, 3, 10, 22, 59).getTime()
48 | const formatInterval = (secondsPassed) => timeAgo.format(now - secondsPassed * 1000, { now, ...twitter, round: 'floor' })
49 |
50 | formatInterval(0).should.equal('сейчас')
51 | formatInterval(0.9).should.equal('сейчас')
52 | formatInterval(1).should.equal('1 с')
53 | formatInterval(60).should.equal('1 мин')
54 | formatInterval(60 * minute).should.equal('1 ч')
55 | formatInterval(day + 62 * minute).should.equal('9 апр.')
56 | formatInterval(year).should.equal('11 апр. 2015 г.')
57 | })
58 | })
--------------------------------------------------------------------------------
/steps/index.cjs:
--------------------------------------------------------------------------------
1 | module.exports = require('../commonjs/steps/index.js')
--------------------------------------------------------------------------------
/steps/index.cjs.js:
--------------------------------------------------------------------------------
1 | // This file id deprecated.
2 | // It's the same as `index.cjs`, just with an added `.js` file extension.
3 | // It only exists for compatibility with the software that doesn't like `*.cjs` file extension.
4 | // https://gitlab.com/catamphetamine/libphonenumber-js/-/issues/61#note_950728292
5 |
6 | module.exports = require('../commonjs/steps/index.js')
--------------------------------------------------------------------------------
/steps/index.js:
--------------------------------------------------------------------------------
1 | export * from '../modules/steps/index.js'
--------------------------------------------------------------------------------
/steps/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "name": "javascript-time-ago/steps",
4 | "main": "index.cjs",
5 | "module": "index.js",
6 | "type": "module",
7 | "exports": {
8 | ".": {
9 | "import": "./index.js",
10 | "require": "./index.cjs"
11 | }
12 | },
13 | "sideEffects": false
14 | }
--------------------------------------------------------------------------------
/test/TimeAgo.js:
--------------------------------------------------------------------------------
1 | // This file exists so that `setupLocales.js` could work from outside the `commonjs` folder.
2 | export { default } from '../source/TimeAgo.js'
--------------------------------------------------------------------------------
/test/addLabels.test.js:
--------------------------------------------------------------------------------
1 | import TimeAgo from './TimeAgo.js'
2 | import en from '../locale/en.json' assert { type: 'json' }
3 |
4 | describe('TimeAgo.addLocale', () => {
5 | it('should add and use custom labels', () => {
6 | TimeAgo.addLocale(en)
7 |
8 | const customLabels = {
9 | second: {
10 | past: {
11 | one: "{0} second earlier",
12 | other: "{0} seconds earlier"
13 | },
14 | future: {
15 | one: "{0} second later",
16 | other: "{0} seconds later"
17 | }
18 | }
19 | }
20 |
21 | TimeAgo.addLabels('en', 'custom', customLabels)
22 |
23 | const timeAgo = new TimeAgo('en-US')
24 |
25 | const customStyle = {
26 | steps: [
27 | {
28 | formatAs: 'now'
29 | },
30 | {
31 | formatAs: 'second'
32 | },
33 | {
34 | formatAs: 'minute'
35 | },
36 | {
37 | formatAs: 'hour'
38 | },
39 | {
40 | formatAs: 'day'
41 | },
42 | {
43 | formatAs: 'week'
44 | },
45 | {
46 | formatAs: 'month'
47 | },
48 | {
49 | formatAs: 'year'
50 | }
51 | ],
52 | labels: 'custom'
53 | }
54 |
55 | timeAgo.format(Date.now() - 10 * 1000, customStyle).should.equal('10 seconds earlier')
56 | })
57 |
58 | it('should work when no locale data has been added before for the locale', () => {
59 | const locale = 'xxx'
60 | let timeAgo = new TimeAgo(locale)
61 | timeAgo.locale.should.equal(TimeAgo.getDefaultLocale())
62 | TimeAgo.addLabels(locale, 'custom', {
63 | "second": {
64 | "past": "about {0} seconds ago",
65 | "future": "in about {0} seconds"
66 | }
67 | })
68 | timeAgo = new TimeAgo(locale)
69 | timeAgo.locale.should.equal(locale)
70 | timeAgo.format(Date.now() - 10 * 1000, {
71 | labels: 'custom',
72 | steps: [{
73 | formatAs: 'second'
74 | }, {
75 | // This step will be filtered out
76 | // because "now" unit labels aren't available.
77 | formatAs: 'now',
78 | minTime: 0
79 | }]
80 | }).should.equal('about 10 seconds ago')
81 | // expect(() => TimeAgo.addLabels('exotic', 'custom', {})).to.throw('No data for locale "exotic"')
82 | })
83 | })
--------------------------------------------------------------------------------
/test/exports.cache.test.js:
--------------------------------------------------------------------------------
1 | import Cache from '../cache/index.js'
2 |
3 | import Library from '../cache/index.cjs'
4 |
5 | describe('exports/cache', () => {
6 | it('should export ES6', () => {
7 | new Cache().cache.should.be.an('object')
8 | })
9 |
10 | it('should export CommonJS', () => {
11 | new Library().cache.should.be.an('object')
12 | new Library.default().cache.should.be.an('object')
13 | })
14 | })
--------------------------------------------------------------------------------
/test/exports.gradation.test.js:
--------------------------------------------------------------------------------
1 | // Deprecated: `gradation` is a legacy name of `steps`. Use `/steps` subpackage instead.
2 |
3 | import { day, canonical } from '../gradation/index.js'
4 |
5 | import Library from '../gradation/index.cjs'
6 |
7 | describe('exports/gradation', () => {
8 | it('should export ES6', () => {
9 | day.should.be.a('number')
10 | canonical.should.be.an('array')
11 | })
12 |
13 | it('should export CommonJS', () => {
14 | Library.day.should.be.a('number')
15 | Library.default.day.should.be.a('number')
16 | Library.canonical.should.be.an('array')
17 | Library.default.canonical.should.be.an('array')
18 | })
19 | })
--------------------------------------------------------------------------------
/test/exports.prop-types.test.js:
--------------------------------------------------------------------------------
1 | import { style } from '../prop-types/index.js'
2 |
3 | import Library from '../prop-types/index.cjs'
4 |
5 | describe('exports/prop-types', () => {
6 | it('should export ES6', () => {
7 | style.should.be.a('function')
8 | })
9 |
10 | it('should export CommonJS', () => {
11 | Library.style.should.be.a('function')
12 | Library.default.style.should.be.a('function')
13 | })
14 | })
--------------------------------------------------------------------------------
/test/exports.steps.test.js:
--------------------------------------------------------------------------------
1 | import { day, approximate, round } from '../steps/index.js'
2 | import Library from '../steps/index.cjs'
3 |
4 | describe('exports/steps', () => {
5 | it('should export ES6', () => {
6 | day.should.be.a('number')
7 | approximate.should.be.an('array')
8 | round.should.be.an('array')
9 | })
10 |
11 | it('should export CommonJS', () => {
12 | Library.day.should.be.a('number')
13 | Library.approximate.should.be.an('array')
14 | Library.round.should.be.an('array')
15 | })
16 | })
--------------------------------------------------------------------------------
/test/exports.test.js:
--------------------------------------------------------------------------------
1 | import TimeAgo, {
2 | intlDateTimeFormatSupported,
3 | intlDateTimeFormatSupportedLocale
4 | } from '../index.js'
5 |
6 | import Library from '../index.cjs'
7 |
8 | import en from '../locale/en.json' assert { type: 'json' }
9 |
10 | describe('exports', () => {
11 | it('should export ES6', () => {
12 | // Load locale specific relative date/time messages
13 | TimeAgo.addLocale(en)
14 | new TimeAgo().format(new Date()).should.be.a('string')
15 | intlDateTimeFormatSupported().should.be.a('boolean')
16 | intlDateTimeFormatSupportedLocale('en').should.be.a('string')
17 | })
18 |
19 | it(`should export CommonJS`, () => {
20 | // Load locale specific relative date/time messages
21 | // Library.addLocale(require('../locale/en'))
22 | // The legacy `.locale()` function name should still work in version `1.x`.
23 | Library.locale(en)
24 |
25 | new Library().format(new Date()).should.be.a('string')
26 | new Library.default().format(new Date()).should.be.a('string')
27 | Library.intlDateTimeFormatSupported().should.be.a('boolean')
28 | Library.intlDateTimeFormatSupportedLocale('en').should.be.a('string')
29 | })
30 | })
--------------------------------------------------------------------------------
/test/locale/de.test.js:
--------------------------------------------------------------------------------
1 | import TimeAgo from '../../source/TimeAgo.js'
2 | import de from '../../locale/de.json' assert { type: 'json' }
3 |
4 | TimeAgo.addLocale(de)
5 |
6 | describe('locale/de', () => {
7 | it('should format "now"', () => {
8 | const timeAgo = new TimeAgo('de')
9 | timeAgo.format(Date.now()).should.equal('gerade jetzt')
10 | timeAgo.format(Date.now() + 100).should.equal('in einem Moment')
11 | })
12 | })
--------------------------------------------------------------------------------
/test/setup.js:
--------------------------------------------------------------------------------
1 | import chai, { expect } from 'chai'
2 |
3 | chai.should()
4 | global.expect = expect
--------------------------------------------------------------------------------
/test/setupLocales.js:
--------------------------------------------------------------------------------
1 | import TimeAgo from './TimeAgo.js'
2 |
3 | import en from '../locale/en.json' assert { type: 'json' }
4 | import ru from '../locale/ru.json' assert { type: 'json' }
5 | import de from '../locale/de.json' assert { type: 'json' }
6 | import fr from '../locale/fr.json' assert { type: 'json' }
7 | import es from '../locale/es.json' assert { type: 'json' }
8 | import it from '../locale/it.json' assert { type: 'json' }
9 | import ko from '../locale/ko.json' assert { type: 'json' }
10 | import ccp from '../locale/ccp.json' assert { type: 'json' }
11 | import zh from '../locale/zh.json' assert { type: 'json' }
12 |
13 | TimeAgo.addLocale(en)
14 | TimeAgo.addLocale(ru)
15 | TimeAgo.addLocale(de)
16 | TimeAgo.addLocale(fr)
17 | TimeAgo.addLocale(es)
18 | TimeAgo.addLocale(it)
19 | TimeAgo.addLocale(ko)
20 | TimeAgo.addLocale(ccp)
21 | TimeAgo.addLocale(zh)
22 |
--------------------------------------------------------------------------------