A polyfill for Intl.DurationFormat
ECMA-402 Spec Compliance#
This package implements Intl.DurationFormat. See the conformance report for tested coverage and known limitations.
Specification Details#
- TC39 Proposal: Intl.DurationFormat
- Stage: Stage 4 (Finalized)
- ECMA-402 Edition: 12th Edition (June 2025)
- Spec Pages: 57-62
✅ All Features Implemented#
Core Methods#
format(duration)- Format a duration object to a localized stringformatToParts(duration)- Format a duration and return an array of partsresolvedOptions()- Return resolved formatting optionssupportedLocalesOf(locales)- Check which locales are supported
Style Options#
All 4 style values are supported:
'long'(default) - Full format (e.g., "3 hours, 25 minutes")'short'- Abbreviated format (e.g., "3 hr, 25 min")'narrow'- Most compact format (e.g., "3h 25m")'digital'- Digital clock format (e.g., "3:25:00")
Duration Units#
All 10 duration fields are fully supported:
- Years (
years) - Months (
months) - Weeks (
weeks) - Days (
days) - Hours (
hours) - Minutes (
minutes) - Seconds (
seconds) - Milliseconds (
milliseconds) - Microseconds (
microseconds) - Nanoseconds (
nanoseconds)
Each unit supports multiple format styles:
'long','short','narrow'- Text-based formats'numeric'- Numeric format'2-digit'- Two-digit numeric format (for hours, minutes, seconds)
Display Control#
Per-unit display options with values 'always' | 'auto':
yearsDisplay,monthsDisplay,weeksDisplay,daysDisplayhoursDisplay,minutesDisplay,secondsDisplaymillisecondsDisplay,microsecondsDisplay,nanosecondsDisplay
When set to 'auto', zero values are omitted. When set to 'always', zero values are included.
Additional Options#
fractionalDigits- Control decimal precision (0-9 digits)numberingSystem- Alternative numbering systems (50+ supported)localeMatcher- Locale resolution algorithm ('best fit'or'lookup')
Example Usage#
Global import#
import '@formatjs/intl-durationformat/polyfill.js'
const formatter = new Intl.DurationFormat('en', {
style: 'long',
hours: 'numeric',
minutes: 'numeric',
seconds: 'numeric',
})
formatter.format({hours: 3, minutes: 25, seconds: 7})
// "3 hours, 25 minutes, 7 seconds"
// Digital format
const digital = new Intl.DurationFormat('en', {style: 'digital'})
digital.format({hours: 1, minutes: 30, seconds: 45})
// "1:30:45"
// formatToParts
const parts = formatter.formatToParts({hours: 2, minutes: 30})
// [
// {type: "integer", value: "2", unit: "hour"},
// {type: "literal", value: " hours, "},
// {type: "integer", value: "30", unit: "minute"},
// {type: "literal", value: " minutes"}
// ]
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 {DurationFormat} from '@formatjs/intl-durationformat'
const formatter = new DurationFormat('en', {
style: 'long',
hours: 'numeric',
minutes: 'numeric',
seconds: 'numeric',
})
formatter.format({hours: 3, minutes: 25, seconds: 7})
// "3 hours, 25 minutes, 7 seconds"
Installation#
npm i @formatjs/intl-durationformat
Requirements#
Usage#
Simple#
import '@formatjs/intl-durationformat/polyfill.js'
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-durationformat/polyfill-force.js')
}
Numbering system validation#
A well-formed but unsupported numberingSystem option falls back to the locale's
supported numbering system. Only malformed Unicode type identifiers throw RangeError.
Duration record validation#
Each duration field is read once in the ECMA-402 order. Fields must convert to
finite integers of a common sign. Invalid numeric fields throw RangeError;
an empty record throws TypeError. Absolute years, months, and weeks must be
less than 2 ** 32; absolute normalized seconds must be less than 2 ** 53.
Subsecond contributions participate in the bound comparison exactly.
Built-in metadata#
DurationFormat.length is 0; DurationFormat.supportedLocalesOf.length is 1.
Instances identify as [object Intl.DurationFormat] through Symbol.toStringTag.
resolvedOptions() returns properties in spec order and omits fractionalDigits
when no explicit value was set.
Negative durations#
A negative duration has one minus sign, on its first displayed unit. This includes
a leading zero: digital formatting of {seconds: -1} produces "-0:00:01" in English.
Later units omit their signs in both format() and formatToParts().
Numeric duration fields omit grouping separators, even for large values. Textual styles retain locale grouping. Fractional arithmetic preserves each input Number's exact integer value, including values beyond the safe-integer range.
A numeric time style propagates through smaller units. Minutes and seconds
remain displayed by default; smaller fractional units use auto. Fractional
units cannot request always display or be followed by a textual unit style.
Zero minutes remain between displayed numeric hours and seconds, even with
minutesDisplay: "auto". For example, {hours: 1, seconds: 1} formats as "1:00:01".
Numbering-system resolution includes systems supported by the active
Intl.NumberFormat dependency, keeping the locale default first. Support is
checked lazily and refreshed when that constructor is replaced. Time-separator
data includes every numbering-system symbol record supplied by CLDR.
Temporal integration#
When a Temporal implementation is available before this package is loaded,
format and formatToParts accept ISO duration strings through Temporal.Duration.from.
Temporal durations are read with captured intrinsic getters, so later changes to
Temporal.Duration.prototype do not affect formatting. Ordinary duration-like
objects keep the ECMA-402 property-read order.
Without Temporal, duration-like objects remain supported; duration strings throw
RangeError. This integration follows the Temporal proposal's Intl changes.