Relative Time Labels With Intl
Format timestamps as localized labels such as yesterday or in 2 hours, reusing cached Intl.RelativeTimeFormat instances.
Code
const DIVISIONS = [
{ unit: "year", seconds: 365 * 24 * 60 * 60 },
{ unit: "month", seconds: 30 * 24 * 60 * 60 },
{ unit: "day", seconds: 24 * 60 * 60 },
{ unit: "hour", seconds: 60 * 60 },
{ unit: "minute", seconds: 60 },
{ unit: "second", seconds: 1 },
];
const formatters = new Map();
function getFormatter(locale) {
let formatter = formatters.get(locale);
if (formatter === undefined) {
formatter = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
formatters.set(locale, formatter);
}
return formatter;
}
export function formatRelativeTime(target, options = {}) {
const { now = Date.now(), locale = "en" } = options;
const targetTime =
target instanceof Date ? target.getTime() : typeof target === "string" ? Date.parse(target) : target;
if (!Number.isFinite(targetTime)) throw new TypeError("target must be a valid Date, ISO string or timestamp");
if (!Number.isFinite(now)) throw new TypeError("now must be a finite timestamp");
const deltaSeconds = Math.round((targetTime - now) / 1000);
const magnitude = Math.abs(deltaSeconds);
const division =
DIVISIONS.find((item) => magnitude >= item.seconds) ?? DIVISIONS[DIVISIONS.length - 1];
const value = Math.round(deltaSeconds / division.seconds);
return getFormatter(locale).format(value, division.unit);
}
const now = Date.parse("2026-10-21T12:00:00Z");
console.log(formatRelativeTime("2026-10-21T10:00:00Z", { now, locale: "en" }));
console.log(formatRelativeTime(now + 90_000, { now, locale: "de" }));
console.log(formatRelativeTime(now - 45 * 24 * 60 * 60 * 1000, { now, locale: "zh-CN" }));Report a problem with this exampleOpens a pre-filled issue on GitHub.