1背景在Java8之前,在处理日期和时间时,使用“三大组件”DateCalenderSimpleDateFormat来声明时间戳,使用日历处理日期,格式化解析日期时间。但是,这些类的API可读性差、使用起来麻烦且非线程安全。像设计一样的IO也是Java被诟病的一大原因。所以Java8推出了一个新的日期和时间类。这些类的API功能强大、简单且线程安全。但毕竟Java8刚刚发布了这些类,序列化、数据访问等类库不支持Java8日期时间类,需要在新旧类之间来回切换。比如在业务逻辑层使用LocalDateTime,入库或者返回前端时切换回Date。因此,它还不如坚持使用旧的日期时间类。然而,我们生活在最好的时代。基本的主流类库都支持新的日期时间类型,但有些项目还在沿用祖传的日期时间类,古今交错的错误做法很多。比如通过随意修改时区,让读取的数据与当前时钟匹配,直接给读取的数据加上或减去几个小时来“修正数据”。本文旨在分析古今时间混淆的本质原因。使用遗留的日期时间类处理日期时间初始化、格式化、解析、计算等可能遇到的问题,以及如何使用新的日期时间类解决这些问题。2初始化日期时间初始化时间为2020年11月11日11:11:11,这样可行吗?日志输出时间为3029年12月11日11:11:11:date:SatDec1111:11:11CST3920这明显是彩笔写的垃圾代码,因为年份应该是和1900的差值,月份应该是0~11而不是1~12,应该是0~23而不是1~24。更正上面的代码如下:Datedate=newDate(2020-1900,10,11,11,11,11);日志输出:MonNov1111:11:11CST2019当有国际化需求时,必须使用Calendar类来初始化时间。使用Calendar转换后,year参数在初始化时可以直接使用当前年份,月份为0~11。也可以直接用Calendar.DECEMBER初始化月份,肯定不会出错。分别使用当前时区和纽约时区初始化两个相同的日期:日志输出显示两个不同的时间,说明时区有效。但我更习惯年/月/日时:分:秒的日期和时间格式。如果您对当前的输出日期格式不满意,请格式化日期和时间。3时区问题世界上有24个时区,同时不同的时区(如中国上海和美国纽约)时间不同。对于全球化项目,如果在初始化时间的时候没有提供时区,那不是实时时间,只能算是我看到的当前时间的一种表示。3.1Date类Date没有时区概念,任何用newDate()初始化的机器都会得到相同的时间。因为,Date中存储的是UTC时间,是基于原子钟的统一时间,并没有参照太阳计时。没有时区划分,Date中存储的是时间戳,代表从1970年1月1日0:00(纪元时间)到现在的毫秒数。尝试输出Date(0):System.out.println(newDate(0));System.out.println(TimeZone.getDefault().getID()+":"+TimeZone.getDefault().getRawOffset()/3600000);获取1970年1月1日8点。我的机器在中国上海,与UTC时差+8小时:ThuJan0108:00:00CST1970Asia/Shanghai:8对于国际项目,首先要处理时间和时区问题是保存日期和时间正确。这里有两种方法3.2如何正确保存日期和时间保存的时间是不带时区属性的UTC时间,以及不涉及时区时差问题的世界时。常说的时间戳或者Java中的Date类就是这种方式,也是推荐的方案,保存年/月/日时:分:秒等字面值,时区信息必须同时保存时间。有了时区,就可以知道字面时间的真实时间点,否则只是给人看的时间表示,只有在当前时区才有意义。Calendar只有时区的概念,所以可以通过初始化不同时区的Calendar得到不同的时间。日期时间保存正确后,才能正确显示,即使用正确的时区,将时间点显示为符合当前时区的时间表示。至此,我们就可以理解为什么会出现“时间紊乱”了。解析字面时间&格式化时间字面表示同一个时间,不同时区转换为Date会得到不同的时间戳,比如2020-11-1111:11:11为当前上海时区/纽约时间zone,转换为UTC时间戳不一样WedNov1111:11:11CST2020:1605064271000ThuNov1200:11:11CST2020:1605111071000这是UTC的意思,不是时间混淆。对于同一个本地时间的表示,不同时区的人解析出来的UTC时间肯定是不一样的。相反,不同的本地时间可能对应相同的UTC。格式化后出现的混淆是同一个Date,在不同的时区格式化得到不同的时间表示。在当前时区和纽约时区格式化2020-11-1111:11:11输出如下,当前时区Offset(时差)为+8小时,对于纽约为-5小时,因此,有时同一个时间在数据库中,由于服务器时区设置不同,读取的时间不同。这不是时间混淆,而是时区的影响,因为UTC时间需要根据当前时区解析为正确的本地时间。所以要正确处理时区,有保存和读取两个阶段。需要使用正确的当前时区保存,这样才能正确读取UTC时间,同时必须正确设置本地时区,才能将UTC时间转换为正确的本地时间。Java8处理时区时间和日期类ZoneId、ZoneOffset、LocalDateTime、ZonedDateTime和DateTimeFormatter使用起来更简单明了。初始化上海、纽约、东京三个时区,可以使用ZoneId.of初始化一个标准时区,也可以使用ZoneOffset.ofHours通过偏移量初始化一个指定时差的自定义时区。日期时间表示LocalDateTime没有时区属性,所以命名为本地时区的日期时间ZonedDateTime=LocalDateTime+ZoneId,有时区属性所以LocalDateTime只是时间表示,ZonedDateTime是有效时间。这里会表示2020-01-0222:00:00的时间,解析东京时区得到一个ZonedDateTime。使用DateTimeFormatter格式化时间可以直接通过withZone设置格式化使用的时区。最后格式化输出上海、纽约、东京三个时区的时间:/NewYork,输出本地时间不同。下午10:00。在+9小时时区是上海时区+8小时,所以上海当地时间是上午10:00,纽约时区是-5小时,晚了14小时,也就是晚上9:00。日期时间类,即使用ZonedDateTime保存时间,然后使用设置了ZoneId的DateTimeFormatter配合ZonedDateTime格式化时间,得到本地时间表示。转载本文请联系JavaEdge公众号。
