发布于: -/最后更新: -/3 分钟/

TypeScript 技巧:使用语义化时间告别“毫秒 vs 秒”的单位换算烦恼

摘要

为了解决不同API对时间单位的不一致性问题,利用TypeScript的模板字面量类型构建了一套语义化时间系统。核心实现包括一个通用的毫秒解析器和一个将时间字符串转换为秒的函数。通过引入Duration类型和ms解析器,获得了三个好处:消除歧义、类型安全和心智解脱。这种方法使得代码更易读、更易维护,并减少了由于时间单位不一致引起的Bug。

每个 API 都有自己的脾气。setTimeout 用毫秒,Redis TTL 通常用秒,JWT 过期时间也是秒,但某些数据库的时间戳又是毫秒。这种不一致性不仅增加了心智负担,还极其容易引发 Bug(比如把 10 秒当成 10 毫秒,或者反过来)。

与其每次写代码时都在心里做乘除法,不如利用 TypeScript 的模板字面量类型,构建一套语义化时间系统。

1. 核心实现:通用的 ms 解析器

TypeScript
// utils/time.ts

type Unit = "ms" | "s" | "m" | "h" | "d";

// 利用 TS 模板字面量类型,限制格式必须如 "10s", "100ms", "1d"
export type Duration = `${number} ${Unit}` | `${number}${Unit}`;

/**
 * 将人类可读的时间字符串转换为毫秒 (Milliseconds)
 */
export function ms(d: Duration): number {
  const match = d.match(/^(\d+)\s?(ms|s|m|h|d)$/);
  if (!match) {
    throw new Error(`Unable to parse duration: ${d}`);
  }
  const time = Number.parseInt(match[1]);
  const unit = match[2] as Unit;

  switch (unit) {
    case "ms": return time;
    case "s":  return time * 1000;
    case "m":  return time * 1000 * 60;
    case "h":  return time * 1000 * 60 * 60;
    case "d":  return time * 1000 * 60 * 60 * 24;
    default:   throw new Error(`Unknown unit: ${unit}`);
  }
}

/**
 * 如果 API 需要的是“秒”,直接套用这个 wrapper
 * 解决了一次性的心智负担,调用处无需再手动 / 1000
 */
export function seconds(d: Duration): number {
  return ms(d) / 1000;
}

2. 举个栗子

TypeScript
// ❌ 容易混淆的写法
checkLimit({
  capacity: 100,
  interval: 86400000 // 这是多少?一天?还是十天?毫秒还是微秒?
});

使用了语义化类型后,代码的可读性和维护性瞬间提升:

TypeScript
import { ms, type Duration } from "./utils/time";

export type RateLimitOptions = {
  capacity: number;
  // 直接在类型定义中强制使用语义化字符串
  interval: Duration; 
  cost?: number;
};

export function checkLimit(options: RateLimitOptions) {
  // 在函数内部统一转换为毫秒处理,外部调用者无感知
  const intervalMs = ms(options.interval);
  
  console.log(`限流窗口大小: ${intervalMs}ms`);
  // ... 具体的令牌桶或漏桶逻辑
}

checkLimit({
  capacity: 5000,
  interval: "1h", // 清晰明了:1小时
});

checkLimit({
  capacity: 10,
  interval: "30s", // 清晰明了:30秒
});

总结

通过引入 Duration 类型和 ms 解析器,我们获得了三个好处:

  1. 消除歧义:不再需要猜测 86400 到底是秒还是毫秒。

  2. 类型安全:TypeScript 会阻止你传入 "10 lightyears" 这种非法单位。

  3. 心智解脱:写代码时想到“10分钟”,直接写 "10m",不用在脑子里做 10 * 60 * 1000 的算术题。

正文结束