记录一次时间戳、夏令时、时区线上问题分析

1,480 阅读2分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

问题描述

问题:输入时间戳、时区,返回一个带时区的时间字符串?

代码如下:

long timestamp = 1647489600000L; // 2022-03-15 12:00:00  Asia/Shanghai
String timeZoneName = "America/New_York";
Instant instant = Instant.ofEpochMilli(timestamp);
ZonedDateTime zonedDateTime = ZonedDateTime.of(LocalDateTime.ofInstant(instant, ZoneId.systemDefault()),
TimeZone.getTimeZone(timeZoneName).toZoneId());
System.out.println(zonedDateTime);

ZonedDateTime zonedDateTime1 = ZonedDateTime.of(LocalDateTime.ofInstant(instant, ZoneId.systemDefault()),
TimeZone.getTimeZone("Asia/Shanghai").toZoneId());
System.out.println(zonedDateTime1);

输出结果如下:

image-20220314230756162.png

几个类简单说明一下:

  • Instant 可以将时间戳转换为本地日期/时间 LocalDateTime

  • JDK 8 之前,Java使用 java.util.TimeZone 来表示时区。而在JDK 8里分别使用了 ZoneId 表示时区,ZoneOffset 表示 UTC 的偏移量。

  • ZonedDateTime 是一个带时区的日期类。

夏令时(DST)

我们知道,纽约是 西五区,上海是 东八区 那么应该和我们的时间相差13 个小时(因为一个时区就是相差一个小时),但是我们得到的结论,确实是相差了 12 个小时,是不是有点怀疑自己。

image-20220314231639699.png 再来看看,居然是 11 点,明明是相差 12 个小时,为什么说是比北京时间慢 14 小时这就是由于当前时间是夏令时导致的。

一个特殊的例子:

ZonedDateTime zonedDateTime2 = ZonedDateTime.ofInstant(instant,
		TimeZone.getTimeZone("GMT-05:00").toZoneId());
System.out.println(zonedDateTime2);
// 结果:2022-03-16T23:00-05:00[GMT-05:00]

如果我们使用 GMT-05:00 来获取时区的时间,那么就不会触发夏令时,可能会和你想象的结果不同。

换句话说 America/New_York 可能是 GMT-05:00 也可能是 GMT-04:00 这个具体要看 LocalDateTime 是否在夏令时的时间区间内.

如何通过 America/New_York 获取到 GMT-xx

一个简单的方法

ZonedDateTime of = ZonedDateTime.of(LocalDateTime.now(ZoneId.of("America/New_York")), ZoneId.of("America/New_York"));
System.out.println(of.getOffset().getId());

输出:

-04:00

思路来源于

image.png

说明一下:
ZonedDateTime 包括 LocalDateTimeZoneId 我们将 -04:00 加上 GTM 前缀就可以了,而且已经包含夏令时的处理。

总结一下

1、如果按照用户侧,百度上的时间来算,由于现在是夏令时,所以 America/New_York 方式的时区才能正确的获取本地时间。

2、 具体的 Java 几个日期类的差别,大家可以看看参考资料中的文件。

3、 对于时区,时间戳,夏令时来说,相互转换有时候特别容易混淆。

参考资料