← 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.

Download with this
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. 1.0.0 Oct 23, 2026

    Initial

JSDoc