引子

最近使用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

java 1900年_JDK与1900年01月01日相关推荐

  1. 如何避免贫穷和忙碌,在2018年你需要这样提升自己 2018年01月07日 00:00:00 2099 热文导读 | 点击标题阅读 Java和Android架构2017年总结:文章精选 吊炸天!74

    如何避免贫穷和忙碌,在2018年你需要这样提升自己 2018年01月07日 00:00:00 2099 热文导读 | 点击标题阅读 Java和Android架构2017年总结:文章精选 吊炸天!74款 ...

  2. Python 是怎么火起来的? 转载 2018年01月12日 00:00:00 133 图:Python 之父 Guido 正在设计 Python 语言,结果家里突然潜入一条大蟒蛇,一番激烈斗争,大

    Python 是怎么火起来的? 转载 2018年01月12日 00:00:00 133 图:Python 之父 Guido 正在设计 Python 语言,结果家里突然潜入一条大蟒蛇,一番激烈斗争,大蟒 ...

  3. java为什么计算时间从1970年1月1日开始

    今天在看Python  API 时,看到 time 模块 : The epoch is the point where the time starts. On January 1st of that  ...

  4. 关于通过Date.getTime()得到1970年01月1日0点零分问题验证

    我的博客:通常认为Date.getTime()可以得到得到1970年01月1日0点零分以来的毫秒数,经过实践证明是错误的 实际上通过Date.getTime()的到的是1970年01月01日8点中以来 ...

  5. 01月26日【Python3 基础知识】

    01月26日[Python3 基础知识] 5.1 九宫格 5.2 函数入门 5.3 判断某天为某年的第几天 5.1 九宫格 import random x = 0 l = [1,2,3,4,5,6,7 ...

  6. 爬虫项目(四)---采集从01月22日以来全国各省疫情数据

    采集从03月02日以来全国各省疫情数据 当然,数据来源仍然是丁香园新型冠状病毒肺炎疫情实时动态首页 url:https://ncov.dxy.cn/ncovh5/view/pneumonia 分析 确 ...

  7. 乐视生态世界发布会官方图文直播(2016年01月12日 15:00)

    2016年01月12日 13:10 生态世界即将开启!乐视将再度引领潮流,让更多人感受到生态的魅力.再一次颠覆,你准备好了吗?彻底焕新,我们来了!1月12日15:00,乐视生态世界发布会全程直播,敬请 ...

  8. Pycharm Professional Edition 激活码(license),有效期至2018年01月30日

    Pycharm Professional Edition 激活码(license)(Yep, 请复制以下全部内容) BIG3CLIK6F-eyJsaWNlbnNlSWQiOiJCSUczQ0xJSzZ ...

  9. 【财经期刊FM-Radio|2021年01月19日】

    title: [财经期刊FM-Radio|2021年01月19日] 微信公众号: 张良信息咨询服务工作室 [今日热点新闻一览↓↓] 美股美债休市,欧股走出逾一周低谷,雪铁龙并购后新公司登陆欧股首日涨超 ...

  10. 当前你所在的服务器更新维护尚未完成,我们将于01月16日09:00-13:00对所有服务器进行更...

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 我们将于01月16日09:00-13:00对所有服务器进行更新维护,在此期间无法登录游戏,给大家带来的不便我们深表歉意. - 新武器 炼狱双刃(密码箱/活 ...

最新文章

  1. cookie JS验证码
  2. 无线可穿戴产品致胜设计六大策略
  3. python3环境下 tensorflow环境中经常遇到'*' has type str, but expected one of: bytes问题的解决
  4. 基于TCP协议的socket通信
  5. [译][Tkinter 教程14] menu 菜单
  6. Android自定义实现FlowLayout
  7. 关于JPA方法名创建自动查询
  8. post的4个参数和ajaxReturn方法
  9. 零基础带你学习MySQL—primary key主键(二十三)
  10. shell 写文件写入内容
  11. phpstudy mysql配置_phpstudy mysql数据库文件位置在哪
  12. 光伏并网系统的谐波分析
  13. 使用密码查看器查看软件的密码
  14. 单节锂电池充电管理芯片,IC电路图
  15. 洛谷P2736 “破锣摇滚”乐队 Raucous Rockers
  16. 2017年PHP培训机构排名
  17. 微信公众号后台添加安全域名 提示:无法访问xxx指向的web服务器(或虚拟主机)的目录,请检查网络设置
  18. 数据库与php衔接,【杂谈】PHP怎样衔接Mysql数据库
  19. 自动跟圈,不要来回编辑的偷懒小技巧
  20. 电路与模拟电子技术第三章(要点及部分习题)

热门文章

  1. linux驱动之字符设备
  2. Laravel 使用视图合成器 view composer 实现视图变量共享
  3. PyCharm安装第三方库如Requests
  4. 亚马逊云科技的Graviton3为什么只有64核,官方回应来了!
  5. 华为、腾讯、百度扎堆进入的自动驾驶仿真市场,到底藏着怎样的秘密?
  6. python怎么输出复数_python基础知识及应用(二)输入输出|递归|深浅拷贝|全局变量|复数...
  7. ant+JMeter接口自动化测试框架实践
  8. 《我的眼睛--图灵识别》第十一章:实战演练:图像类识别
  9. Unfortunately,Launcher has stopped
  10. canon老的一体机在win10上无法直接安装成功