Java以JDK 8为界,有两套处理日期和时间的API,分别是
Date
和JSR 310
。
java.util.Date自JDK1.0就已经存在,用于表示日期+时间的类型,虽然年代十分久远,并且此类具有的职责不单一,使用很不方便等诸多毛病,但因为时间久远,它的生命力依旧顽强,用户量庞大。
简单说几个Date的缺点:
-
定义不一致,在java.util和java.sql都有Date类,而且对它进行格式化/解析类又跑到了java.text中去了。
-
java.util.Date等类在建模日期的设计上行为不一致,缺陷明显。包括易变性、糟糕的偏移值、默认值、命名等。
-
java.util.Date同时包含日期和时间,而其子类java.sql.Date却仅仅包含日期。
-
国际化支持的并不是很好,比如跨时区操作,夏令时等。
14年随着Java 8的发布引入了全新的JSR 310日期时间。JSR 310源于joda-time打造,解决了诸多问题,是整个Java 8的最大亮点之一。
JSR 310日期时间所有的API都在java.time包中,没有例外。
时区/偏移量ZoneId
在JDK 8之前,Java使用java.util.TimeZone来表示时区。而在JDK 8之后分别使用了ZoneId表示时区,ZoneOffset表示UTC的偏移量。时区和偏移量在概念和实际作用上是有较大的区别的:
-
UTC偏移量仅仅是记录了偏移的小时分钟而已,除此之外并无其他任何信息。举个栗子:+8:00的意思就是比UTC时间早8小时,没有地理/时区的含义。
-
时区是特定与地区而言的,它和地理上的地区绑定在一起。比如整个中国都叫东八区。
中国没有夏令时,所有东八区对应的偏移量永远都是+8;纽约有夏令时,因此它的偏移量可能是-4或-5。
因为有着夏令时的存在,所以时区更好用。夏令时的问题,如果是用偏移量表示就会很麻烦,因为夏令时是可变的;用ZoneId时区去表示就很方便,因为时区内置了对夏令时规则的处理,在任何时候获取当地的时间都是正确的。
ZoneId代表是个时区的ID,如Asia/Shanghai。它规定了一些规则可用于将一个Instant时间戳转换为本地日期/时间LocaleDateTime。
JSR 310时区相关性
java.util.Date类型它具有时区无关性,带来的弊端就是一旦涉及到国际化时间转换等需求时,使用Date来处理很不方便。
JSR 310解决了Date存在的一系列问题:对日期、时间进行了分开表示(LocalDate,LocalTime,LocalDateTime),对本地时间和带时区的时间进行了分开管理。LocalXXX表示本地时间,也就是当前JVM所在的时区的时间,ZonedXXX表示的是一个带有时区的日期时间,它们能非常方便的互相完成转换。
读取字符串为JSR 310类型
一个独立的日期时间类型字符串如2021-06-27T09:20它是没有任何意义的,因为没有时区无法确定它代表哪个瞬间。
遇到一个日期时间格式字符串,要解析它一般有两种情况:
-
不带时区/偏移量的字符串:要么不理会说转换不了,要么约定一个时区(一般使用默认时区),使用LocalDateTime解析。
-
带时区的字符串用ZonedDateTime解析,带偏移量的字符串用OffsetDateTime解析。注意:有可能字符串参数偏移量为-05:00,转换为ZonedDateTime后偏移量就为-4:00,这是由于夏令时的缘故。
JSR 310格式化
针对JSR 310日期时间的格式化/解析,有一个专门的类
java.time.format.DateTimeFormatter
用于处理。DateTimeFormatter是一个不可变的类,所以线程是安全的,它还内置了许多格式化模板实例供使用。