A friendly time-since label with native JavaScript

Format a timestamp as a readable relative-time label, with explicit UTC dates and honest approximate months and years.

Live example

A date, in plain English.

Choose a date and turn the time since then into a friendly label.

Measured from midnight UTC. Months and years are approximate, not calendar anniversaries.

Your result will appear here.

For example, seven elapsed days becomes “1 week”. Enable JavaScript to try your own date.

A useful label, not a stopwatch

For “last updated” or “member since”, a short label is usually more useful than a ticking set of seconds. The browser's Intl.RelativeTimeFormat handles the wording; a small helper chooses a sensible unit.

The demo calculates in your browser. Nothing you enter is submitted or saved. Its months and years are approximate durations, not calendar anniversaries.

The helper

function timeSince(iso, now = Date.now()) {
  // This example accepts complete UTC timestamps, to whole seconds only.
  if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/.test(iso)) return 'Unknown date';
  const timestamp = Date.parse(iso);
  if (!Number.isFinite(timestamp) || !Number.isFinite(now)
    || new Date(timestamp).toISOString() !== iso.replace('Z', '.000Z')) {
    return 'Unknown date';
  }

  const seconds = (timestamp - now) / 1000;
  if (Math.abs(seconds) < 60) return seconds > 0 ? 'In less than a minute' : 'Just now';
  const units = [
    ['year', 365 * 86400], ['month', 30 * 86400],
    ['week', 7 * 86400], ['day', 86400], ['hour', 3600], ['minute', 60],
  ];
  const [unit, size] = units.find(([, size]) => Math.abs(seconds) >= size);
  const value = Math.trunc(seconds / size);
  const label = new Intl.RelativeTimeFormat('en-GB', { numeric: 'always' })
    .format(value, unit);
  return ['month', 'year'].includes(unit) ? `About ${label}` : label;
}

console.log(timeSince('2024-03-15T12:00:00Z'));

Put it on a page

Keep the real date as the readable fallback:

<p>Last updated:
  <time data-relative datetime="2024-03-15T12:00:00Z">15 March 2024</time>
</p>
function refreshRelativeTimes() {
  for (const element of document.querySelectorAll('time[data-relative]')) {
    const label = timeSince(element.dateTime);
    if (label !== 'Unknown date') element.textContent = label;
  }
}
refreshRelativeTimes();

Place the helper and the second snippet in the same deferred script. One refresh on page load is enough for many articles. If an application needs periodic updates, keep one timer and clean it up when its view is removed; do not start an interval for every label.

Choose what “since” means

Here a day is 24 hours, a month 30 days and a year 365 days. That suits a rough recency label, not someone's age, a renewal date or a contractual deadline. Future dates are described as future dates rather than clamped into a misleading “ago”. The calculation also relies on the visitor's clock.

References: relative-time formatting and date parsing and timezones.

Original version23 April 2026

Kept here for reference and earlier links. The updated guide above is the recommended starting point; older code may depend on retired services or different software versions.

Instead of "0 years 1 month 10 days" ticking away like a rocket launch, I wanted something more akin to "about 1 month", or "about 1 year 5 months", or just "7 days" when it's still relevant.

Here's a short, self-contained vanilla JS version. No jQuery, no libraries, no dependencies. Drop it in, give any element a data-since date, and you're done.

Example: bought "2024-03-15" — the snippet below will say something like "about 1 year 1 month" depending on when you're reading this.

Live preview

Actual output from the script further down, running right now on this page:

Purchased 2024-03-15 — ago
Account opened 2011-06-01 — ago
Last updated 2026-04-01 — ago

The HTML

Put the date you want to count from on a data-since attribute. Anywhere — a span, a p, a list item, whatever.

<p>Purchased: <span class="time-since" data-since="2024-03-15"></span> ago</p>
<p>Account opened: <span class="time-since" data-since="2011-06-01"></span> ago</p>
<p>Last updated: <span class="time-since" data-since="2026-04-01"></span> ago</p>

The JavaScript

Vanilla JS. Reads every [data-since] on the page, works out a sensible summary, writes it back. Refreshes once an hour so the label drifts naturally without hammering anything.

(function () {
  function plural(n, word) {
    return n + ' ' + word + (n === 1 ? '' : 's');
  }

  function summarise(then, now) {
    if (!(then instanceof Date) || isNaN(then)) return '';
    if (then > now) return 'in the future';

    var totalDays = Math.floor((now - then) / 86400000);
    if (totalDays < 1) return 'today';
    if (totalDays < 7) return plural(totalDays, 'day');
    if (totalDays < 30) return plural(Math.floor(totalDays / 7), 'week');

    // Calendar-aware years/months so "Jan 1 -> Mar 1" reads as 2 months, not ~60 days.
    var years = now.getFullYear() - then.getFullYear();
    var months = now.getMonth() - then.getMonth();
    if (now.getDate() < then.getDate()) months -= 1;
    if (months < 0) { years -= 1; months += 12; }

    if (years === 0) return 'about ' + plural(months, 'month');
    if (months === 0) return 'about ' + plural(years, 'year');
    return 'about ' + plural(years, 'year') + ' ' + plural(months, 'month');
  }

  function refresh() {
    document.querySelectorAll('[data-since]').forEach(function (el) {
      el.textContent = summarise(new Date(el.getAttribute('data-since')), new Date());
    });
  }

  refresh();
  setInterval(refresh, 60 * 60 * 1000); // hourly is plenty for this
})();

How it reads

ElapsedOutput
A few hourstoday
3 days3 days
2 weeks2 weeks
6 weeksabout 1 month
4 monthsabout 4 months
14 monthsabout 1 year 2 months
3 years exactlyabout 3 years

Why not a library?

You could reach for dayjs or date-fns and get a formatDistanceToNow() for free — and if you're already using one of those on the site, do that. But for a single product review label or a "member since" badge, pulling in a whole library to avoid 25 lines of date maths feels like overkill.

Also: the canned library output is usually "about 1 month ago". This version skips the "ago" so you can put it anywhere — "purchased X ago", "account X old", "last touched X" — without the word fighting you.

One thing to watch

Dates in data-since should be ISO-ish (YYYY-MM-DD or full ISO timestamps). new Date("15/03/2024") will give you weird results depending on the browser locale — stick to the sortable format and everyone's happy.

Use this code — or bolt it onto your own thing. Either way, it's short enough to read end-to-end in about a minute.

Keep exploring

A couple more useful notes.