JDK8新时间API的用法
对于时间API,一般业务用到的比较少,因此平时不怎么太关注。
最近在做国际化业务,经常涉及不同时区、时间格式等问题;团队要求使用
LocalDateTime
等新实践API替代Date
类。
为什么用LocalDateTime
替代Date
类?
- 可变性:
Date
类是可变的,而LocalDateTime
是不可变的。这意味着一旦创建了LocalDateTime
对象,就不会发生任何更改。这有助于避免在多线程环境下的并发问题。 - 线程安全性:由于
LocalDateTime
是不可变的,它可以在多线程环境下安全地使用,而不需要额外的同步操作。 - 可读性:
LocalDateTime
具有更好的可读性和可理解性。它提供了明确的方法来获取和设置日期时间的各个部分,例如年、月、日、小时、分钟和秒等。 - 更灵活:
LocalDateTime
提供了更多的功能和灵活性。它可以轻松地进行日期时间的运算和比较,以及格式化为字符串和解析字符串为日期时间。
新API的使用
时间戳相关
Instant
类是JDK8
提供的一个用于记录时刻/时间点
的类。通俗来说,这个类的作用等同于一个时间戳。
根据JDK8官方文档的注释,Instant是指时间线上的一个瞬时点(An instantaneous point on the time-line.)。
构造Instant实例:
通过用Instant
来代替旧的System
方法,可以更高性能的获取当前时刻的时间戳:
关于System方法有并发性能问题:System.currentTimeMillis()性能问题
计算当前时刻的相对时刻
场景:计算相对当前时刻而言,7天后的时刻,用于给出数据的过期时间
通过使用Instant.plus()
方法,在当前时刻的基础上增加7天时间,获得对应的时间点,实现时间点在时间轴上的平移。举例如下:
注意:
Instant
提供了多种版本的plus
重载方法,上面版本的plus
方法接收两个入参:参数1为long
类型变量,指代时间点平移的大小,参数2为TemporalUnit
类型变量,指代某种时间单位。- 上面方法调用的第二个参数
DAYS
是ChronoUnit
枚举类的成员,ChronoUnit
枚举实现了TemporalUnit
接口,并定义了多种时间单位,小到诺秒NANOS
,大到世纪CENTURIES
,都被定义在其中。 Instant
提供了minus
方法,表示plus
方法的反操作,内部逻辑也是反向调用plus
方法,这里不再赘述。
比较时间先后
场景: 比较两个时间点的先后,判断是否过期/超期
通过isAfter()
、isBefore()
方法,对两个Instant实例进行比较,判断时间线上的先后顺序。举例如下:
日期时间相关
-
LocalDate用于表示一个日期,这个日期是指以年、月、日为单位表示的一个时间概念,不包括时分秒信息。
-
LocalTime用于表示一个时间,这个时间是指以时、分、秒为单位表示的一个时间概念,不包括年月日信息。
-
LocalDateTime是上述两个类的合成,内部仅有两个实例变量,分别是一个LocalDate实例和一个LocalTime实例。
注意:这3个类不包括任何时区信息,在表示效果上,这3个类内部存储的年月日时分秒信息与使用字符串存储基本没有区别。如果想要考虑时区相关因素,请参考下面的【时区相关】章节。
构造实例
根据时、分、秒、毫秒构造LocalTime或LocalDateTime
场景:需要用时、分、秒、毫秒来构造LocalTime、LocalDateTime,但不想把毫秒转为诺秒
首先构造LocalTime
实例,然后通过plus()
方法,在对应实例上增加毫秒级时间差值。举例如下:
注意:这里的plus
方法与Instant
类的plus
方法都是对他们的接口Temporal
的方法实现,但两者实现方式不同。
注意:
如果使用jackson
序列化LocalDateTime
,会出现序列化错误。这时需要引入相关的jackson-datatype
依赖解决问题:
时间间隔相关
Duration类是一个表示时间间隔的类,主要以较小的时间单位(日、时、分、秒等单位)来表示时间间隔。如果想要以较大的时间单位表示时间间隔,建议用Period类。
用处:计算今天早上9点到明天下午2点之间的时间间隔,并计算这段时间间隔有多少【天】,多少【小时】,多少【分钟】,多少【秒】
注意:Duration类可以用来表示超过24小时的时间间隔,但最大只支持以【日】为单位表示间隔,不支持以更大的单位,如【月】来表示间隔。
举例如下:Duration可以表示2022-0-0
到2023-0-0
之间的时间间隔,但通过API只能知道,这段时间间隔有365天(因为有toDays()
方法),不能通过API知道,这段时间间隔有1年(因为没有相关方法)。
构造实例
以指定时间单位表示对应时间间隔
场景:判断两个时间点之间的间隔是否超过1小时。举例如下:
注意:Duration的get
方法只支持秒和诺秒两个入参,输入其他时间单位会报错。
注意:Duration.get(ChronoUnit.NANOS)
和Duration.toNanos()
都是合法的方法调用,但两者含义不同:前者表示获取当前duration
中不足1秒的部分,并以诺秒表示;后者表示获取当前duration
的全部时间间隔,并以诺秒表示。
举例:对于一个表示1秒99诺秒的Duration,调用第一个方法会返回99,调用第二个方法会返回1000000099
(1 秒 = 1000000000 诺秒)。
Period类是一个表示时间间隔的类,主要以较大的时间单位(日、月、年)来表示时间间隔。如果想要以较小的时间单位表示时间间隔,建议用Duration类。
注意:Period类可以计算24小时内的时间间隔,但无法展示。这是因为Period最小只支持getDays()
方法,也就是说,只能知道这段时间间隔有几天,更小的时间单位,如小时,Period不支持。
构造实例
以指定时间单位表示对应时间间隔
场景:以天为单位获取Period实例对应时间间隔
注意:与Duration类似,Period的getDays()
方法并不是返回一段时间间隔里的总天数,而是返回这段时间里不足1月的时间的总天数。
时区相关
ZoneId是一个用于描述时区的类。
确定好时区后,时间戳信息才能正确的转化为时间信息,反之亦然。
构造实例
注意:ZoneId不支持接受如UTC+1:00
、GMT+1:00
、Etc/GMT-1:00
等参数进行实例构造。
注意:GMT和Etc/GMT是不同的时区表示方式。
根据Instant+ZoneId获取LocalDateTime
场景:需要根据时间戳和时区信息获取本地化时间
ZoneOffset是ZoneId的子类,用于表示不同时区间的时间差异。
构造实例
注意:当构造负时差时,相关的时分秒都要是负数
无时区时间转时间戳
对无时区时间传入时区信息,使其转换为时间戳
ZonedDateTime也是用于表示日期时间的类,与LocalDateTime类似,区别在于他带有时区信息,也就是说可以用于国际化时间转换。
构造实例
在本地时间不变的情况下获取不同时区的国际化时间
场景:需要获取A地5:30
的时间戳和B地5:30
的时间戳,重点在于无时区时间相同,但不在乎是否是同一时刻。
注意:上面的例子用LocalDateTime.atZone()
也可以实现,使用withZoneSameLocal()
方法看似是多余的。但有时并不能获取到ZonedDateTime对应的LocalDateTime,这是就需要withZoneSameLocal()
方法。
在时间戳不变的情况下获取其他地区的国际化时间
场景:需要获取A地5:30
时B地的无时区时间,重点在于两个时间要在同一时刻。
获取有时区时间在当前时区的无时区时间
注意:上面的代码并非无意义。ZonedDateTime本质上是用LocalDateTime+时区信息来表示国际化时间。当withZoneSameLocal()
方法或其他实例构造方法被调用时,ZonedDateTime类已经完成了对LocalDateTime和时区信息的正确赋值。所以对于一个已经构造完成的ZonedDateTime实例,直接调用toLocalDateTime()
方法即可获取正确的LocalDateTime结果。