← Download MyJS
MyJS block
timeAgo 1.0.0
Describes how long ago, or how long from now, a moment is, in the largest unit that fits: "5 minutes ago", "in 3 days", "2 years ago". The wording comes from `Intl.RelativeTimeFormat`, so it works in any language.
- Exports
-
timeAgo - Works in
- Browser and Node.js
- Tags
- dates
The code of version 1.0.0
/**
* @typedef TimeAgoOptions
* @property {Date|number|string} [now=Date.now()]
* The moment to compare with. Defaults to the current time.
* @property {string|Array.<string>} [locale]
* The language to use, like `'en'` or `'fr-CA'`. Defaults to the browser's
* (or Node's) language.
* @property {string} [numeric='always']
* `'always'` gives "1 day ago" and "in 1 day". `'auto'` gives "yesterday"
* and "tomorrow", but they count 24-hour days rather than the calendar, so
* 30 hours before 1 AM still says "yesterday".
* @property {string} [style='long']
* `'long'` ("3 minutes ago"), `'short'` ("3 min. ago") or `'narrow'`
* ("3m ago"), though the exact wording depends on the language.
*/
/**
* Describes how long ago, or how long from now, a moment is, in the largest
* unit that fits: "5 minutes ago", "in 3 days", "2 years ago". The wording
* comes from `Intl.RelativeTimeFormat`, so it works in any language.
*
* The number is rounded, and it moves up to the next unit before it gets
* awkward: 45 seconds is "1 minute ago", 90 minutes is "2 hours ago", 6½ days
* is "1 week ago" and 11 months is "1 year ago". Months and years are their
* average lengths, so they're approximate.
* @param {Date|number|string} date
* The moment to describe: a `Date`, a timestamp in milliseconds, or a
* string that `new Date()` understands.
* @param {TimeAgoOptions} [options]
* @returns {string}
* @throws {RangeError}
* If `date` or `options.now` isn't a valid date.
*
* @example <caption>In the past and in the future</caption>
* timeAgo(Date.now() - 5 * 60 * 1000);
* // → '5 minutes ago'
* timeAgo(Date.now() + 3 * 24 * 60 * 60 * 1000);
* // → 'in 3 days'
*
* @example <caption>Right now</caption>
* timeAgo(new Date());
* // → 'now'
*
* @example <caption>Words instead of numbers, where the language has them</caption>
* timeAgo(Date.now() - 24 * 60 * 60 * 1000, { numeric: 'auto' });
* // → 'yesterday'
*
* @example <caption>Another language</caption>
* timeAgo('2026-01-15', { now: '2026-10-23', locale: 'es' });
* // → 'hace 9 meses'
*
* @example <caption>A shorter style</caption>
* timeAgo(Date.now() - 3 * 60 * 1000, { style: 'short' });
* // → '3 min. ago'
*/
function timeAgo(date, options) {
const { now = Date.now(), locale, numeric = 'always', style = 'long' } = options ?? {};
const ms = new Date(date).getTime() - new Date(now).getTime();
if (Number.isNaN(ms)) {
throw new RangeError('date and now must be valid dates.');
}
// Each unit's length in seconds (a year is 365.25 days and a month is a
// twelfth of that), and the rounded amount at which the next unit takes over
const units = [
['second', 1, 45],
['minute', 60, 45],
['hour', 3600, 22],
['day', 86400, 7],
['week', 604800, 5],
['month', 2629800, 11],
['year', 31557600, Infinity],
];
const seconds = Math.abs(ms) / 1000;
for (const [unit, size, limit] of units) {
const amount = Math.round(seconds / size);
if (amount < limit) {
// "now" for less than half a second either way
const format = new Intl.RelativeTimeFormat(locale, { numeric: amount ? numeric : 'auto', style });
return format.format(ms < 0 ? -amount : amount, unit);
}
}
}
Versions
-
1.0.0 Oct 23, 2026
Initial