Chrome 129 就支持的一个 API,Node、Bun、Deno 全跟上了——你还在手拼“3h 12min”这种时长字符串
你在页面上挂一个「还剩 3 小时 12 分」的倒计时,或者给后台导出任务显示一个「耗时 1 分 46 秒 40」,第一反应基本都是自己拼字符串——把小时分钟秒拆出来,前面补零,中间塞个冒号或单位词。这个拼法有一个没那么明显的问题:它只对中文和英文勉强成立,一旦产品出海,你把「3h 12min」原样丢给法国用户,或者把「1 heure」拼进日文界面,就露馅了。
其实浏览器早就替你备好了一个 API,只是它一直躺在 Intl 里没被注意。Intl.DurationFormat 做的事很简单:你给它一个 { hours, minutes, seconds } 这样的 duration 对象,它按当前 locale 的语法和复数规则,直接吐出一句人话。
先说它现在能用的范围,因为「能不能上线」是前端最先要问的。Chrome 从 129 开始支持,Edge 129、Firefox 136、Safari 16.4 跟上,Node.js 23、Bun 1.0.3、Deno 1.46 也已经进了各自的标准库。按 caniuse 的口径,全球覆盖率约 89.14%,MDN 把它标为 Baseline 2025——也就是 2025 年 3 月起,主流浏览器的最新版本都能跑。换句话说,今天写,不用再引第三方库。
再看两种写法差在哪。以前你要展示 1 小时 46 分 40 秒:
const d = { hours: 1, minutes: 46, seconds: 40 };
const text = `${d.hours}h ${d.minutes}min ${d.seconds}s`;
// "1h 46min 40s" —— 无论用户什么语言,都长这样
现在换 Intl.DurationFormat 来拼:
const d = { hours: 1, minutes: 46, seconds: 40 };
new Intl.DurationFormat('fr-FR', { style: 'long' }).format(d);
// "1 heure, 46 minutes et 40 secondes"
new Intl.DurationFormat('en', { style: 'short' }).format(d);
// "1 hr, 46 min and 40 sec"
new Intl.DurationFormat('pt', { style: 'narrow' }).format(d);
// "1h 46min 40s"
同一份数据,三种风格:long 出完整单词并带上 locale 特有的连接词(法语那个 et、英语那个 and),short 出缩写,narrow 出最紧凑的符号形式。这些连接词和复数变化,以前全是你要自己 if-else 硬编码的。
它还有个 digital 风格,专门给计时器类 UI 用,可以带小数秒:
new Intl.DurationFormat('en', { style: 'digital' }).format({ hours: 11, minutes: 30, seconds: 12, milliseconds: 345 });
// "11:30:12.345"
倒计时、上传进度、视频时长、后台任务耗时——这些场景以前要么套 dayjs 的 duration 插件,要么自己写一套补零逻辑,现在一行 format() 就够了。如果 UI 需要把数字和单位分开做样式,用 formatToParts() 拿到分段结果,而不是自己 split(' ')。
还有一点容易被忽略:Intl.DurationFormat 只负责「呈现」,不负责「计算」。把两个时间点之间的差值算成 { hours, minutes, seconds },那是 Temporal.Duration 或你自己的事;单位之间怎么进位、跨夏令时怎么算,它一概不管。它解决的是「这个数字该怎么读给用户听」这一个问题,别指望它顺手把调度逻辑也办了。
下一步:翻一下你们项目里所有 h + 'h' + m 这类手工拼时长的位置,特别是倒计时组件和导出耗时提示,把它们换成 Intl.DurationFormat。跑之前在入口加一句
if (typeof Intl.DurationFormat === 'undefined') { /* 走旧逻辑兜底 */ }
就能安全灰度。这一步做完,你的时长文案会一次性支持所有 locale,而你在多语言项目里省下的,正好是那堆你本来要手写的复数规则。
评论区
登录后可评论。