3 时区问题
全球有24个时区,同一个时刻不同时区(比如中国上海和美国纽约)的时间不同。全球化项目,若初始化时间时未提供时区,那就不是真正意义上的时间,只能认为是我看到的当前时间的一个表示。
3.1 Date类
- Date无时区概念,任一机器使用
new Date()
初始化得到时间相同。因为,Date中保存的是UTC时间,其为以原子钟为基础的统一时间,不以太阳参照计时,无时区划分 - Date中保存的是一个时间戳,代表从1970年1月1日0点(Epoch时间)到现在的毫秒数。尝试输出Date(0):
System.out.println(new Date(0)); System.out.println(TimeZone.getDefault().getID() + ":" + TimeZone.getDefault().getRawOffset()/3600000);
得到1970年1月1日8点。我的机器在中国上海,相比UTC时差+8小时:
Thu Jan 01 08:00:00 CST 1970 Asia/Shanghai:8
对于国际化项目,处理好时间和时区问题首先就是要正确保存日期时间。
这里有两种
3.2 如何正确保存日期时间
- 保存UTC
保存的时间无时区属性,不涉及时区时间差问题的世界统一时间。常说的时间戳或Java中的Date类就是这种方式,也是推荐方案
保存字面量
比如年/月/日 时:分:秒,务必同时保存时区信息。有了时区,才能知道该字面量时间真正的时间点,否则它只是一个给人看的时间表示且只在当前时区有意义。
而Calendar才具有时区概念,所以通过使用不同时区初始化Calendar,才能得到不同时间。
正确地保存日期时间后,就是正确展示,即要使用正确时区,将时间点展示为符合当前时区的时间表示。至此也就能理解为何会发生“时间错乱”。
从字面量解析成时间 & 从时间格式化为字面量
对同一时间表示,不同时区转换成Date会得到不同时间戳
- 比如2020-11-11 11:11:11
- 对当前上海时区/纽约时区,转化为UTC时间戳不同
Wed Nov 11 11:11:11 CST 2020:1605064271000 Thu Nov 12 00:11:11 CST 2020:1605111071000
这就是UTC的意义,并非时间错乱。对同一本地时间的表示,不同时区的人解析得到的UTC时间必定不同,反过来不同本地时间可能对应同一UTC。
格式化后出现的错乱
即同一Date,在不同时区下格式化得到不同时间表示。
- 在当前时区和纽约时区格式化2020-11-11 11:11:11
- 输出如下,当前时区Offset(时差)是+8小时,对于-5小时的纽约
- 因此,有时数据库中相同时间,由于服务器时区设置不同,读取到的时间表示不同。这不是时间错乱,而是时区作用,因为UTC时间需根据当前时区解析为正确的本地时间。
所以要正确处理时区,在于存和读两阶段
存,需使用正确的当前时区来保存,这样UTC时间才会正确
读,也须正确设置本地时区,才能把UTC时间转换为正确当地时间
Java8处理时区问题
时间日期类ZoneId、ZoneOffset、LocalDateTime、ZonedDateTime和DateTimeFormatter,使用起来更简单清晰。
初始化上海、纽约和东京三时区
可使用ZoneId.of
初始化一个标准时区,也可使用ZoneOffset.ofHours
通过一个offset初始化一个具有指定时间差的自定义时区。
日期时间表示
LocalDateTime
无时区属性,所以命名为本地时区的日期时间ZonedDateTime=LocalDateTime+ZoneId
,带时区属性
因此,LocalDateTime仅是一个时间表示,ZonedDateTime才是一个有效时间。这里将把2020-01-02 22:00:00这个时间表示,使用东京时区解析得到一个ZonedDateTime。
DateTimeFormatter格式化时间
可直接通过withZone
直接设置格式化使用的时区。最后,分别以上海、纽约和东京三个时区来格式化这个时间输出:
日志输出:
- 相同时区,经过解析存和读的时间表示一样(比如最后一行)
- 不同时区,比如上海/纽约,输出本地时间不同。
+9小时时区的晚上10点,对上海时区+8小时,所以上海本地时间为早10点
而纽约时区-5小时,差14小时,为晚上9点
小结
要正确处理国际化时间问题,推荐Java8的日期时间类,即
- 使用
ZonedDateTime
保存时间 - 然后使用设置了
ZoneId
的DateTimeFormatter
配合ZonedDateTime
进行时间格式化得到本地时间表示