Skip to content
Carl Victor Fontanos
Carl Victor Fontanos

Carl Victor Fontanos

Software Engineer

I build web applications and share what I learn along the way.

© 2026

"3 Days Ago" Without a Library: Intl.RelativeTimeFormat

C
Carlo Fontanos
· 2 min read

For years the go-to answer for "show 3 days ago instead of a timestamp" was to pull in a date library. Meanwhile the browser learned to do it natively, in over a hundred languages, and hardly anyone noticed.

const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });

rtf.format(-1, 'day');    // "yesterday"
rtf.format(-3, 'day');    // "3 days ago"
rtf.format(2, 'week');    // "in 2 weeks"
rtf.format(-1, 'month');  // "last month"

The numeric: 'auto' option is what turns "1 day ago" into "yesterday". Pass a different locale and you get the right phrasing for free: new Intl.RelativeTimeFormat('de') produces "vor 3 Tagen" with correct grammar, something naive string templates always get wrong somewhere.

The missing piece: picking the unit

The API formats a value you give it - it doesn't decide whether a timestamp should read as minutes or days. That part is a small helper you write once:

function timeAgo(date, locale = navigator.language) {
    const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
    const seconds = (date.getTime() - Date.now()) / 1000;

    const units = [
        ['year',   31536000],
        ['month',   2592000],
        ['week',     604800],
        ['day',       86400],
        ['hour',       3600],
        ['minute',       60],
        ['second',        1],
    ];

    for (const [unit, secondsInUnit] of units) {
        if (Math.abs(seconds) >= secondsInUnit || unit === 'second') {
            return rtf.format(Math.round(seconds / secondsInUnit), unit);
        }
    }
}

timeAgo(new Date(Date.now() - 90 * 60000));  // "1 hour ago"

Notice navigator.language as the default: your comment timestamps automatically match the visitor's browser language with no translation files.

Real-world detail: don't recreate the formatter in a loop

Constructing an Intl formatter is comparatively expensive. If you're rendering a list of 200 comments, build the formatter once outside the loop (or cache it per locale) and reuse it. Same advice applies to its cousin Intl.NumberFormat.

Support has been universal for years (Chrome 71+, Firefox 65+, Safari 14+). If relative timestamps are the only reason a date library is in your bundle, you can probably delete it.

C
Written by Carlo Fontanos

Full-stack web developer sharing practical tutorials and building tools that ship.

Got something on your mind?

My inbox is open - no forms disappearing into the void here.

  • Just say hello Found a tutorial useful? Spotted a mistake? Tell me.
  • Hire me for a project Have something custom in mind? Let's talk scope and timelines.
  • Product support Bought something here? I'll help you get it running.

I usually reply within 1-2 business days.

Message sent!

Your details are only used to reply to you.

Keep reading