简介
Java8已经出了很长一段时间,主流的第三方库也都基本已经支持新的Time API,但是见到不少开发还是习惯用Date
和Calendar
,所以在这篇文章里总结如何使用这个新的Time API,以及旧API的一些设计上的缺陷和使用上的不便。
Date和Calendar的设计
Date设计的开始年份是1900年,月份是从0开始,日又是从1开始,所以想要创建一个2020年1月1日的Date对象得这样:
new Date(120,0,1);
这行代码很难直观看出是想要创建什么时候的Date对象,使用起来十分不便。
Calendar设计年份要好一些,去掉了从1900年开始的设计,但是月份还是从0开始计算,使用起来也是不那么直观:
Calendar calendar = Calendar.getInstance();
calendar.set(2020, 0, 1,0,0,0);
这样的设计和API的不一致,导致不少功能需要各个项目使用utils
类来重复实现,或者是使用Joda-Time
这样的第三方库。
好在Java8推出了新的Time API,JSR-310来解决这样的情况。新的API与Joda-Time
比较相似,但也有一些新的设计。具体可以查看Stephen Colebourne
的文章[1]。
Java8的TimeAPI的使用
这一节总结Time包下各个API的使用。
LocalDate、LocalTime和LocalDateTime
这几个类名取得比较好,把这几个类的名字合在一起看,可以比较直观的了解各个类的作用。LocalDate
是表示日期的类,LocalTime
表示时间,LocalDateTime
则表示日期和时间的组合。
使用这几个类创建一个表示日期、时间和日期时间组合的对象也非常直观,注释是打印后的结果:
LocalDate.of(2020, 1, 1);// 2020-01-01
LocalTime.of(0, 0);// 00:00
LocalDateTime.of(2020, 1, 1, 0, 0);// 2020-01-01T00:00
LocalDate.parse("2020-01-01");// 2020-01-01
LocalTime.parse("00:00:00");// 00:00
LocalDateTime.parse("2020-01-01T00:00:00");// 2020-01-01T00:00
这些类不仅使用起来很直观,toString()
的结果也很容易看懂。如果要打印Date对象,总是免不了要借助DateFormat
类来使打印更容易读懂。需要注意的是,LocalDateTime
默认是使用ISO 8601
标准,跟国内显示常用的会有些差别。
新的API修改时间也很方便和直观,能直接修改时间的每个节点,也能以某个时间为基点,相对地增加或减少时间,并且每次修改都会返回一个新的对象:
//直接修改
LocalDateTime.parse("2020-01-01T00:00:00")
.withYear(2010)
.withMonth(6)
.withDayOfMonth(16);//2010-06-16T00:00
//相对修改
LocalDateTime localDateTime = LocalDateTime.parse("2020-01-01T00:00:00");
localDateTime.plusDays(6);// 2020-01-07T00:00
localDateTime.plusHours(6);// 2020-01-01T06:00
localDateTime.minusDays(1);// 2019-12-31T00:00
如果有较为复杂的时间修改操作,可以使用TemporalAdjuster
类。它的辅助类TemporalAdjusters
定义了大量内置常见的复杂修改时间的操作。如果这些内置的方式不能满足需求,也可以自行实现TemporalAdjuster
类。
Instant
Instant类主要用于描述从Unix元年开始计算的时间,与System.currentTimeMillis()
比起来,除了能使用Instant.now().toEpochMilli()
来表示Unix元年到现在经过的毫秒数,也能方便使用Instant.now().getEpochSecond()
来表示经过秒数,还能使用多种方便的语义化操作。
Duration和Period
这两个对象都是用于表示两个时间的差值,不同点在于Duration
不支持LocalDate
的差值计算,只支持LocalTime
、LocalDateTime
和Instant
的差值计算。而Period
只支持LocalDate
的差值计算。一般来说,Duration
用于计算两个时间相差的天数、小时数、分钟数、秒数、毫秒数和纳秒数,而Period
用于计算相差的天数、月数和年数,如:
Duration duration = Duration.between(LocalDateTime.parse("2019-01-01T00:00:00"),LocalDateTime.parse("2020-01-01T00:00:00"));
duration.toDays();// 365
duration.toHours();// 8760
duration.toMinutes();// 525600
duration.getSeconds();// 31536000
duration.toMillis();// 31536000000
duration.toNanos();// 31536000000000000
Period period = Period.between(LocalDate.parse("2019-01-01"), LocalDate.parse("2020-02-05"));
period.getDays();// 4
period.getMonths();// 1
period.getYears();// 1
period;// P1Y1M4D
需要注意的是Period
类的差值是对应到每个单位上的,如上面的示例,getDays()
的结果不是400天,而是01日和05日的差值,4天。
时间格式化
Java8之前要做时间格式化经常需要用到SimpleDateFormat
类,但它不是线程安全的,虽然不影响使用,但是新的DateTimeFormatter
有着更好的设计。DateTimeFormatter
对象设计为线程安全的,并且内置了多种常见的格式。
常用格式化和解析时间示例如下:
//通过默认解析格式创建
LocalDateTime localDateTime = LocalDateTime.parse("2020-01-01T00:00:00");
//常见格式化输出,方式1
localDateTime.format(DateTimeFormatter.ISO_DATE_TIME);// 2020-01-01T00:00:00
localDateTime.format(DateTimeFormatter.ISO_DATE);// 2020-01-01
localDateTime.format(DateTimeFormatter.ISO_TIME);//00:00:00
//方式2
DateTimeFormatter.ISO_DATE_TIME.format(localDateTime);
DateTimeFormatter.ISO_DATE.format(localDateTime);
DateTimeFormatter.ISO_TIME.format(localDateTime);
//自定义格式化输出和解析格式
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
localDateTime.format(dateTimeFormatter);//2020-01-01 00:00:00 自定义格式化输出
LocalDateTime.parse("2020-01-01 00:00:00", dateTimeFormatter);//自定义解析格式
//使用Builder方式创建DateTimeFormatter,如:yyyy-MM-dd HH:mm:ss
new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_DATE)
.appendLiteral(" ")
.append(DateTimeFormatter.ISO_TIME).toFormatter();
类之间的转换
LocalDateTime
和LocalDate
、LocalTime
之间的转换
LocalDateTime localDateTime = LocalDateTime.of(LocalDate.now(), LocalTime.now());
localDateTime.toLocalDate();
localDateTime.toLocalTime();
LocalDateTime
、LocalDate
和Instant
之间的转换
Instant.now().atZone(ZoneId.systemDefault()).toLocalDateTime();//Instant转LocalDateTime
LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant();//LocalDateTime转Instant
LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();//LocalDateTime转毫秒
LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant();//LocalDate转Instant
总结
可以看到新的Time API使用起来比之前的要更加方便和直观。更多信息可以参考下面的参考资料。
参考资料
[1] https://blog.joda.org/2009/11/why-jsr-310-isn-joda-time_4941.html
[2] https://github.com/mybatis/mybatis-3/pull/1478 MyBatis支持JSR-310的讨论
[3] https://stackoverflow.com/questions/45863678/json-parse-error-can-not-construct-instance-of-java-time-localdate-no-string-a jackson使用JSR-310的方法
[4] https://docs.oracle.com/javase/8/docs/api/java/time/package-summary.html Javadoc
评论
发表评论