java 1900年_JDK与1900年01月01日

引子

最近使用Spark SQL读取Date类型(精度为天)的数据时发现一个很有意思的问题:

0: jdbc:hive2://0.0.0.0:10000> select cast('1900-01-01' as Date);

+-------------+--+| _c0 |

+-------------+--+| 1899-12-31 |

+-------------+--+1 row selected (0.038 seconds)

根据Spark的处理逻辑(Spark 1.6.1),上面的数据类型转换可以等效为下面的代码:

import java.util.Calendar;

import java.util.Date;

import java.util.TimeZone;

public class DateDemo {

public static void main(String[] args) {

// 1900-01-01 Date date = new Date(0, Calendar.JANUARY, 1);

long secondsPerDay = 24 * 3600 * 1000;

// Spark SQL内部使用EpochDays,即距离1970-01-01 00:00:00 GMT的天数,存储Date类型 long days = (date.getTime() + TimeZone.getDefault().getOffset(date.getTime())) / secondsPerDay;

// 将EpochDays还原为Date long millsOfDays = days * secondsPerDay;

Date newDate = new Date(millsOfDays - TimeZone.getDefault().getOffset(millsOfDays));

// 输出:Sun Dec 31 23:54:17 CST 1899 System.out.println(newDate);

}

}

分析

先来理解一下这个方法:java.util.TimeZone#getOffset(long)。

时区和零时区的时间差不是固定的么?为什么需要时间参数?

来看看java.util.TimeZone#getOffset(long)的Javadoc。

Returns the offset of this time zone from UTC at the specified date. If Daylight Saving Time is in effect at the specified date, the offset value is adjusted with the amount of daylight saving.

This method returns a historically correct offset value if an underlying TimeZone implementation subclass supports historical Daylight Saving Time schedule and GMT offset changes.

也就是说,时差还要考虑DST和历史上的时区变更!

通过stackoverlfow,我找到一个神奇的网站,记录了世界上主要城市的时区变更历史。与这个问题相关的时区(Asia/Shanghai)在1900年附近有如下变更:

在当地时间1901-01-01 00:00:00,上海时区由LMT(Local Mean Time)切换为CST (China Standard Time),与GMT的时差由+8:05:43调整为+8:00:00。

但JDK中的java.util.TimeZone#getOffset(long)对这一时区变更的处理似乎有些问题:

// 1900-01-01Date date1 = new Date(0, Calendar.JANUARY, 1);

// 输出:28800000,即+8:00:00System.out.println(TimeZone.getDefault().getOffset(date1.getTime()));

Date date2 = new Date(0, Calendar.JANUARY, 2);

// 输出:29143000,即+8:05:43System.out.println(TimeZone.getDefault().getOffset(date2.getTime()));

通过debug,发现上海时间1900-01-01 08:05:43(即1900-01-01 00:00:00 GMT)之前的时差被JDK错误计算成了8小时。

OpenJDK的bug跟踪系统里在2005年就记录了这个bug,至今未修复。。。

在DateDemo的代码中,millsOfDays的时间是1900-01-01 00:00:00 GMT,TimeZone.getDefault().getOffset(millsOfDays)返回的时差是+8:05:43。

但是因为JDK的bug,输出newDate对应的当地时间时,又是按照8小时的时差计算的,产生了5:43秒的误差。

总结

知识点:一个时区与零时区的时差在各个历史时期是不同的。

此外,Spark 2.4改进了Spark 1.6.1计算时差的逻辑,规避了JDK的bug:https://github.com/apache/spark/blob/7955b3962ac46b89564e0613db7bea98a1478bf2/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/util/DateTimeUtils.scala#L1085​github.com

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值