Intl.DateTimeFormat 午夜显示 24:h23、h24 与 hour12 的覆盖顺序

前天 3阅读

同一个午夜,小时字段未必都是零

前端格式化时间时,指定二十四小时制后却看到午夜显示二十四,很容易怀疑时间戳多了一天。先别急着调整日期:Intl.DateTimeFormat 的 h23 与 h24 是不同的小时周期。前者小时范围从零到二十三,后者从一到二十四,因此同一个当地午夜可以有不同的小时数字。

这里讨论的是显示规则。格式化器不会因为采用 h24 就把传入的 Date 修改成另一天。定位问题时,应先固定时区,再检查所选小时周期;否则电脑所在时区的差异会和显示差异混在一起,看到的“午夜”甚至可能已经不是同一个当地时间。

Intl.DateTimeFormat 午夜显示 24:h23、h24 与 hour12 的覆盖顺序

配图为 AI 生成的概念插画:两个指向午夜的无字钟面,表示同一时刻的不同显示约定。

用部件观察小时,不切割本地化字符串

以下程序固定 UTC、英语地区和拉丁数字,减少演示中的额外变量。每次同时打印 resolvedOptions 的小时周期,以及 formatToParts 得到的 hour 部件。后者按语义读取小时,避免假定所有语言都使用相同冒号、空格或上午下午标记。

const instant = new Date("2026-01-01T00:00:00.000Z");
const base = {
  timeZone: "UTC",
  numberingSystem: "latn",
  hour: "2-digit",
  minute: "2-digit"
};
function show(label, locale, options) {
  const formatter = new Intl.DateTimeFormat(locale, { ...base, ...options });
  const hour = formatter.formatToParts(instant).find(p => p.type === "hour").value;
  const resolved = formatter.resolvedOptions();
  console.log(label, JSON.stringify({
    hourCycle: resolved.hourCycle, hour12: resolved.hour12, hour
  }));
}
show("h23", "en-US", { hourCycle: "h23" });
show("h24", "en-US", { hourCycle: "h24" });
show("hour12 true wins", "en-US", { hourCycle: "h23", hour12: true });
show("hour12 false wins", "en-US", { hourCycle: "h12", hour12: false });
show("option overrides locale", "en-US-u-hc-h24", { hourCycle: "h23" });

前两个结果分别是 h23 与小时 00、h24 与小时 24。第二组故意同时传入 hourCycle 和 hour12,用来观察选项覆盖:hour12 存在时会覆盖 hourCycle,不能把两项理解成各管一个独立开关。对于这里的 en-US,true 会采用十二小时周期;false 则采用该实现与地区数据对应的二十四小时周期。

需要零点写成 00,就表达这个具体要求

如果产品明确要求小时字段始终落在零到二十三,应使用 hourCycle: "h23",并避免同时传入 hour12。仅写 hour12: false 表达的是二十四小时显示,不能把它当成所有运行时、所有地区中精确等同 h23 的承诺。读取 resolvedOptions,才能看到格式化器实际解析出的设置。

地区字符串也可以携带小时周期扩展,例如示例中的 en-US-u-hc-h24。显式 hourCycle 选项能覆盖这个扩展,hour12 又具有更高优先级。代码最后一组把扩展设置为 h24、选项设置为 h23,得到零点小时 00,展示了为什么只检查语言标签不足以判断最终格式。

展示文本与机器交换格式分开设计

测试时可以断言 hour 部件和解析后的周期,不宜把完整本地化字符串锁死成某种空白组合。不同引擎或地区数据版本可能调整标点、空格及其他表现细节;如果页面需要重新排版,可以保留 formatToParts 返回的 literal 部件,再按设计组合,避免误删有意义的分隔符。

也不要把 h24 的显示文本直接拼成准备交给其他系统解析的日期字符串,尤其要小心午夜所属日期的约定。用于存储、比较或接口传输时,应选择双方明确约定的格式和时区;Intl 输出主要服务于用户阅读。排查时保留原始时间戳、时区、选项及解析结果,通常比对显示字符串做替换更容易查清原因。

如果某段公共组件默认设置了 hour12,调用方再传入 hourCycle 时仍可能被覆盖。应检查最终传给构造器的整个选项对象,而不是只看页面上新增的一行配置。把零点、正午和接近换日的时刻都放进测试,也能避免只测下午时间时看不见这类问题。

此外,小时选项应实际包含在格式化请求里。如果格式化器只输出年月日,解析结果可能不会提供小时周期相关字段。诊断时先确认当前组件确实在格式化时间,再根据部件信息定位具体配置。

参考资料

ECMA-402:创建日期时间格式化器;ECMA-402:日期时间模式中的小时处理;ECMA-402:formatToParts

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。