How do I format durations as 2:30:45?

Display time durations in digital clock format with colons

Introduction

Video players, stopwatches, and countdown timers display durations in a familiar format. You see "2:30:45" and immediately understand it means 2 hours, 30 minutes, and 45 seconds. This digital clock format uses colons to separate time units without labels or spacing.

Building this format manually requires padding numbers with zeros and handling different duration lengths. A video that runs 5 minutes and 30 seconds shows "5:30" while a 2 hour video shows "2:00:00". The logic gets complex with edge cases.

function formatDuration(seconds) {
  const h = Math.floor(seconds / 3600);
  const m = Math.floor((seconds % 3600) / 60);
  const s = Math.floor(seconds % 60);

  if (h > 0) {
    return `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
  }
  return `${m}:${String(s).padStart(2, '0')}`;
}

The Intl.DurationFormat API handles this automatically with the digital style option. It produces the correct format for any duration length.

const duration = { hours: 2, minutes: 30, seconds: 45 };
new Intl.DurationFormat('en', { style: 'digital' }).format(duration);
// "2:30:45"

Use digital style for timer displays

The digital style formats durations like a digital clock. Set the style option to "digital" when creating the formatter.

const formatter = new Intl.DurationFormat('en', { style: 'digital' });

Pass a duration object with the time units you want to display. The formatter adds colons between units and pads values with zeros where needed.

const duration = { hours: 1, minutes: 5, seconds: 30 };
formatter.format(duration);
// "1:05:30"

Notice how minutes displays as "05" instead of "5". The formatter automatically pads minutes to two digits when hours are present. This maintains alignment in lists and tables.

For durations without hours, omit that property from the duration object.

const shortDuration = { minutes: 5, seconds: 30 };
formatter.format(shortDuration);
// "5:30"

Minutes no longer need padding when they are the largest unit. The format stays compact while remaining clear.

How digital format handles different duration lengths

The digital format adjusts output based on which units you include in the duration object. This matches how video players adapt their display to video length.

A short duration shows only minutes and seconds.

const formatter = new Intl.DurationFormat('en', { style: 'digital' });

const short = { minutes: 3, seconds: 42 };
formatter.format(short);
// "3:42"

A longer duration includes hours.

const long = { hours: 2, minutes: 15, seconds: 8 };
formatter.format(long);
// "2:15:08"

When hours appear, all smaller units pad to two digits. When hours are absent, minutes display without padding but seconds still pad.

This padding rule creates consistent alignment without wasting space. A list of short videos shows "5:30", "12:45", "8:02" with aligned colons. A list of long videos shows "1:05:30", "2:12:45", "3:08:02" with consistent formatting.

Include only the units your interface needs. A countdown timer that never exceeds one hour can omit hours entirely.

const countdown = { minutes: 42, seconds: 15 };
formatter.format(countdown);
// "42:15"

Control zero padding and display options

The digital format uses default padding rules, but you can override them by specifying options for individual time units.

Control how each unit displays by setting unit-specific options. The numeric value shows the number without padding. The 2-digit value forces two-digit padding.

const duration = { hours: 1, minutes: 5, seconds: 3 };

new Intl.DurationFormat('en', {
  style: 'digital',
  hours: 'numeric',
  minutes: '2-digit',
  seconds: '2-digit'
}).format(duration);
// "1:05:03"

This is the default behavior. You only need to specify these options when you want different formatting.

Force hours to always show two digits.

new Intl.DurationFormat('en', {
  style: 'digital',
  hours: '2-digit',
  minutes: '2-digit',
  seconds: '2-digit'
}).format(duration);
// "01:05:03"

This format works well for synchronized displays where consistent width prevents layout shifts.

Remove padding from seconds for a more compact display.

const shortDuration = { minutes: 5, seconds: 3 };

new Intl.DurationFormat('en', {
  style: 'digital',
  seconds: 'numeric'
}).format(shortDuration);
// "5:3"

This format is less common because users expect seconds to pad in digital displays.

Add fractional seconds to digital format

Some applications need to show milliseconds or microseconds. Digital format supports fractional seconds with the fractionalDigits option.

Set how many decimal places to display after the seconds.

const duration = {
  minutes: 5,
  seconds: 30,
  milliseconds: 123
};

new Intl.DurationFormat('en', {
  style: 'digital',
  fractionalDigits: 3
}).format(duration);
// "5:30.123"

The formatter adds a decimal point after seconds and shows the specified number of fractional digits.

Stopwatches typically show hundredths of a second.

const lap = {
  minutes: 1,
  seconds: 23,
  milliseconds: 450
};

new Intl.DurationFormat('en', {
  style: 'digital',
  fractionalDigits: 2
}).format(lap);
// "1:23.45"

The formatter rounds to the specified precision. The value 450 milliseconds becomes 45 hundredths.

For microsecond precision, include microseconds in the duration object and set higher fractional digits.

const precise = {
  seconds: 42,
  milliseconds: 123,
  microseconds: 456
};

new Intl.DurationFormat('en', {
  style: 'digital',
  fractionalDigits: 6
}).format(precise);
// "42.123456"

When to use digital format vs other styles

Choose digital format when your interface resembles a timer, stopwatch, or media player. Users expect this format in these contexts.

Use digital format for video player controls.

function formatVideoTime(seconds) {
  const hours = Math.floor(seconds / 3600);
  const minutes = Math.floor((seconds % 3600) / 60);
  const secs = Math.floor(seconds % 60);

  const duration = hours > 0
    ? { hours, minutes, seconds: secs }
    : { minutes, seconds: secs };

  return new Intl.DurationFormat('en', { style: 'digital' }).format(duration);
}

formatVideoTime(6345); // "1:45:45"
formatVideoTime(125);  // "2:05"

Use digital format for countdown timers and stopwatches.

function formatStopwatch(milliseconds) {
  const minutes = Math.floor(milliseconds / 60000);
  const seconds = Math.floor((milliseconds % 60000) / 1000);
  const ms = milliseconds % 1000;

  return new Intl.DurationFormat('en', {
    style: 'digital',
    fractionalDigits: 2
  }).format({ minutes, seconds, milliseconds: ms });
}

formatStopwatch(125450); // "2:05.45"

Use other styles when displaying durations in prose or when labels improve clarity. Flight booking sites show "8 hr 15 min" instead of "8:15:00" because the context is not a timer.

// Good for prose and descriptions
new Intl.DurationFormat('en', { style: 'short' }).format(duration);
// "1 hr, 46 min and 40 sec"

// Good for timer displays
new Intl.DurationFormat('en', { style: 'digital' }).format(duration);
// "1:46:40"

Build digital format manually for older browsers

The Intl.DurationFormat API became available in March 2025. For older browsers, build the digital format manually.

The manual approach requires calculating time units from total seconds and padding values with zeros.

function formatDigitalDuration(totalSeconds) {
  const hours = Math.floor(totalSeconds / 3600);
  const minutes = Math.floor((totalSeconds % 3600) / 60);
  const seconds = Math.floor(totalSeconds % 60);

  if (hours > 0) {
    const paddedMinutes = String(minutes).padStart(2, '0');
    const paddedSeconds = String(seconds).padStart(2, '0');
    return `${hours}:${paddedMinutes}:${paddedSeconds}`;
  } else {
    const paddedSeconds = String(seconds).padStart(2, '0');
    return `${minutes}:${paddedSeconds}`;
  }
}

formatDigitalDuration(6345); // "1:45:45"
formatDigitalDuration(125);  // "2:05"

The function divides total seconds by 3600 to get hours. The remainder divided by 60 gives minutes. The final remainder is seconds.

The padStart() method adds leading zeros when the value is less than 10. This ensures "5" becomes "05" for consistent formatting.

For durations stored as duration objects instead of total seconds, extract the values directly.

function formatDurationObject(duration) {
  const h = duration.hours || 0;
  const m = duration.minutes || 0;
  const s = duration.seconds || 0;

  if (h > 0) {
    return `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
  }
  return `${m}:${String(s).padStart(2, '0')}`;
}

formatDurationObject({ hours: 1, minutes: 5, seconds: 30 }); // "1:05:30"
formatDurationObject({ minutes: 5, seconds: 30 }); // "5:30"

Add fractional seconds by including milliseconds and formatting with decimal precision.

function formatWithMilliseconds(duration) {
  const m = duration.minutes || 0;
  const s = duration.seconds || 0;
  const ms = duration.milliseconds || 0;

  const paddedSeconds = String(s).padStart(2, '0');
  const fractional = String(ms).padStart(3, '0').slice(0, 2);

  return `${m}:${paddedSeconds}.${fractional}`;
}

formatWithMilliseconds({ minutes: 1, seconds: 23, milliseconds: 450 });
// "1:23.45"

Check for API support before using it.

function formatDuration(duration) {
  if (typeof Intl.DurationFormat !== 'undefined') {
    return new Intl.DurationFormat('en', { style: 'digital' }).format(duration);
  } else {
    return formatDurationObject(duration);
  }
}

This approach provides consistent output across all browsers while using the native API when available.

Common patterns for video players and timers

Video players need to format both current time and total duration. Create a reusable formatter to handle both values.

const videoFormatter = new Intl.DurationFormat('en', { style: 'digital' });

function formatVideoPosition(currentSeconds, totalSeconds) {
  const current = secondsToDuration(currentSeconds);
  const total = secondsToDuration(totalSeconds);

  return `${videoFormatter.format(current)} / ${videoFormatter.format(total)}`;
}

function secondsToDuration(seconds) {
  const hours = Math.floor(seconds / 3600);
  const minutes = Math.floor((seconds % 3600) / 60);
  const secs = Math.floor(seconds % 60);

  return hours > 0
    ? { hours, minutes, seconds: secs }
    : { minutes, seconds: secs };
}

formatVideoPosition(125, 6345); // "2:05 / 1:45:45"

For countdown timers that update every frame, create the formatter once and reuse it.

const timerFormatter = new Intl.DurationFormat('en', {
  style: 'digital',
  fractionalDigits: 2
});

function updateTimer(remainingMs) {
  const duration = {
    minutes: Math.floor(remainingMs / 60000),
    seconds: Math.floor((remainingMs % 60000) / 1000),
    milliseconds: remainingMs % 1000
  };

  document.getElementById('timer').textContent = timerFormatter.format(duration);
}

For stopwatches that show lap times, format elapsed time with fractional seconds.

const lapFormatter = new Intl.DurationFormat('en', {
  style: 'digital',
  fractionalDigits: 3
});

function formatLapTime(startMs, endMs) {
  const elapsedMs = endMs - startMs;

  return lapFormatter.format({
    minutes: Math.floor(elapsedMs / 60000),
    seconds: Math.floor((elapsedMs % 60000) / 1000),
    milliseconds: elapsedMs % 1000
  });
}

const lap1Start = performance.now();
// ... time passes ...
const lap1End = performance.now();

formatLapTime(lap1Start, lap1End); // "1:23.456"

These patterns handle the most common duration formatting scenarios while maintaining clean, reusable code.