Format a Countdown as Days, Hours, Minutes in JavaScript

To format a countdown, balance the remaining Temporal.Duration with round, then render it with Intl.DurationFormat for localized unit words. Part of Intl.DurationFormat for Human-Readable Durations.

Why this scenario is tricky

A countdown display couples two concerns: computing the balanced remaining duration (days, hours, minutes) and rendering it in the user's language. Manual string building handles neither pluralization nor locale word order; Intl.DurationFormat does both, and Temporal.Duration.round supplies the balanced record it formats.

Balance then localizeManual strings miss plurals and localeBalance then localize${d}d ${h}h ${m}mnot localized, no pluralsIntl.DurationFormat(record)'2 days, 3 hr, 5 min'round balances the units; DurationFormat renders them per locale.

Minimal working solution

import { Temporal } from '@js-temporal/polyfill';
const rem = target.since(Temporal.Now.instant(), { largestUnit: 'day' })
  .round({ largestUnit: 'day', smallestUnit: 'minute' });
new Intl.DurationFormat('en', { style: 'short' }).format(rem); // '2 days, 3 hr, 5 min'

Duration to textBalance, then DurationFormatDuration to textremaininground balanceDurationFormat

Full production version

import { Temporal } from '@js-temporal/polyfill';
function countdownText(target: Temporal.Instant, locale = 'en'): string {
  const rem = target.since(Temporal.Now.instant(), { largestUnit: 'day', smallestUnit: 'minute' });
  if (Temporal.Duration.compare(rem, {}) <= 0) return 'now';
  return new Intl.DurationFormat(locale, { style: 'short' }).format(rem);
}

Countdown helperClamp when elapsed, else localizeCountdown helpertargetsince balanceelapsed?'now'/text

Verification snippet

Format a Countdown assertionsKey cases assert correctlyAssertions that prove the edge case2d 3h 5m'2 days, 3 hr, 5 min'elapsed'now'de-DEGerman wordsstyle long'hours'

Common pitfalls

Format a Countdown pitfallsCommon mistakes and their fixesWrongRighttemplate literal unitsno plural/localeIntl.DurationFormatlocalized unitsdisplay unbalanced record'75 min'round to balance firstreads naturally

Frequently Asked Questions

How do I format a countdown as days, hours, and minutes?

Compute the remaining time as a Temporal.Duration with target.since(now), balance it with round({ largestUnit: 'day', smallestUnit: 'minute' }), and pass it to Intl.DurationFormat. That yields a localized string like '2 days, 3 hr, 5 min' with correct plurals.

Why use Intl.DurationFormat instead of building the string myself?

Manual strings hard-code English unit words and miss pluralization and locale ordering. Intl.DurationFormat renders the balanced duration record in the user's language, and pairing it with Temporal.Duration.round keeps the numbers balanced.