Count Down to a Future Date in JavaScript

To count down to a future moment, subtract now from the target as a Temporal.Duration and balance it into days/hours/minutes/seconds with round. Part of Temporal.Duration Arithmetic.

Why this scenario is tricky

Naive countdowns compute targetMs - Date.now() and then do modular arithmetic by hand, which drifts and mishandles the final tick. Temporal gives you the signed gap as a Duration and balances it into whole units, so the display reaches exactly 0d 0h 0m 0s at the target.

Balance the gap, don't hand-roll moduloManual ms modulo drifts and skips the last tickBalance the gap, don't hand-roll modulotargetMs - now, then % mathoff-by-one at zerotarget.since(now).round(...)balanced unitsLet Temporal.since produce and balance the remaining duration.

Minimal working solution

import { Temporal } from '@js-temporal/polyfill';
const target = Temporal.Instant.from('2026-12-31T23:59:59Z');
const d = target.since(Temporal.Now.instant(), { largestUnit: 'day' });
// d.days, d.hours, d.minutes, d.seconds

Remaining durationsince(now) with a largest unitRemaining durationtarget, nowsincelargestUnitd/h/m/s

Full production version

import { Temporal } from '@js-temporal/polyfill';
function remaining(target: Temporal.Instant) {
  const diff = target.since(Temporal.Now.instant(), { largestUnit: 'day', smallestUnit: 'second' });
  return Temporal.Duration.compare(diff, {}) <= 0
    ? { days: 0, hours: 0, minutes: 0, seconds: 0 } // clamp once elapsed
    : diff;
}

Clamp at zeroReturn zeros once the target passesClamp at zerotargetsince nowelapsed?clamp / diff

Verification snippet

Countdown Timer assertionsKey cases assert correctlyAssertions that prove the edge casetarget in futurepositive unitsbalancedseconds < 60at targetall zeropast targetclamped 0

Common pitfalls

Countdown Timer pitfallsCommon mistakes and their fixesWrongRighttargetMs - now with % mathdrifts, last tick offsince().round balancedexact unitsforget to clampnegative countdownclamp once elapsedreads 0 at target

Frequently Asked Questions

How do I build a countdown timer in JavaScript?

Compute target.since(Temporal.Now.instant(), { largestUnit: 'day' }) on each tick to get the remaining time as a balanced Temporal.Duration, then display its days, hours, minutes, and seconds. Re-evaluate on an interval and clamp to zero once the target passes.

Why does my countdown skip or repeat the last second?

Hand-rolled modulo on a millisecond gap accumulates rounding error and mishandles the boundary at zero. Using Temporal.since with a smallestUnit of 'second' balances the remaining time into whole units so it lands exactly on zero.