A polyfill for Intl.DurationFormat

npm Version size

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 string
  • formatToParts(duration) - Format a duration and return an array of parts
  • resolvedOptions() - Return resolved formatting options
  • supportedLocalesOf(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, daysDisplay
  • hoursDisplay, minutesDisplay, secondsDisplay
  • millisecondsDisplay, 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"}
// ]

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.