Intl.RelativeTimeFormat 可将时间差转为“2小时前”等自然语言,需指定 locale(如'zh-CN')和 style(long/short/narrow),单位仅支持预设8种且须为整数;format()不接收Date对象,需自行计算差值并注意时区与精度问题。
它能直接把时间差转成“2小时前”“明年”这类自然语言,但必须明确传入 locale 和 style,否则默认是英文且可能不按预期工作。浏览器兼容性没问题(Chrome 71+、Firefox 70+、Safari 14+),Node.js 需要 13.0+ 并启用 --icu-data-dir 或使用完整 ICU 构建版。
常见错误是只写 new Intl.RelativeTimeFormat() 不带参数,结果在中文环境仍输出英文;或者传了 locale: 'zh' 却没用 'zh-CN',导致 fallback 到默认语言。
locale 必须是完整 BCP 47 标签,比如 'zh-CN'、'ja-JP'、'en-US'
style 可选 'long'(“3天前”)、'short'(“3天前”→“3天前”,但日语会变“3日前”)、'narrow'(“3d前”)'year'、'quarter'、'month'、'week'、'day'、'hour'、'minute'、'second'
format() 不接收 Date 对象或毫秒数,只认「数值 + 单位」,所以得自己算差值。别直接用 new Date() - targetDate 然后除以 60000 —— 浮点误差和时区切换(比如夏令时边界)会让分钟/小时结果偶尔偏 ±1。
更稳的做法是逐级向下取整:先算天数差,再算剩余小时、分钟。但多数场景下,用 Math.round((now - target) / 60000) 算分钟级已够用;若需更高精度(比如显示“23小时59分前”),建议用 Temporal.PlainDateTime(ES2023+)或 date-fns 的 differenceInMinutes。
formatter.format(-2, 'hour') → “2小时前”0 给 minute 或 second 单位——部分 locale(如德语)对零值的表述不一致,宁可用 second 单位兜底formatToParts() 做拼接,除非你真需要分别样式化数字和单位(比如把“2”加粗、“小时”变灰)原生 Intl.RelativeTimeFormat 不自动降级:传 -30 和 'day' 就是“30天前”,不会说“上个月”。业务中常需要“7天内用相对时间,超过就回退到绝对日期格式”,这个逻辑得自己写。
也别指望它识别“昨天”“今天”“明天”这种固定词——它确实支持,但前提是手动判断差值是否为 -1、0、1 再调用 format(-1, 'day')。中文里“昨天”没问题,但“大前天”“后天”就得额外配置映射表,因为 Intl 不提供这些。
abs(diffInDays) 才用 relative,否则交给 <code>Intl.DateTimeFormat 格式化为“10月5日”diffInHours 比 diffInDays * 24 更准每个 Intl.RelativeTimeFormat 实例初始化有开销,尤其多 locale 场景。如果页面要格式化上百条消息时间,为每个都 new Intl.RelativeTimeFormat('zh-CN') 会明显拖慢首屏。
正确做法是复用实例:按 locale + style + unit 组合缓存,或者直接全局单例(只要 locale 不变)。React 中可封装成 hook,Vue 中用 computed 缓存,纯 JS 就用模块级变量。
format(-5, 'minute') 前先查缓存实例style 相同但单位不同不影响复用,但 locale 或 style 变了就得新造最易被忽略的一点:relative 时间标签没有“绝对时间锚点”概念。它永远基于当前系统时间计算,如果用户手动改了系统时间,所有“X分钟前”都会跳变。需要强一致性时,得靠服务端下发的基准时间戳 + 客户端 diff,而不是无条件信任 Date.now()。