跳至主要内容

Java8:Time API使用总结

Java8:Time API使用总结

简介

Java8已经出了很长一段时间,主流的第三方库也都基本已经支持新的Time API,但是见到不少开发还是习惯用DateCalendar,所以在这篇文章里总结如何使用这个新的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的差值计算,只支持LocalTimeLocalDateTimeInstant的差值计算。而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();

类之间的转换

LocalDateTimeLocalDateLocalTime之间的转换

LocalDateTime localDateTime = LocalDateTime.of(LocalDate.now(), LocalTime.now());

localDateTime.toLocalDate();
localDateTime.toLocalTime();

LocalDateTimeLocalDateInstant之间的转换

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

评论

此博客中的热门博文

国密SM2签名封装成PKCS7格式

在国内做金融行业,难免会有被强制使用国密算法的情况,而且一般还会指定必须使用硬件加密机之类的设备,所以我也稍微的研究了一下国密算法,使用软算法签名并封装 PKCS7 格式(文档中的一个交互)。 以下是基于 Bouncy Castle 的示例,密钥对的生成可以参考 Bouncy Castle 中 test 包下 SM2 相关代码 public static String sign ( ) throws Exception { //加载公钥 byte [ ] plainText = "hello, world" . getBytes ( ) ; FileInputStream input = new FileInputStream ( "F:\\certificate\\public.cer" ) ; CertificateFactory certificateFactory = new CertificateFactory ( ) ; X509Certificate certificate = ( X509Certificate ) certificateFactory . engineGenerateCertificate ( input ) ; input . close ( ) ; //加载私钥,private为换成实际的私钥 PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec ( "private" . getBytes ( ) ) ; //SM2算法实际上为ECC算法,并指定了一些参数值,所以这里的参数是EC KeyFactory factory = KeyFactory . getInstance ( "EC" , "BC" ) ; PrivateKey privateKey = factory . generatePrivate ( spec ) ; //以下为签名并封装成PKCS7格式 byte [ ] signedMessag

Spring Boot Actuator 2 示例

Welcome file 简介 Spring Boot Actuator为应用程序提供了各种开箱即用的运维特性,可以与应用方便的交互和监控。 使用环境:Java 11 和 Spring Boot 2.4.3.RELEASE 集成Spring Boot Actuator 在Spring Boot中集成Spring Boot Actuator与集成其他的框架类似,在 pom.xml 里引入相关的starter就可以: < dependency > < groupId > org.springframework.boot </ groupId > < artifactId > spring-boot-starter-actuator </ artifactId > </ dependency > < dependency > < groupId > org.springframework.boot </ groupId > < artifactId > spring-boot-starter-web </ artifactId > </ dependency > 由于大部分的使用场景还是web,所以这里也用Spring MVC做示例。 配置好 pom.xml 后,默认actuator仅暴露一些基本功能,实际使用中,根据需求暴露对应功能。为了简便测试,这里在 application.yml 中配置暴露全部功能: management : endpoints : web : exposure : include : "*" endpoint : health : enabled : true show-details : always probes : enabled : true shutdown : enabled : true metr

NextCloud数据目录迁移

最近服务器的环境坏了,所以迁移了NextCloud的数据目录。不过在迁移过程中有点小问题。 环境: Ubuntu 18.04 Docker 19.03.7 1.NextCloud页面不正常,Docker日志显示XX目录permission denied 参考了 这里 的做法,不过是把  /var/www/html/   整个目录的权限都修改为  chown -R www-data:www-data ,之后就不再报权限问题了。 2.数据库配置修改 因为NextCloud是在初始化时填的数据库连接信息,所以直接迁移数据目录的情况下,会导致应用连不到新的数据库环境。此时可以找到数据目录下的  config/config.php 文件,直接修改数据库连接配置。