使用 Java8 新的时间 API

本文最后更新于:2020年12月30日 凌晨

简介

Java8 面世以来已经 6 年了,许多人也开始使用起了 lambda,Stream<T>,Optional<T> 之类的新的语言特性,然而对于 Java8 提供的新的时间 API 虽然据说比旧版本的 Date 好很多,但并没有得到完全的使用。一方面是为了兼容旧的系统,另一方面 Java8 的时间 API 似乎太过于强大了,让人有些不知所措,不知道应该从何下手。再加上因为对 Date,Calendar 的熟悉,此消彼长之下自然是懒得去修改了。

其实对于时间 API,大致的需求是一样的

  • 创建/修改/比较/转换简单
  • 对遗留系统的时间可以集成/转换
  • 主流框架对其要有支持

API

常用的类

LocalDate

一个不可变(线程安全)的日期对象,用且表示 年-月-日 的时间,默认 toString() 格式是 yyyy-MM-dd

基本操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//获取当前的日期
final LocalDate now = LocalDate.now();
//toString 一下日期
//会得到 yyyy-MM-dd 格式
System.out.println("现在:" + now);
//1 天后的日期
final LocalDate localDatePlusDayOne = now.plusDays(1);
System.out.println("一天后:" + localDatePlusDayOne);
//一天前的日期
final LocalDate localDateMinusDayOne = now.minusDays(1);
System.out.println("一天前:" + localDateMinusDayOne);
//比较两个日期/时间的大小
final int nowEvenBigger = now.compareTo(localDatePlusDayOne);
System.out.println("当前时间更大么?" + nowEvenBigger);
//获取指定单位的日期(年/月/日/星期)
//获取当前月的时间
final int dayOfMonth = now.getDayOfMonth();
System.out.println("当前月的天数:" + dayOfMonth);
//更加通用获取方式
//使用枚举类 ChronoField
final int dayOfMonthForChronoField = now.get(ChronoField.DAY_OF_MONTH);
System.out.println("当前月的天数(通过 get() 获取):" + dayOfMonthForChronoField);
//比较两个日期的差值
final long between = ChronoUnit.DAYS.between(now, localDatePlusDayOne);
System.out.println(between);

上面有些地方看不太懂不碍事,先过一遍,下面对其中的部分代码会有解释

LocalTime

一个不可变的(线程安全)的时间对象,用于表示 时:分:秒:毫秒 的时间,默认 toString() 格式是 hh:mm:ss.SSS

基本操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//获得当前时间
final LocalTime now = LocalTime.now();
//默认格式 hh:mm:ss.SSS
System.out.println("现在:" + now);
//一个小时后的时间
final LocalTime localTimePlusHourOne = now.plusHours(1);
System.out.println("一小时后:" + localTimePlusHourOne);
//一分钟前的时间
final LocalTime localTimeMinusMinuteOne = now.minusMinutes(1);
System.out.println(localTimeMinusMinuteOne);
//比较时间大小(实现了 Comparable 接口)
final int nowEvenBigger = now.compareTo(localTimeMinusMinuteOne);
System.out.println("当前时间更大么?" + nowEvenBigger);
//获取指定单位的时间
//当前小时数
final int hour = now.getHour();
System.out.println("当前时间的小时数:" + hour);
//使用枚举类 ChronoField 获取
final int hourOfDay = now.get(ChronoField.HOUR_OF_DAY);
System.out.println("当前时间的小时数(通过 get() 获取):" + hourOfDay);
//比较两个日期的差值
final long between = ChronoUnit.HOURS.between(now, localTimePlusHourOne);
System.out.println(between);

LocalDateTime

不可变的日期时间对象,用于表示 日-月-年 时:分:秒:毫秒 的日期时间,默认格式化格式是 yyyy-MM-ddThh:mm:ss.SSS

基本操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//当前日期时间
final LocalDateTime now = LocalDateTime.now();
//默认格式 yyyy-MM-ddThh:mm:ss.SSS
System.out.println("现在:" + now);
//一个小时后的时间
final LocalDateTime localTimePlusHourOne = now.plusHours(1);
System.out.println("一小时后:" + localTimePlusHourOne);
//一分钟前的时间
final LocalDateTime localTimeMinusMinuteOne = now.minusMinutes(1);
System.out.println(localTimeMinusMinuteOne);
//比较时间大小(实现了 Comparable 接口)
final int nowEvenBigger = now.compareTo(localTimeMinusMinuteOne);
System.out.println("当前时间更大么?" + nowEvenBigger);
//获取指定单位的时间
//当前小时数
final int hour = now.getHour();
System.out.println("当前时间的小时数:" + hour);
//使用枚举类 ChronoField 获取
final int hourOfDay = now.get(ChronoField.HOUR_OF_DAY);
System.out.println("当前时间的小时数(通过 get() 获取):" + hourOfDay);
//比较两个日期的差值
final long between = ChronoUnit.HOURS.between(now, localTimePlusHourOne);
System.out.println(between);
//获取一天的开始和结束
final LocalDateTime start = LocalDateTime.of(yesterday, LocalTime.MIN);
final LocalDateTime end = LocalDateTime.of(yesterday, LocalTime.MAX);
System.out.println(start);
System.out.println(end);

可以看到,和上面的 LocalTime 除了类型不同外,代码是完全相同的,因为 LocalDateTime 是包含 LocalDateLocalTime 的。在源码中也可以看到其包含了两个属性。

1
2
3
4
5
//获取日期/时间对象
final LocalDate localDate = now.toLocalDate();
final LocalTime localTime = now.toLocalTime();
System.out.println("当前日期:" + localDate);
System.out.println("当前时间:" + localTime);

OffsetDateTime

代表偏移标准 UTC 时间的日期时间不可变对象,用于表示 日-月-年 时:分:秒:毫秒时区,默认格式是 yyyy-MM-ddThh:mm:ss.SSSZoneId

基本操作

1
2
3
4
5
6
7
8
9
10
11
//当前偏移的日期时间
final OffsetDateTime now = OffsetDateTime.now();
System.out.println(now);
//其实个人感觉就是多了一个时区
//获取到时区
ZoneId zone = now.toZonedDateTime().getZone();
System.out.println(zone);
//转换时区
ZonedDateTime zonedDateTime = now.atZoneSameInstant(ZoneId.of("+00:00"));
System.out.println(zonedDateTime);
//其他基本操作和上面的差不多,就不啰嗦啦

Temporal/TemporalAccessor

上面的 LocalDate, LocalTime, LocalDateTime, OffsetDateTime 的基类,并定义了一系列非常通用的方法

  • minus: 减少时间
  • plus: 增加时间
  • with: 获取时间指定单位的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
final LocalDateTime now = LocalDateTime.now();
//获取上个星期
final LocalDateTime lastWeek = now.minus(1, ChronoUnit.WEEKS);
//获取下个月
final LocalDateTime nextMonth = now.plus(1, ChronoUnit.MONTHS);
//获取当前是今年的第几个星期
final int weekValue = now.get(ChronoField.ALIGNED_WEEK_OF_YEAR);
//获取这周星期一的时间
final LocalDateTime nowOfMonday = now.with(DayOfWeek.MONDAY);
final LocalDateTime nowOfMonday2 = now.with(ChronoField.DAY_OF_WEEK, 1);
System.out.println(
"lastWeek: " + lastWeek
+ "\nnextMonth: " + nextMonth
+ "\nweekValue: " + weekValue
+ "\nnowOfMonday: " + nowOfMonday
+ "\nnowOfMonday2: " + nowOfMonday2
);

Period

代表两个日期的差值,默认 toString() 格式是 P([时间][单位])*

基本操作

1
2
3
4
5
6
7
8
9
10
final LocalDate now = LocalDate.now();
final LocalDate localTimePlusMonthOne = now.plusDays(1);
//计算相差的时间
final Period betweenForDay = Period.between(now, localTimePlusMonthOne);
//获取相差的天数
System.out.println("相差的天数:" + betweenForDay.getDays());
//使用 get() 方法获取通用的相差的天数
System.out.println("相差的天数(使用 get() 获取):" + betweenForDay.get(ChronoUnit.DAYS));
//对相差的日期减去一天并判断是否为 0
System.out.println("相差的日期减去 1 天是不是就相同了呢?" + betweenForDay.minusDays(1).isZero());

Duration

代表两个日期时间的差值,默认 toString() 格式是 PT([时间][单位])*

基本操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
final LocalDateTime now = LocalDateTime.now();
final LocalDateTime localDateTimeForPlusHourOne = now.plusHours(1);
//计算两个时间的差值
final Duration between = Duration.between(now, localDateTimeForPlusHourOne);
//默认格式是 PT 时间 单位
System.out.println("相差的时间:" + between);
//获取日期
final long betweenSeconds = between.getSeconds();
System.out.println("相差的时间(/秒):" + betweenSeconds);
//根据指定单位获取相差时间的大小
final long betweenSecondsForGet = between.get(ChronoUnit.SECONDS);
System.out.println("相差的时间(/秒):" + betweenSecondsForGet);
//转换成毫秒
System.out.println("相差的时间(/毫秒):" + between.toMillis());
//获取相差时间支持的单位列表(其实感觉上没太大意义)
final List<TemporalUnit> temporalUnitList = between.getUnits();
System.out.println("相差的时间列表:" + temporalUnitList);
//在创建一个时间差
final Duration betweenForMinutes = Duration.between(now, now.minusMinutes(100));
//判断两个时间差哪个更大
System.out.println("between 的时间差更大么?" + between.compareTo(betweenForMinutes));
//获取绝对值
System.out.println("相差的绝对时间:" + betweenForMinutes.abs());

TemporalField/ChronoField

日期/时间单位字段,TemporalField 是接口,ChronoField 则是实现类。

基本操作

1
2
3
4
5
6
7
8
9
10
11
12
final LocalDateTime now = LocalDateTime.now();
final ChronoField hourOfDay = ChronoField.HOUR_OF_DAY;
System.out.println(hourOfDay);
//获取当前小时数
final long nowForHours = hourOfDay.getFrom(now);
System.out.println("当前时间的小时数:" + nowForHours);
//获取到指定单位的时间大小
final int hoursForGet = now.get(ChronoField.HOUR_OF_DAY);
System.out.println("当前时间的小时数(get()):" + hoursForGet);
//获取到特定单位的时间对比对象 Duration
final TemporalUnit baseUnit = hourOfDay.getBaseUnit();
System.out.println(baseUnit);

TemporalUnit/ChronoUnit

也是时间单位,TemporalUnit 是接口,ChronoUnit 则是实现类。和上面的不同的地方在于上面的不能 ChronoField 不能对时间进行对比差值,只能根据指定单位获取时间的大小。

基本操作

1
2
3
4
5
6
7
8
9
10
11
//获取一个小时数
final ChronoUnit chronoUnit = ChronoUnit.MILLIS;
System.out.println(chronoUnit);
//获取两个时间的差值毫秒数
final LocalDateTime now = LocalDateTime.now();
final LocalDateTime localDateTimePlusHourOne = now.plusHours(1);
final long betweenForMillis = chronoUnit.between(now, localDateTimePlusHourOne);
System.out.println("相差的毫秒数:" + betweenForMillis);
//根据指定的单位修改时间也需要使用这个
final LocalDateTime localDateTimePlusMinuteOne = now.plus(1, ChronoUnit.MINUTES);
System.out.println("一分钟后的时间:" + localDateTimePlusMinuteOne);

DateTimeFormatter

日期时间格式化类,基本上没什么好说的(标准的格式化一般就足够了,毕竟显示是前端的事情,而标准的格式化确实是国际标准呢)

基本使用

1
2
3
4
5
6
7
8
9
//获取一个标准 iso 日期时间格式化对象
final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ISO_DATE_TIME;
System.out.println(dateTimeFormatter);
//格式化日期时间
final LocalDateTime now = LocalDateTime.now();
System.out.println("当前日期时间:" + dateTimeFormatter.format(now));
//根据自定义的格式格式化时间
final DateTimeFormatter customDateTimeFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 hh时mm分ss秒");
System.out.println(customDateTimeFormatter.format(now));

兼容 Date

使用新的时间 API 固然很令人舒服,但有时候不得不兼容旧的 Date 类型,这时候如何转换就很重要了呢

1
2
3
4
5
6
7
8
9
10
11
12
13
14
final LocalDateTime now = LocalDateTime.now();
//转换为 Instant(包括时区)
final Instant instant = now.toInstant(ZoneOffset.UTC);
System.out.println(instant);
//转换为 Date
final Date date = Date.from(instant);
System.out.println("当前时间:" + date);
//转换为 Instant
final Instant dateToInstant = date.toInstant();
System.out.println("两次转换后的时间还是相同的吧?" + dateToInstant.equals(instant));
//转换为 LocalDateTime
final ZoneId zoneId = ZoneId.systemDefault();
final LocalDateTime toLocalDateTime = LocalDateTime.ofInstant(dateToInstant, zoneId);
System.out.println("转换后的日期时间:" + toLocalDateTime);

主流框架支持

现如今大部分的包应该都支持 Java8 时间 API 了吧

例如:

  • Jackson:jackson-datatype-jsr310
  • Mybatis:mybatis-typehandlers-jsr310

那么,关于 Java8 新的时间 API 的使用暂且到这里了,想到什么吾辈再补充啦(=´∇ `=)


使用 Java8 新的时间 API
https://blog.rxliuli.com/p/1be5a81d31cd4c0b9b1f198f556eec81/
作者
rxliuli
发布于
2020年4月18日
许可协议