← 中文资源

简体中文版本

人类可读时长解析器

把 1h30m、500ms 或 2.5s 等紧凑时长解析为毫秒,并校验单位与语法错误。

阅读英文原文

功能说明

该示例把配置或命令行中常见的人类可读时长转换为毫秒,支持毫秒、秒、分、时、天、周六个单位,可混合多个片段并允许片段间有空格,例如 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);
}