如何将持续时间格式化为 2:30:45?

以带冒号的数字时钟格式显示时间持续时间

介绍

视频播放器、秒表和倒计时器以一种熟悉的格式显示时间。您看到 "2:30:45",会立即理解它表示 2 小时 30 分钟 45 秒。这种数字时钟格式使用冒号分隔时间单位,没有标签或空格。

手动构建这种格式需要用零填充数字并处理不同的时间长度。例如,一个运行 5 分钟 30 秒的视频显示为 "5:30",而一个 2 小时的视频显示为 "2:00:00"。处理边界情况时逻辑会变得复杂。

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')}`;
}

Intl.DurationFormat API 使用 digital 样式选项可以自动处理这些情况。它可以为任何时间长度生成正确的格式。

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

使用数字样式显示计时器

digital 样式以数字时钟的形式格式化时间。创建格式化器时,将 style 选项设置为 "digital"

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

传递一个包含所需时间单位的时间对象。格式化器会在单位之间添加冒号,并在需要时用零填充值。

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

注意,当存在小时时,分钟会显示为 "05" 而不是 "5"。格式化器会自动将分钟填充为两位数字,以便在列表和表格中保持对齐。

对于没有小时的时间,只需从时间对象中省略该属性。

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

当分钟是最大单位时,不再需要填充。格式保持紧凑,同时仍然清晰。

数字格式如何处理不同的持续时间长度

数字格式会根据持续时间对象中包含的单位调整输出。这与视频播放器根据视频长度调整显示方式相匹配。

较短的持续时间仅显示分钟和秒。

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

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

较长的持续时间包括小时。

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

当显示小时时,所有较小的单位会填充为两位数。当不显示小时时,分钟不填充,但秒仍然填充。

这种填充规则在不浪费空间的情况下创建了一致的对齐方式。一组短视频显示为 "5:30"、"12:45"、"8:02",冒号对齐。一组长视频显示为 "1:05:30"、"2:12:45"、"3:08:02",格式一致。

仅包含界面所需的单位。一个从不超过一小时的倒计时计时器可以完全省略小时。

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

控制零填充和显示选项

数字格式使用默认的填充规则,但您可以通过为各个时间单位指定选项来覆盖它们。

通过设置特定单位的选项来控制每个单位的显示方式。numeric 值显示不带填充的数字,2-digit 值强制两位数填充。

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"

这是默认行为。只有当您需要不同的格式时才需要指定这些选项。

强制小时始终显示为两位数。

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

这种格式适用于同步显示,其中一致的宽度可以防止布局偏移。

从秒中移除填充以获得更紧凑的显示。

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

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

这种格式较少见,因为用户期望数字显示中的秒数是填充的。

为数字格式添加小数秒

某些应用程序需要显示毫秒或微秒。数字格式通过 fractionalDigits 选项支持小数秒。

设置秒后显示的小数位数。

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

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

格式化器会在秒后添加小数点,并显示指定的小数位数。

秒表通常显示百分之一秒。

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

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

格式化器会根据指定的精度进行四舍五入。450 毫秒的值变为 45 百分之一秒。

对于微秒精度,请在持续时间对象中包含微秒,并设置更高的小数位数。

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

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

何时使用数字格式与其他样式

当您的界面类似于计时器、秒表或媒体播放器时,请选择数字格式。在这些上下文中,用户期望使用此格式。

在视频播放器控件中使用数字格式。

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"

在倒计时计时器和秒表中使用数字格式。

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"

在以散文形式显示持续时间或需要标签提高清晰度时,请使用其他样式。航班预订网站显示 "8 小时 15 分钟" 而不是 "8:15:00",因为上下文不是计时器。

// 适合散文和描述
new Intl.DurationFormat('en', { style: 'short' }).format(duration);
// "1 小时, 46 分钟和 40 秒"

// 适合计时器显示
new Intl.DurationFormat('en', { style: 'digital' }).format(duration);
// "1:46:40"

为旧版浏览器手动构建数字格式

Intl.DurationFormat API 于 2025 年 3 月推出。对于旧版浏览器,可以手动构建数字格式。

手动方法需要从总秒数中计算时间单位,并用零填充值。

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"

该函数通过将总秒数除以 3600 来获取小时数。余数除以 60 得到分钟数。最终的余数是秒数。

padStart() 方法在值小于 10 时添加前导零。这确保了 "5" 会变成 "05",以实现一致的格式。

对于以持续时间对象而非总秒数存储的持续时间,可以直接提取值。

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"

通过包括毫秒并使用小数精度格式化,可以添加分数秒。

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"

在使用 API 之前检查其支持情况。

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

这种方法在所有浏览器中提供一致的输出,同时在可用时使用原生 API。

视频播放器和计时器的常见模式

视频播放器需要格式化当前时间和总时长。创建一个可重用的格式化器来处理这两个值。

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"

对于每帧更新的倒计时计时器,创建一次格式化器并重复使用它。

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);
}

对于显示圈速时间的秒表,用小数秒格式化经过时间。

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();
// ... 时间流逝 ...
const lap1End = performance.now();

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

这些模式处理了最常见的持续时间格式化场景,同时保持了代码的简洁和可重用性。