logo

时间(2)

入门。

每种语言都会提供类或函数来处理日期和时间。但有时自带的函数不能满足需求,这就催生了很多第三方的函数库,比如 JavaScript 的 moment.js 和 Java 的 joda-time。Java 8 之后推出了新的java.time,相当于把 joda-time 并入了 Java。JavaScript 的moment.js也在 2020 正式退役。

在时间的计算上要格外注意时区,还有单位是秒还是毫秒。

放弃?

我们会用到SimpleDateFormatDate,先 import

jshell> import java.text.SimpleDateFormat;

jshell> import java.util.Date;

设好时间的格式,并把时区改为上海。

jshell> SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
df ==> java.text.SimpleDateFormat@4f76f1a0

jshell> df.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));

当我们计算 1900 年 12 月 31 日的 23:54:18 和 23:54:17 之间差距时,程序返回了 1000 毫秒,也就是 1 秒,准确无误。

jshell> df.parse("1900-12-31 23:54:18").getTime() - df.parse("1900-12-31 23:54:17").getTime()
$3 ==> 1000

但如果计算 23:54:17 和 23:54:16,结果却是……344 秒……

jshell> df.parse("1900-12-31 23:54:17").getTime() - df.parse("1900-12-31 23:54:16").getTime()
$4 ==> 344000

进阶!

这个页面的解释,上海时区在 1901 年 1 月 1 号 0 点前时间回拨了 5 分 43 秒(343 秒),到了 11:54:17。也就是说 11:54:17 出现了两次,第二次出现在 11:54:16 的 344 秒后。而程序中只保存了第二次的时间点。

这个问题有在Stackoverflow上讨论,但随着时间的推移,上面的例子已经不适用,最初的时间回拨发生在 1927-12-31 23:54:08,而且回拨的是 5 分 52 秒。这是由于不同版本的 JDK 采用不同版本的tzdata,所以必须强调这里用的例子适用的是 JDK 9,未来的某个时刻可能这个数据会被再次更改,例子将失效。

说这些的意义并不在于要记住某些特殊时区特殊时间点的改变细节,或是鼓励大家跟踪时区数据库的每一次细小变动。而是要强调每个时区的时间并不是线性的,有很多这样的突然跳跃,最常见的就是夏令时,所以用本地时间是极其不可靠的,应该尽量使用 UTC,程序中存储时间一定要用 Unix Time。