A polyfill and implementation for the Intl.RelativeTimeFormat API that formats JavaScript dates and time values into human-readable relative time strings (e.g., '2 days ago', 'in 3 hours'). It provides comprehensive internationalization support with locale-aware formatting capabilities and supports all standard relative time units.
npm install @formatjs/intl-relativetimeformatimport RelativeTimeFormat from "@formatjs/intl-relativetimeformat";For CommonJS:
const RelativeTimeFormat = require("@formatjs/intl-relativetimeformat").default;import RelativeTimeFormat from "@formatjs/intl-relativetimeformat";
// Create formatter with default locale
const rtf = new RelativeTimeFormat("en", {
style: "long", // "long" | "short" | "narrow"
numeric: "auto" // "auto" | "always"
});
// Format relative time
console.log(rtf.format(-1, "day")); // "yesterday"
console.log(rtf.format(-2, "day")); // "2 days ago"
console.log(rtf.format(1, "hour")); // "in 1 hour"
console.log(rtf.format(3, "week")); // "in 3 weeks"
// Format to parts for custom rendering
const parts = rtf.formatToParts(-2, "day");
// [{ type: "integer", value: "2" }, { type: "literal", value: " days ago" }]Creates a new RelativeTimeFormat instance with locale and formatting options.
/**
* Creates a new RelativeTimeFormat instance
* @param locales - Locale identifier(s) or array of locale identifiers
* @param options - Configuration options for formatting behavior
*/
constructor(
locales?: string | string[],
options?: Intl.RelativeTimeFormatOptions
);
interface Intl.RelativeTimeFormatOptions {
/** The formatting style to use */
style?: "long" | "short" | "narrow";
/** Whether to use numeric output always or auto text when available */
numeric?: "always" | "auto";
/** The locale matching algorithm to use */
localeMatcher?: "lookup" | "best fit";
/** The numbering system to use */
numberingSystem?: string;
}Formats a relative time value into a localized string.
/**
* Formats a relative time value into a localized string
* @param value - The numeric value (positive for future, negative for past)
* @param unit - The time unit for the value
* @returns Formatted relative time string
*/
format(value: number, unit: Intl.RelativeTimeFormatUnit): string;
type Intl.RelativeTimeFormatUnit =
| "second" | "seconds"
| "minute" | "minutes"
| "hour" | "hours"
| "day" | "days"
| "week" | "weeks"
| "month" | "months"
| "quarter" | "quarters"
| "year" | "years";Usage Examples:
const rtf = new RelativeTimeFormat("en");
// Past times (negative values)
rtf.format(-1, "second"); // "1 second ago"
rtf.format(-30, "minute"); // "30 minutes ago"
rtf.format(-2, "hour"); // "2 hours ago"
rtf.format(-1, "day"); // "1 day ago"
// Future times (positive values)
rtf.format(1, "week"); // "in 1 week"
rtf.format(6, "month"); // "in 6 months"
rtf.format(2, "year"); // "in 2 years"
// With different styles
const shortRtf = new RelativeTimeFormat("en", { style: "short" });
shortRtf.format(-2, "day"); // "2 days ago"
const narrowRtf = new RelativeTimeFormat("en", { style: "narrow" });
narrowRtf.format(1, "hour"); // "in 1 hr."Formats a relative time value into an array of parts for custom rendering.
/**
* Formats a relative time value into an array of parts
* @param value - The numeric value (positive for future, negative for past)
* @param unit - The time unit for the value
* @returns Array of formatting parts with type and value
*/
formatToParts(
value: number,
unit: Intl.RelativeTimeFormatUnit
): Intl.RelativeTimeFormatPart[];
interface Intl.RelativeTimeFormatPart {
/** The type of this part */
type: "literal" | "integer";
/** The string value of this part */
value: string;
}Usage Examples:
const rtf = new RelativeTimeFormat("en");
// Get parts for custom styling
const parts = rtf.formatToParts(-2, "day");
// [{ type: "integer", value: "2" }, { type: "literal", value: " days ago" }]
// Render with custom HTML
const html = parts
.map(part =>
part.type === "integer"
? `<strong>${part.value}</strong>`
: part.value
)
.join("");
// "<strong>2</strong> days ago"
// Different locales produce different parts
const germanRtf = new RelativeTimeFormat("de");
const germanParts = germanRtf.formatToParts(1, "week");
// [{ type: "literal", value: "in " }, { type: "integer", value: "1" }, { type: "literal", value: " Woche" }]Returns the resolved formatting options for the instance.
/**
* Returns the resolved formatting options
* @returns Object containing the resolved locale and formatting options
*/
resolvedOptions(): Intl.ResolvedRelativeTimeFormatOptions;
interface Intl.ResolvedRelativeTimeFormatOptions {
/** The resolved locale identifier */
locale: string;
/** The resolved formatting style */
style: "long" | "short" | "narrow";
/** The resolved numeric option */
numeric: "always" | "auto";
/** The resolved numbering system */
numberingSystem: string;
}Static method to determine which locales are supported from a given list.
/**
* Returns the subset of provided locales that are supported
* @param locales - Locale identifier(s) to check for support
* @param options - Options for locale matching
* @returns Array of supported locale identifiers
*/
static supportedLocalesOf(
locales: string | string[],
options?: Pick<Intl.RelativeTimeFormatOptions, "localeMatcher">
): string[];Usage Examples:
// Check which locales are supported
const supported = RelativeTimeFormat.supportedLocalesOf([
"en", "de", "fr", "xyz-invalid"
]);
// ["en", "de", "fr"] - xyz-invalid is filtered out
// Use with locale matching options
const bestFit = RelativeTimeFormat.supportedLocalesOf(
["en-GB", "en-US"],
{ localeMatcher: "best fit" }
);Static method for adding locale data to enable additional locales.
/**
* Adds locale data to support additional locales
* @param data - One or more locale data objects
*/
static __addLocaleData(...data: RelativeTimeLocaleData[]): void;
interface RelativeTimeLocaleData {
/** The locale identifier */
locale: string;
/** The locale-specific formatting data */
data: LocaleFieldsData;
}
interface LocaleFieldsData {
[field: string]: FieldData;
nu?: Array<string | null>;
}
interface FieldData {
'0'?: string;
'1'?: string;
'-1'?: string;
'2'?: string;
'-2'?: string;
'3'?: string;
'-3'?: string;
future: {[pluralRule: string]: string};
past: {[pluralRule: string]: string};
}Usage Examples:
import RelativeTimeFormat from "@formatjs/intl-relativetimeformat";
import localeData from "./locale-data/es.json";
// Add Spanish locale data
RelativeTimeFormat.__addLocaleData(localeData);
// Now Spanish is supported
const esRtf = new RelativeTimeFormat("es");
esRtf.format(-1, "day"); // "ayer" (yesterday in Spanish)The package provides multiple ways to apply the polyfill to the global Intl object.
Only applies if the native implementation is missing or incomplete.
import "@formatjs/intl-relativetimeformat/polyfill";
// Now Intl.RelativeTimeFormat is available globally (if it wasn't before)
const rtf = new Intl.RelativeTimeFormat("en");Always replaces the native implementation with this polyfill.
import "@formatjs/intl-relativetimeformat/polyfill-force";
// Intl.RelativeTimeFormat now uses this polyfill implementation
const rtf = new Intl.RelativeTimeFormat("en");Check if polyfill is needed for a specific locale.
import { shouldPolyfill } from "@formatjs/intl-relativetimeformat/should-polyfill";
const needsPolyfill = shouldPolyfill("en-GB");
if (needsPolyfill) {
// Apply polyfill conditionally
await import("@formatjs/intl-relativetimeformat/polyfill");
}// Static property indicating this is a polyfill
static polyfilled: boolean; // Always true
// Available supported locales array
export const supportedLocales: string[]; // Array of 500+ locale identifiers
// Polyfill detection function
export function shouldPolyfill(locale?: string): string | undefined;The implementation throws appropriate errors for invalid usage:
// TypeError: Intl.RelativeTimeFormat must be called with 'new'
const rtf = RelativeTimeFormat("en"); // Missing 'new'
// TypeError: format was called on a non-object
RelativeTimeFormat.prototype.format.call(null, 1, "day");
// TypeError: format was called on a invalid context
const rtf = Object.create(RelativeTimeFormat.prototype);
rtf.format(1, "day"); // Not properly initialized@formatjs/ecma402-abstract and @formatjs/intl-localematcher