temporal 提案通过 plaindatetime + timezone 分离日历意图与真实时刻,用 zoneddatetime 锚定计费起止点,instant 统一运算,显式处理夏令时重叠与间隙,确保计费零偏差。

全球排班系统中,夏令时切换会导致同一本地时间点对应两个不同 UTC 时刻(重叠)或根本不存在(间隙),传统 Date 对象无法识别、不报错、不区分,直接造成计费时段错位、工时重复计算或漏算。Temporal 提案通过强制显式时区语义、内置 IANA 时区规则和不可变类型设计,让计费时间校准可验证、可追溯、零静默偏差。
用 PlainDateTime + TimeZone 分离“排班意图”与“真实时刻”
排班表中的“周一早9点开会”是日历意图,不是绝对时间。不能用 new Date('2026-10-26T09:00') 存储——它在纽约和东京解析结果完全不同,且丢失原始时区上下文。
- 用户录入排班时间 → 用 Temporal.PlainDateTime.from({ year, month, day, hour, minute }) 构造,只存日历值,不绑定任何时区
- 绑定员工所属时区(如 'Europe/Berlin')→ 调用 .toZonedDateTimeISO(timeZone),Temporal 自动查数据库:2026年10月26日柏林已切回标准时间(UTC+1),所以 09:00 合法且唯一
- 存储建议:数据库字段存 PlainDateTime.toString()(如 "2026-10-26T09:00") + 时区标识符字符串(如 "Europe/Berlin"),而非 Date.toISOString()
用 ZonedDateTime 精确锚定计费起止点,拒绝模糊字符串
计费逻辑必须基于真实世界发生的“某时区下的确定时刻”。任何含 +02:00 这类静态偏移的字符串都不足以支撑夏令时校准——它不知道 2026 年 3 月 30 日柏林是否已启用夏令时。
- ✅ 正确构造: Temporal.ZonedDateTime.from('2026-03-30T09:00[Europe/Berlin]') —— 查 IANA 数据库确认当日为夏令时(UTC+2),返回带 offset: '+02:00' 的实例
- ❌ 错误构造: Temporal.ZonedDateTime.from('2026-03-30T09:00+02:00') —— 抛 TypeError,因缺少 [IANA] 时区名,无法动态匹配规则
- 遇到夏令时间隙(如 2026-03-30T02:30[America/New_York])→ 直接抛 RangeError,强制你在业务层决定策略:跳过、提前、延后,而不是静默设成 03:30
用 Instant 统一做计费区间运算,避开日历陷阱
加减、比较、求差这些计费核心操作,绝不能在 ZonedDateTime 上直接调 add({hours: 8}) 或 .equals() —— 它们按本地日历语义执行,夏令时边界会改变“一天”的实际毫秒数。
- 所有计费时段计算前,先转成 Temporal.Instant:
zdt1.toInstant()和zdt2.toInstant() - 区间长度 = instantEnd.getEpochNanoseconds() - instantStart.getEpochNanoseconds() → 恒为精确纳秒差,不受 DST 影响
- 判断是否跨班次:用 instant.compareTo(otherInstant) > 0,而非 zdt1.equals(zdt2),避免因时区不同返回 false 却指向同一时刻
- 生成下期排班时间:先
instant.add({ hours: 168 })(固定 7 天),再.toZonedDateTimeISO('Europe/Berlin')转回本地视图
处理重叠时刻(如 2026-10-26T02:30)的显式消歧策略
夏令时结束日,凌晨 2:00–2:59 在欧洲多数地区出现两次。排班系统需明确“员工这次上的是标准时间班还是夏令时间班”。
- 默认行为(disambiguation: 'compatible')取后一次(即标准时间),但业务可能要求取前一次(夏令时间)
- 显式声明:
plain.toZonedDateTimeISO(timeZone, { disambiguation: 'earlier' }) - 前端展示时,可附加时区缩写:
zdt.toLocaleString('en', { timeZoneName: 'short' })→ 显示 "2:30 AM CEST" 或 "2:30 AM CET" - API 返回计费区间时,附带
zdt.offset和zdt.timeZoneId字段,供下游审计校验










