功能说明
该示例把配置或命令行中常见的人类可读时长转换为毫秒,支持毫秒、秒、分、时、天、周六个单位,可混合多个片段并允许片段间有空格,例如 1h30m、2.5s、1d 2h。返回值四舍五入为整数毫秒,适合直接交给定时器、超时配置或缓存 TTL 使用。
关键代码
输入先 trim 并转小写,拒绝空串与带正负号的数值,避免把负数解释成过去时间。正则逐段匹配数字与单位,数字允许小数但不允许科学计数法;每段之间只接受空白,否则说明存在未识别的字符。解析循环记录上一次结束位置,最后检查剩余字符与是否至少匹配到一段,任何不符合都抛 SyntaxError;累加结果非有限时抛 RangeError。
使用注意
单位顺序不被强制,1h30m 与 30m1h 都会得到相同结果;重复单位也按累加处理。纯数字没有单位会被拒绝,避免毫秒与秒的歧义。毫秒以下的小数最后四舍五入,500.4ms 变成 500,500.5ms 变成 501。不支持年、月,因为它们的长度不固定。输入大小写不敏感,1H30M 可以解析。
验证方式
测试 1h30m 得到五四零零零零零毫秒;测试 2.5s、500ms、1d 2h 与带空格的混合片段;验证大写输入与小写等价;检查空串、纯数字、1h30、1e3ms、负号前缀分别抛 SyntaxError 或 RangeError;确认 0s 返回零;极大值导致非有限时抛 RangeError。
代码示例
const UNITS: Readonly<Record<string, number>> = {
ms: 1,
s: 1_000,
m: 60_000,
h: 3_600_000,
d: 86_400_000,
w: 604_800_000,
};
export function parseDuration(input: string): number {
if (typeof input !== "string") throw new TypeError("input must be a string");
const text = input.trim().toLowerCase();
if (text === "") throw new SyntaxError("duration must not be empty");
if (text.startsWith("-") || text.startsWith("+")) {
throw new RangeError("signed durations are not supported");
}
const pattern = /(\d+(?:\.\d+)?)\s*(ms|s|m|h|d|w)/g;
let total = 0;
let lastIndex = 0;
let matched = false;
let match: RegExpExecArray | null;
while ((match = pattern.exec(text)) !== null) {
const gap = text.slice(lastIndex, match.index);
if (gap.trim() !== "") throw new SyntaxError("invalid duration: " + input);
const amount = Number(match[1]);
const unit = match[2];
total += amount * (UNITS[unit] ?? 0);
lastIndex = pattern.lastIndex;
matched = true;
}
if (!matched || text.slice(lastIndex).trim() !== "") {
throw new SyntaxError("invalid duration: " + input);
}
if (!Number.isFinite(total)) throw new RangeError("duration is too large");
return Math.round(total);
}