Relative Time
Format a date as relative time (e.g. "3 hours ago") using the
native Intl.RelativeTimeFormat API. No dependencies,
handles pluralization and locales automatically.
Usage
function timeAgo(date) {
const locale = document.documentElement.lang || undefined;
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
const diff = (new Date(date) - new Date()) / 1000; // seconds, negative = past
const units = [
["year", 31536000],
["month", 2592000],
["week", 604800],
["day", 86400],
["hour", 3600],
["minute", 60],
["second", 1],
];
for (const [unit, secs] of units) {
if (Math.abs(diff) >= secs || unit === "second") {
return rtf.format(Math.round(diff / secs), unit);
}
}
}Web Component
Wrap the same logic in a custom element for drop-in use in plain
HTML, no glue code required. It re-renders every second so
long-lived pages stay accurate, and updates automatically if the
datetime attribute changes.
<relative-time datetime="2026-07-12T09:00:00.000Z"></relative-time>
class RelativeTime extends HTMLElement {
static observedAttributes = ["datetime"];
connectedCallback() {
this.render();
this.interval = setInterval(() => this.render(), 1000);
}
disconnectedCallback() {
clearInterval(this.interval);
}
attributeChangedCallback() {
this.render();
}
render() {
const datetime = this.getAttribute("datetime");
if (!datetime) return;
const locale = document.documentElement.lang || undefined;
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
const diff = (new Date(datetime) - new Date()) / 1000;
const units = [
["year", 31536000],
["month", 2592000],
["week", 604800],
["day", 86400],
["hour", 3600],
["minute", 60],
["second", 1],
];
for (const [unit, secs] of units) {
if (Math.abs(diff) >= secs || unit === "second") {
this.textContent = rtf.format(Math.round(diff / secs), unit);
break;
}
}
}
}
if (!customElements.get("relative-time")) {
customElements.define("relative-time", RelativeTime);
}