做国际化的时候,基本会遇到的一个问题就是时间的转换,其中包括时间的语言上的转化,还有就是时区的转化,这些是其中的一些痛点
先来了解一些基本概念
基本概念
- 相对时间
跟现在相对的时间,比如说十分钟前,十天前
- 绝对时间
没有参照物的时间
- 时区
- 协调世界时
如果时间是以协调世界时(UTC)表示,则在时间后面直接加上一个“Z”(不加空格)。“Z”是协调世界时中0时区的标志。因此,“09:30 UTC”就写作“09:30Z”或是“0930Z”。“14:45:15 UTC”则为“14:45:15Z”或“144515Z”。
这个就是基准的时间
- 时间偏移量
比如说我现在在中国,我的时区是Asia/Shanghai,就是 UTC+8,8 就是那个偏移量
- 时区不规则
但是由于一些历史原因和其他原因,时区的形状不是规则的,会随国际线变化而变化
- 夏令时和冬令时
夏令时(Daylight Saving Time,DST)和冬令时是现代社会应对日照时间变化而实施的时间调整机制。
夏令时的基本思想是在夏季时,将时间拨快一小时,以便更多的日照时间能被充分利用,尤其是在能源消耗较为密集的白天。冬令时则是指冬季恢复到标准时间,通常是将时间调整回正常的标准时间,以适应冬季较短的白昼。
很多国家会用这种方式来应对季节性日照变化、提高资源利用效率。
但对于开发来说,夏令时是一个非常典型的坑点:
- 有些国家有夏令时,有些没有
- 有些国家曾经用过,后来取消了
- 夏令时切换的日期不统一
- 某一天可能会出现“少一个小时”或“多一个小时”的情况
比如在夏令时切换当天:
- 有的时间点会不存在
- 有的时间点会出现两次
这就意味着,像 2025-03-30 02:30:00 这种本地时间,在某些时区里可能根本无效。
所以处理时间时,不能只看“年月日时分秒”字符串本身,还必须结合时区一起判断。
具体思路
处理国际化时间问题,比较推荐的思路是:
1. 存储时统一用 UTC
这是最常见也最稳妥的方案。
不管用户在哪个国家,不管前端传过来的是什么时区,到了服务端之后,统一转换成 UTC 再存储
2025-03-18T06:30:00Z
而不是直接存:
2025-03-18 14:30:00
因为后者如果没有时区信息,后续几乎无法准确还原
2. 展示时转换为用户本地时区
存储用 UTC,展示时再根据用户所在时区转成本地时间。
这一步通常有几种策略:
- 使用浏览器自动识别时区
- 用户自己在设置里指定时区
- 根据业务场景使用固定时区
这个不只是技术,也得看业务需要
3. 语言格式化交给国际化 API 或库处理
时间不仅要转换,还要“说得自然”。
比如在前端可以使用 Intl.DateTimeFormat 和 Intl.RelativeTimeFormat。
格式化绝对时间
const date = new Date('2025-03-18T06:30:00Z')
const formatter = new Intl.DateTimeFormat('zh-CN', {
dateStyle: 'full',
timeStyle: 'long',
timeZone: 'Asia/Shanghai'
})
console.log(formatter.format(date))
输出可能类似:
2025年3月18日星期二 中国标准时间 14:30:00
如果换成英文环境:
const formatterEn = new Intl.DateTimeFormat('en-US', {
dateStyle: 'full',
timeStyle: 'long',
timeZone: 'America/New_York'
})
输出风格就会变成英文习惯
格式化相对时间
const rtf = new Intl.RelativeTimeFormat('zh-CN', { numeric: 'auto' })
console.log(rtf.format(-10, 'minute'))
输出:
10分钟前
英文:
const rtfEn = new Intl.RelativeTimeFormat('en-US', { numeric: 'auto' })
console.log(rtfEn.format(-10, 'minute'))
输出:
10 minutes ago
4. 拿到系统时区
浏览器对Intl.DateTimeFormat的支持很好,基本不存在拿不到系统时区的场景,web 项目可以通过这个那,如果是 rn 低版本的那可以要自己注入一个 polyfill 来提供支持了
try {
// 尝试创建一个 DateTimeFormat 实例来获取时区
const dtf = new Intl.DateTimeFormat();
const timeZone = dtf.resolvedOptions().timeZone;
if (timeZone) {
return timeZone;
}
} catch (e) {
// 继续尝试其他方法
}
上面这些差不多就是在小红书实习的时候做的一个需求,我总结了一下,其实大概思路是这样,具体实现就不说了