Java 时间戳与时区时间转换

在某群看到有人提了一个时间戳与时区时间转换的问题,当时就看出他的计算方式有问题,但是对相关概念有些模糊,今天想起来就整理了一下这个问题。

加减时间戳

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Instant instantPlusEightHours = Instant.now().plus(8, ChronoUnit.HOURS);
Instant now = Instant.now();

System.out.println("UTC: " + now);
System.out.println("UTC+8: " + instantPlusEightHours);

System.out.println("对应上面UTC的时间戳: " + now.toEpochMilli());
System.out.println("系统时间戳: " + System.currentTimeMillis());
System.out.println("对应上面UTC+8的时间戳: " + instantPlusEightHours.toEpochMilli());

//Output:
//UTC: 2019-03-15T02:42:40.958Z
//UTC+8: 2019-03-15T10:42:40.953Z
//对应上面UTC的时间戳: 1552617760958
//系统时间戳: 1552617761041
//对应上面UTC+8的时间戳: 1552646560953

可以看出他想将时间戳转换为对应UTC+8时区的时间,但这里他犯了一个概念性的错误。

Unix时间戳是从UTC时间 1970年1月1日 00:00开始所经过的秒数,不考虑闰秒。
协调世界时(英语:Coordinated Universal Time,法语:Temps Universel Coordonné,简称UTC)是最主要的世界时间标准,其以原子时秒长为基础,在时刻上尽量接近于格林尼治标准时间。
协调世界时是世界上调节时钟和时间的主要时间标准。

在时间轴的某一时刻,不管处于哪个时区,Unix时间戳是相同的。(见Unix时间戳的定义)

所以这个程序的错误就是通过加减时间戳来得到对应不同时区的时间,修改了时间戳就不再是对原来的时刻进行计算了,这里给时间戳加上8小时,从输出结果来看,显示的时间是对的,但时间戳是变化了的。

我们可以加一句输出来查看这个变化后的时刻实际对应UTC+8时区的时间是什么:

1
2
3
System.out.println("UTC+8实际时间: " + instantPlusEightHours.atZone(ZoneId.of("UTC+8")));
//Output:
//UTC+8实际时间: 2019-03-15T18:42:40.953+08:00[UTC+08:00]

可以看到,最终导致的结果是,这个时间快了8个小时,是错误的。

使用时区工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Instant now = Instant.now();
ZonedDateTime UTCplusEight = now.atZone(ZoneId.of("UTC+8"));

System.out.println("UTC: " + now);
System.out.println("UTC+8: " + UTCplusEight);

System.out.println("对应上面UTC的时间戳: " + now.toEpochMilli());
System.out.println("系统时间戳: " + System.currentTimeMillis());
System.out.println("对应上面UTC+8的时间戳: " + UTCplusEight.toInstant().toEpochMilli());

//Output:
//UTC: 2019-03-15T02:42:41.041Z
//UTC+8: 2019-03-15T10:42:41.041+08:00[UTC+08:00]
//对应上面UTC的时间戳: 1552617761041
//系统时间戳: 1552617761042
//对应上面UTC+8的时间戳: 1552617761041

通过ZonedDateTime来计算,实际是先将时间戳转换为UTC标准时间,之后做对应时区的偏移。