功能说明
formatDuration 把毫秒转换为由天、时、分、秒组成的可读文本,例如 3725000 毫秒得到 1 hour 2 minutes 5 seconds,短模式则输出 1h 2m 5s。maxUnits 可限制展示的单位数量,适合空间有限的卡片与表格。formatDurationClock 提供 HH:MM:SS 形式的时钟样式,适用于播放进度或计时器。
关键代码
单位表按从大到小排列,循环用整除得到当前单位数量,再用余数继续下一位。前导零单位会被跳过,但一旦开始输出就不再补零单位,保证 1h 0m 5s 不会变成 1h 5s 造成歧义。单数判断基于当前数量,英语下 1 不加 s。短标签取单位首字母,通过分隔符选项可输出紧凑形式。毫秒为负或非有限值会直接抛错。
使用注意
月份和年份不是固定毫秒数,本函数只处理天及以下单位,跨月时长应使用日历库。格式化结果不参与计算,反解析需另写解析器。maxUnits 统计的是非零单位数量,因此 1 天 0 小时仍只占一个单位。本地化只覆盖英语词形,面向其他语言应传入自定义标签或使用 Intl 的相对时间格式。时钟模式超过 24 小时不会进位到天。
验证方式
测试 0、999、1000、59000、60000、3599999 与 86400000 等边界,确认进位和省略前导单位正确。验证 maxUnits 为 1 与 2 的输出,测试短标签和自定义分隔符。检查单数 1 hour 与复数 2 hours 的词形。再测试负数、NaN、字符串输入以及时钟模式下的跨小时与跨天数值。
代码示例
const UNITS = [
["day", 86400000],
["hour", 3600000],
["minute", 60000],
["second", 1000],
];
// Human-readable duration formatting for elapsed time.
export function formatDuration(milliseconds, options = {}) {
if (typeof milliseconds !== "number" || !Number.isFinite(milliseconds) || milliseconds < 0) {
throw new RangeError("duration must be a non-negative finite number");
}
const { maxUnits = UNITS.length, separator = " ", short = false } = options;
if (!Number.isInteger(maxUnits) || maxUnits < 1) {
throw new RangeError("maxUnits must be a positive integer");
}
let remaining = Math.floor(milliseconds);
const parts = [];
for (const [name, size] of UNITS) {
if (parts.length >= maxUnits) break;
const amount = Math.floor(remaining / size);
remaining -= amount * size;
if (amount === 0 && parts.length === 0) continue;
if (amount === 0) continue;
const label = short ? name[0] : " " + name + (amount === 1 ? "" : "s");
parts.push(amount + label);
}
if (parts.length === 0) return short ? "0s" : "0 seconds";
return parts.join(separator);
}
export function formatDurationClock(milliseconds) {
if (typeof milliseconds !== "number" || !Number.isFinite(milliseconds) || milliseconds < 0) {
throw new RangeError("duration must be a non-negative finite number");
}
const totalSeconds = Math.floor(milliseconds / 1000);
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
return [hours, minutes, seconds].map((part) => String(part).padStart(2, "0")).join(":");
}