java拨钟,关于时间的那些事 - 中国也曾实行过夏令时,你知道吗?

之前遇到过一个由于标准时间/夏令时时间转换引起的问题,这里记录下来分享给大家。

大家都知道,地球上按照经度分成24个时区,每个时区相差一个小时。一般来说每个国家法定的时间都对应一个时区,比如中国用的东八区时间,韩国用的东九区时间,韩国时间比中国快一个小时。同时,很多高纬度国家都实行夏令时,即每到夏天把时钟拨快一个小时,每到冬天再把时钟拨慢一个小时,比如德国。

下图中可以看出很多国家现在或者曾经都实行过夏令时,蓝色表示正在实行,灰色表示曾经实行过,具体可查看wikipedia。

70354e349e8a0f76d0760dba1ad42925.png

问题的原型是:在Postgres数据库中有一张表,表的定义如下:

CREATE TABLE public.datetimetest (

createdat timestamp NULL,

id varchar NULL

);

首先通过DBeaver执行如下语句向表中插入三条记录:

--001--

INSERT INTO public.datetimetest (id, createdat) VALUES('001', '1989-04-16 00:02:00.78');

--002--

INSERT INTO public.datetimetest (id, createdat) VALUES('002', '1989-04-16 00:50:00.450');

--003--

INSERT INTO public.datetimetest (id, createdat) VALUES('003', '1989-04-16 01:00:00.32');

然后执行如下语句查询:

--query--

select id, createdat, createdat::text as createdatstr from public.datetimetest;

得到的结果如下:

195d08425e64f61cfa1594602af02b79.png

可以看到,记录001和002的createdat字段从00:02和00:50变成了01:02和01:50,这两条记录时间往后加了一个小时。但是,记录003却保持没变,和插入的时间是一致的。同时,可以看到,把createdat字段转成text类型,输出的值和插入的时间是一致的。

这是为什么呢?

开始还以为跟电脑或者数据库客户端IDE有关,后面发现在其他人电脑上有同样的问题。并且,用下面这段Java程序处理这个时间,出现了同样的问题,打印出来的时间也往后加了一个小时:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");

Date date = sdf.parse("1989-04-16 00:02:00.78");

System.out.println(date);

Sun Apr 16 01:02:00 CDT 1989 //打印结果,加了一个小时,timezone变成了CDT,中国的标准时间是CST

后来mina同学发现,这个问题是标准时间/夏令时转换引起的。

那么什么是夏令时呢?夏令时英文全称Daylight Saving Time,缩写为dst,在上世纪初,为了节约能源而出现的计时方式,即每到夏天,把时钟拨快一个小时,每到冬天再把时钟拨慢一个小时,这种夏令时制在很多高纬度国家都在使用,比如德国,他们的timezone是Europe/Berlin,每到夏天他们会把时钟拨快一个小时,采用东二区的时间(CEST:Central European Summer Time ),夏令时结束,又把时钟拨慢一个小时,采用东一区时间(CET:Central European Time)。一个实际的例子就是:喜欢看球赛的同学可能有注意到,欧冠比赛的比赛时间在夏天是北京时间凌晨2:45am,冬天则是3:45am,其实都是当地时间8:45pm开始。

现在很多人可能不知道,其实中国曾经也实行过夏令时(86年至91年),后来由于各种原因就取消了,全年采用统一的时间。关于为什么取消,网上有很多此方面的讨论。

现在计算机里面已经能够自动处理标准时间/夏令时的转换。比如用下面这段Java程序打印出当前时间:

System.out.println(new Date());

如果当前时间正处于标准时间/夏令时转换,程序会自动把当前时间加一个小时。

回到上面问题,我们把这个时间转成Java Date对象:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");

Date date = sdf.parse("1989-04-16 00:02:00.78");

System.out.println(date);

Sun Apr 16 01:02:00 CDT 1989 //打印结果,加了一个小时,timezone变成了CDT,中国的标准时间是CST

由于这个时间刚好是执行标准时间/夏令时转换的那一个小时,在转成Java Date对象时,jdk认为这是一个标准时间,需要自动加一个小时,变成夏令时(类似于人为拨快一个小时动作)。

而其他时间没有这个问题,是因为jdk认为传入的时间就是一个夏令时时间,直接应用夏令时。

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");

Date date = sdf.parse("1989-04-16 01:00:00.32");

System.out.println(date);

Sun Apr 16 01:00:00 CDT 1989 //打印结果

这就是为什么发现有问题的时间都是在标准时间/夏令时转换的这一个小时。尽管中国已经不实行夏令时了,jdk还是能够自动处理曾经实行过的这一段时间。

最后,当我们在处理时间时,如有必要,一定要把timezone信息存上,之前就遇到过由于压缩文件的时间戳不带timezone引起的问题,参见另一篇文章关于时间的那些事 - 文件的时间戳。

另外,由于夏令时的存在,程序在处理某些时间时,可能会把标准时间转成夏令时时间(反之亦然),导致意想不到的结果。

在研究过程中发现一个奇怪的现象:目前从各种资料上看到,中国实行夏令时的那几年(86-91年),标准时间到夏令时时间转换发生在凌晨2am,但是程序测试结果来看,转换是在0am,无论是java还是javascript,发现都是0am。

Java代码:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");

Date date = sdf.parse("1989-04-16 00:02:00.78");

System.out.println(date);

Sun Apr 16 01:02:00 CDT 1989 //打印结果可以看到,在夏令时转换的这一个小时,凌晨0点过的时间自动加了一个小时,变成了一点过.

Javascript代码:

new Date("1989-04-16 00:02:00.78");

Sun Apr 16 1989 01:02:00 GMT+0900 (China Daylight Time)

同理,德国的夏令时转换也是凌晨2am,但是程序处理这个转换就是两点,和网上资料显示时一致的。

TimeZone.setDefault(TimeZone.getTimeZone(ZoneId.of("Europe/Berlin")));

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");

Date date = sdf.parse("2019-03-31 02:02:00.78");

System.out.println(date);

Sun Mar 31 03:02:00 CEST 2019 //打印结果可以看到,在夏令时转换的这一个小时,凌晨两点过的时间自动加了一个小时,变成了三点过.

有童鞋知道这个现象吗?欢迎分享。

Rerefences

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值