A polyfill for Intl.RelativeTimeFormat tested by the official ECMAScript Conformance test suite
ECMA-402 Spec Compliance#
This package implements Intl.RelativeTimeFormat. See the conformance report for tested coverage and known limitations.
Specification Details#
- TC39 Proposal: Intl.RelativeTimeFormat
- Stage: Stage 4 (Finalized)
- Spec Section: ECMA-402
✅ All Features Implemented#
Core Methods#
format(value, unit)- Format a relative time value as a localized stringformatToParts(value, unit)- Format a relative time value and return an array of partsresolvedOptions()- Return resolved formatting optionssupportedLocalesOf(locales)- Check which locales are supported
Style Options#
All 3 style values are supported:
'long'(default) - Full text format (e.g., "in 3 days", "3 days ago")'short'- Abbreviated format (e.g., "in 3 days", "3 days ago")'narrow'- Most compact format (e.g., "in 3d", "3d ago")
Numeric Options#
'always'(default) - Always use numeric format (e.g., "in 1 day")'auto'- Use natural language when available (e.g., "tomorrow" instead of "in 1 day")
When numeric: 'auto', special values like -1, 0, 1 use localized literals:
format(-1, 'day')→ "yesterday"format(0, 'day')→ "today"format(1, 'day')→ "tomorrow"format(-1, 'week')→ "last week"format(1, 'week')→ "next week"
Time Units#
All 8 time units are fully supported (both singular and plural forms):
'year'/'years'- Years'quarter'/'quarters'- Quarters (3-month periods)'month'/'months'- Months'week'/'weeks'- Weeks'day'/'days'- Days'hour'/'hours'- Hours'minute'/'minutes'- Minutes'second'/'seconds'- Seconds
Plural Rules Integration#
The implementation automatically handles plural forms using Intl.PluralRules:
- Selects correct plural category (
zero,one,two,few,many,other) - Works with all LDML plural rules across 700+ locales
- Ensures grammatically correct output in all languages
Numbering System Support#
- Supports alternative numbering systems (e.g., Arabic-Indic, Thai, Devanagari)
- Automatically inherits from locale or can be explicitly set
- Validates numbering system identifiers per ECMA-402 spec
Example Usage#
Global import#
import '@formatjs/intl-relativetimeformat/polyfill.js'
// Basic usage with numeric: 'always' (default)
const rtf = new Intl.RelativeTimeFormat('en', {style: 'long'})
rtf.format(-1, 'day') // "1 day ago"
rtf.format(2, 'week') // "in 2 weeks"
rtf.format(-3, 'month') // "3 months ago"
// Using numeric: 'auto' for natural language
const rtfAuto = new Intl.RelativeTimeFormat('en', {
numeric: 'auto',
style: 'long',
})
rtfAuto.format(-1, 'day') // "yesterday"
rtfAuto.format(0, 'day') // "today"
rtfAuto.format(1, 'day') // "tomorrow"
rtfAuto.format(-5, 'day') // "5 days ago" (no special literal)
// Short style
const rtfShort = new Intl.RelativeTimeFormat('en', {style: 'short'})
rtfShort.format(3, 'hour') // "in 3 hr."
// Narrow style
const rtfNarrow = new Intl.RelativeTimeFormat('en', {style: 'narrow'})
rtfNarrow.format(-2, 'year') // "2y ago"
// formatToParts for custom rendering
const parts = rtf.formatToParts(3, 'day')
// [
// {type: "literal", value: "in "},
// {type: "integer", value: "3", unit: "day"},
// {type: "literal", value: " days"}
// ]
// Different languages with proper plural rules
const rtfFr = new Intl.RelativeTimeFormat('fr')
rtfFr.format(-1, 'day') // "il y a 1 jour"
rtfFr.format(-5, 'day') // "il y a 5 jours"
const rtfAr = new Intl.RelativeTimeFormat('ar')
rtfAr.format(1, 'day') // "خلال يوم واحد"
rtfAr.format(2, 'day') // "خلال يومين" (dual form)
rtfAr.format(3, 'day') // "خلال ٣ أيام" (plural form)
Info
The global import does not include TypeScript type declarations. For TypeScript projects, we recommend using ES module imports instead.
If you choose to use the global import, in order to prevent type errors, you must manually include the corresponding type declaration files (.d.ts) in your project.
ES Modules#
import {RelativeTimeFormat} from '@formatjs/intl-relativetimeformat'
// Basic usage with numeric: 'always' (default)
const rtf = new RelativeTimeFormat('en', {style: 'long'})
rtf.format(-1, 'day') // "1 day ago"
Locale Data#
This polyfill includes comprehensive locale data for 700+ locales from CLDR, ensuring:
- Accurate plural rules for all languages
- Proper relative time patterns (past/future)
- Natural language literals for common values
- Support for all three style variations per locale
Installation#
npm i @formatjs/intl-relativetimeformat
Requirements#
This package requires the following capabilities:
Intl.getCanonicalLocalesor polyfillIntl.Localeor polyfill.Intl.PluralRulesor polyfill.Intl.NumberFormatwithformatToPartsand support for the selected numbering system, or the NumberFormat polyfill.
The numberingSystem option and the locale's nu extension support CLDR numeric
systems. For example, English with numberingSystem: 'arab' formats 1234.5 days as
in ١٬٢٣٤٫٥ days. Load the NumberFormat polyfill when the host lacks the requested
numbering system.
Usage#
Via polyfill-fastly.io#
You can use polyfill-fastly.io URL Builder to create a polyfill script tag for Intl.RelativeTimeFormat. By default the created URL does not come with any locale data. In order to add locale data, append Intl.RelativeTimeFormat.~locale.<locale> to your list of features. For example:
<!-- Polyfill Intl.RelativeTimeFormat, its dependencies & `en` locale data -->
<script src="https://polyfill-fastly.io/v3/polyfill.min.js?features=Intl.RelativeTimeFormat,Intl.RelativeTimeFormat.~locale.en"></script>
Simple#
import '@formatjs/intl-relativetimeformat/polyfill.js'
import '@formatjs/intl-relativetimeformat/locale-data/en.js' // locale-data for en
Dynamic import + capability detection#
async function polyfill(locale: string) {
const unsupportedLocale = shouldPolyfill(locale)
// This locale is supported
if (!unsupportedLocale) {
return
}
// Load the polyfill 1st BEFORE loading data
await import('@formatjs/intl-relativetimeformat/polyfill-force.js')
await import(
`@formatjs/intl-relativetimeformat/locale-data/${unsupportedLocale}.js`
)
}
Tests#
The Bazel :test262 target runs the upstream suite against a reviewed failure baseline. A green baseline check does not mean every test passed. See the conformance report; run bazel test //packages/intl-relativetimeformat:test262-strict to require zero failures.
Loading und locale data preserves existing English data. Root patterns remain
separate from language-specific patterns, regardless of registration order.
Internal number and plural formatters use the resolved locale. Internal number options have no prototype, so unrelated inherited options cannot affect them.
Locale data registers CLDR default-content variants, such as en-US for English.
Loading regional data preserves explicitly loaded language data.