26.5 Java 8的日期和时间API
26.5 Java 8的日期和时间API
本节介绍Java 8对日期和时间API的增强。我们在之前介绍了Java 8以前的日期和时间API,主要的类是Date和Calendar,由于它的设计有一些不足,Java 8引入了一套新的API,位于包java.time下。本节我们就来简要介绍这套新的API,先从日期和时间的表示开始。
26.5.1 表示日期和时间
我们在第7章介绍过日期和时间的几个基本概念,包括时刻、时区和年历,这里就不赘述了。Java 8中表示日期和时间的类有多个,主要的有:
- Instant:表示时刻,不直接对应年月日信息,需要通过时区转换;
- LocalDateTime:表示与时区无关的日期和时间,不直接对应时刻,需要通过时区转换;
- ZoneId/ZoneOffset:表示时区;
- LocalDate:表示与时区无关的日期,与LocalDateTime相比,只有日期,没有时间信息;
- LocalTime:表示与时区无关的时间,与LocalDateTime相比,只有时间,没有日期信息;
- ZonedDateTime:表示特定时区的日期和时间。
类比较多,但概念更为清晰了,下面我们逐个介绍。
1. Instant
Instant表示时刻,获取当前时刻,代码为:
1 | Instant now = Instant.now(); |
可以根据Epoch Time(纪元时)创建Instant。比如,另一种获取当前时刻的代码可以为:
1 | Instant now = Instant.ofEpochMilli(System.currentTimeMillis()); |
我们知道,Date也表示时刻,Instant和Date可以通过纪元时相互转换,比如,转换Date为Instant,代码为:
1 | public static Instant toInstant(Date date) { |
转换Instant为Date,代码为:
1 | public static Date toDate(Instant instant) { |
Instant有很多基于时刻的比较和计算方法,大多比较直观,我们就不列举了。
2. LocalDateTime
LocalDateTime表示与时区无关的日期和时间,获取系统默认时区的当前日期和时间,代码为:
1 | LocalDateTime ldt = LocalDateTime.now(); |
还可以直接用年月日等信息构建LocalDateTime。比如,表示2017年7月11日20点45分5秒,代码可以为:
1 | LocalDateTime ldt = LocalDateTime.of(2017, 7, 11, 20, 45, 5); |
LocalDateTime有很多方法,可以获取年月日时分秒等日历信息,比如:
1 | public int getYear() |
还可以获取星期几等信息,比如:
1 | public DayOfWeek getDayOfWeek() |
DayOfWeek是一个枚举,有7个取值,从DayOfWeek.MONDAY到DayOfWeek.SUN-DAY。
3. ZoneId/ZoneOffset
LocalDateTime不能直接转为时刻Instant,转换需要一个参数ZoneOffset,ZoneOffset表示相对于格林尼治的时区差,北京是+08:00。比如,转换一个LocalDateTime为北京的时刻,方法为:
1 | public static Instant toBeijingInstant(LocalDateTime ldt) { |
给定一个时刻,使用不同时区解读,日历信息是不同的,Instant有方法根据时区返回一个ZonedDateTime:
1 | public ZonedDateTime atZone(ZoneId zone) |
默认时区是ZoneId.systemDefault(),可以这样构建ZoneId:
1 | //北京时区 |
ZoneOffset是ZoneId的子类,可以根据时区差构造。
4. LocalDate/LocalTime
可以认为LocalDateTime由两部分组成,一部分是日期LocalDate,另一部分是时间LocalTime。它们的用法也很直观,比如:
1 | //表示2017年7月11日 |
LocalDateTime由LocalDate和LocalTime构成,LocalDate加上时间可以构成LocalDate-Time, LocalTime加上日期可以构成LocalDateTime,比如:
1 | LocalDateTime ldt = LocalDateTime.of(2017, 7, 11, 20, 45, 5); |
5. ZonedDateTime
ZonedDateTime表示特定时区的日期和时间,获取系统默认时区的当前日期和时间,代码为:
1 | ZonedDateTime zdt = ZonedDateTime.now(); |
LocalDateTime.now()也是获取默认时区的当前日期和时间,有什么区别呢?Local-DateTime内部不会记录时区信息,只会单纯记录年月日时分秒等信息,而ZonedDateTime除了记录日历信息,还会记录时区,它的其他大部分构建方法都需要显式传递时区,比如:
1 | //根据Instant和时区构建ZonedDateTime |
ZonedDateTime可以直接转换为Instant,比如:
1 | ZonedDateTime ldt = ZonedDateTime.now(); |
26.5.2 格式化
Java 8中,主要的格式化类是java.time.format.DateTimeFormatter,它是线程安全的,看个例子:
1 | DateTimeFormatter formatter = DateTimeFormatter.ofPattern( |
输出为:
1 | 2016-08-18 14:20:45 |
将字符串转化为日期和时间对象,可以使用对应类的parse方法,比如:
1 | DateTimeFormatter formatter = DateTimeFormatter.ofPattern( |
26.5.3 设置和修改时间
修改时期和时间有两种方式,一种是直接设置绝对值,另一种是在现有值的基础上进行相对增减操作,Java 8的大部分类都支持这两种方式。另外,Java 8的大部分类都是不可变类,修改操作是通过创建并返回新对象来实现的,原对象本身不会变。我们来看一些例子。
调整时间为下午3点20分,代码为:
1 | LocalDateTime ldt = LocalDateTime.now(); |
还可以为:
1 | LocalDateTime ldt = LocalDateTime.now(); |
3小时5分钟后,示例代码为:
1 | LocalDateTime ldt = LocalDateTime.now(); |
LocalDateTime有很多plusXXX和minusXXX方法,分别用于相对增加和减少时间。
今天0点,可以为:
1 | LocalDateTime ldt = LocalDateTime.now(); |
ChronoField是一个枚举,里面定义了很多表示日历的字段,MILLI_OF_DAY表示在一天中的毫秒数,值从0到(24 * 60 * 60 * 1000)-1。还可以为:
1 | LocalDateTime ldt = LocalDateTime.of(LocalDate.now(), LocalTime.MIN); |
LocalTime.MIN表示”00:00”。也可以为:
1 | LocalDateTime ldt = LocalDate.now().atTime(0, 0); |
下周二上午10点整,可以为:
1 | LocalDateTime ldt = LocalDateTime.now(); |
上面下周二指定是下周,如果是下一个周二呢?这与当前是周几有关,如果当前是周一,则下一个周二就是明天,而其他情况则是下周,代码可以为:
1 | LocalDate ld = LocalDate.now(); |
针对这种复杂一点的调整,Java 8有一个专门的接口TemporalAdjuster,这是一个函数式接口,定义为:
1 | public interface TemporalAdjuster { |
Temporal是一个接口,表示日期或时间对象,Instant、LocalDateTime和LocalDate等都实现了它,这个接口就是对日期或时间进行调整,还有一个专门的类TemporalAdjusters,里面提供了很多TemporalAdjuster的实现。比如,针对下一个周几的调整,方法是:
1 | public static TemporalAdjuster next(DayOfWeek dayOfWeek) |
针对上面的例子,代码可以为:
1 | LocalDate ld = LocalDate.now(); |
这个next方法是怎么实现的呢?看代码:
1 | public static TemporalAdjuster next(DayOfWeek dayOfWeek) { |
它内部封装了一些条件判断和具体调整,提供了更为易用的接口。
TemporalAdjusters中还有很多方法,部分方法如下:
1 | public static TemporalAdjuster firstDayOfMonth() |
这些方法的含义比较直观,就不解释了。它们主要是封装了日期和时间调整的一些基本操作,更为易用。
明天最后一刻,代码可以为:
1 | LocalDateTime ldt = LocalDateTime.of( |
或者为:
1 | LocalDateTime ldt = LocalTime.MAX.atDate(LocalDate.now().plusDays(1)); |
本月最后一天最后一刻,代码可以为:
1 | LocalDateTime ldt = LocalDate.now() |
lastDayOfMonth()是怎么实现的呢?看代码:
1 | public static TemporalAdjuster lastDayOfMonth() { |
这里使用了range方法,从它的返回值可以获取对应日历单位的最大最小值,展开,本月最后一天最后一刻的代码还可以为:
1 | long maxDayOfMonth = LocalDate.now().range( |
下个月第一个周一的下午5点整,代码可以为:
1 | LocalDateTime ldt = LocalDate.now().plusMonths(1) |
26.5.4 时间段的计算
Java 8中表示时间段的类主要有两个:Period和Duration。Period表示日期之间的差,用年月日表示,不能表示时间;Duration表示时间差,用时分秒等表示,也可以用天表示,一天严格等于24小时,不能用年月表示。下面看一些例子。
计算两个日期之间的差,看个Period的例子:
1 | LocalDate ld1 = LocalDate.of(2016, 3, 24); |
输出为:
1 | 1年3月18天 |
根据生日计算年龄,示例代码可以为:
1 | LocalDate born = LocalDate.of(1990,06,20); |
计算迟到分钟数,假定早上9点是上班时间,过了9点算迟到,迟到要统计迟到的分钟数,怎么计算呢?看代码:
1 | long lateMinutes = Duration.between(LocalTime.of(9,0), |
26.5.5 与Date/Calendar对象的转换
Java 8的日期和时间API没有提供与老的Date/Calendar相互转换的方法,但在实际中,我们可能是需要的。前面介绍了Date可以与Instant通过毫秒数相互转换,对于其他类型,也可以通过毫秒数/Instant相互转换。比如,将LocalDateTime按默认时区转换为Date,代码可以为:
1 | public static Date toDate(LocalDateTime ldt){ |
将ZonedDateTime转换为Calendar,代码可以为:
1 | public static Calendar toCalendar(ZonedDateTime zdt) { |
Calendar保持了ZonedDateTime的时区信息。
将Date按默认时区转换为LocalDateTime,代码可以为:
1 | public static LocalDateTime toLocalDateTime(Date date) { |
将Calendar转换为ZonedDateTime,代码可以为:
1 | public static ZonedDateTime toZonedDateTime(Calendar calendar) { |
至此,关于Java 8的日期和时间API就介绍完了。相比以前版本的Date和Calendar,它引入了更多的类,但概念更为清晰,更为强大和易用。
本章介绍了Java 8引入的Lambda表达式、函数式编程,以及日期和时间API,利用本章介绍的内容,我们可以在更高的抽象层次上思考和解决问题,包括处理集合数据、管理异步任务、操作日期和时间等。