在 Java 中,想处理日期和时间时,一般都会选用 java.util.Date
这个类进行处理。不过不知道是设计者在当时没想好仍是其它缘由,在 Java 1.0 中引入的这个类,大部分的 API 在 Java 1.1 中就被标记为了 Deprecated(已过期),而这些标记为已过期的接口大部分都是一些 getter 和 setter,它们被移到了 java.util.Calendar
和 java.text.DateFormat
这些类里面。这样就出现了我想操做日期和时间,结果须要同时操做好几个类,给编程带来了麻烦。除此以外,java.util.Date
自己还有设计上的缺陷。诸如月份从 0 开始啦、年份从 1900 年开始推算(JavaScript 也是这个尿性),外部能够随意更改等。为了解决这些痛点,也出现了一些第三方的工具包帮助开发者,在 Java 8 中,一组新的日期与时间 API (JSR-310)被引入进来,解决了上面的种种问题。接下来,将简要介绍这一组 API,并给出我本身的一些使用建议。html
Java 8 中引入的这一套新的 API 位于 java.base
模块,java.time
包下。官方文档对这一组 API 的描述以下:java
The main API for dates, times, instants, and durations.数据库
The classes defined here represent the principle date-time concepts, including instants, durations, dates, times, time-zones and periods. They are based on the ISO calendar system, which is the de facto world calendar following the proleptic Gregorian rules. All the classes are immutable and thread-safe.编程
简单地说,就是把咱们能想到的全部对时间的相关操做都包含进来了。这些日期/时间/时刻/时段类的实体都是不可改变(immutable)的,对它们的任何修改都将产生一个新的对象。所以也是线程安全的。api
在这个包下经常使用的一些类/枚举列举以下安全
class /enum |
描述 |
---|---|
Instant |
表示时间轴上的一个时刻。与 java.util.Date 能够经过 Date.toInstant() 和 Date.from(Instant) 进行互相转化。 |
LocalDateTime /LocalDate /LocalTime |
表示(不带时区的)日期/时间 |
DayOfWeek |
定义了一组表示星期几的常量(e.g. TUESDAY ) |
Month |
定义了一组表示月份的常量(e.g. JULY ) |
在 API 的命名上,该包下的 API 命名遵循以下规则:mybatis
of
- static factory methodparse
- static factory method focussed on parsingget
- gets the value of somethingis
- checks if something is truewith
- the immutable equivalent of a setterplus
- adds an amount to an objectminus
- subtracts an amount from an objectto
- converts this object to another typeat
- combines this object with another, such as date.atTime(time)
另外,根据 MyBatis 文档的说明,从 3.4.5 开始,MyBatis 默认支持 JSR-310(日期和时间 API),因此用上述的日期与时间 API 也是能够用来操做数据库中的时间数据的。其它的 ORM 框架(Hibernate 等)应该也提供了对这一套 API 的支持。框架
对于 Android 开发者来讲,印象中是直到 API Level 28 才可以使用 JSR-310 这一套 API,因此对于 Android 开发者来讲,使用第三方的日期时间库多是更稳当的选择(出于应用程序的向下兼容)工具
这里举一个我在开发考试系统过程当中的例子。读者也能够经过阅读这篇文章获取更加完整的 API 使用的示例。ui
在考生登陆考试系统中,须要设定他的考试开始时间和结束时间,其中开始时间默认为如今,结束时间由开始时间加上试卷上的考试时间(以分钟为单位)获得。
考试记录的实体类的部分代码以下:
@TableName("t_exam_record") public class ExamRecordEntity implements Serializable { private static final long serialVersionUID = 1L; /** * 考试记录ID */ @TableId("id") private Long examRecordId; /** * 计划开始时间 */ @TableField("plan_start_time") private Instant planStartTime; /** * 计划结束时间 */ @TableField("plan_end_time") private Instant planEndTime; /** * 实际开始时间 */ @TableField("actual_start_time") private Instant actualStartTime; /** * 实际结束时间 */ @TableField("actual_end_time") private Instant actualEndTime; // 省略 getter、setter }
其中当考生开始考试后,须要设置 planStartTime
、planEndTime
和 actualStartTime
为相应的值。若是这里使用 java.util.Date
,那么除了直接用 new
建立一个实体会比较方便之外,其它的操做就变得至关麻烦了。而若是用上新式 API,只要如下几行代码便可完成:
/** * 考试开始,保存考试记录 * @param limitTime 考试时长(分钟为单位) * @return 考试记录的 DTO,用于后续处理 */ private MobilePaperDTO saveNewRecord(int limitTime) { ExamRecordEntity entity = new ExamRecordEntity(); Instant currentTime = Instant.now(); entity.setPlanStartTime(currentTime); entity.setActualStartTime(currentTime); entity.setPlanEndTime(currentTime.add(limitTime, ChronoUnit.MINUTES)); // 省略其它操做 }
其中 ChronoUnit
是预先定义好的一组时间单位。因为 Instant
表明的是时间轴上的一个点,只能加减上一个“时间段”(在 java.time.temporal
包下有相关定义)。若是这里选择使用 LocalDateTime
保存日期和时间,则可直接使用 LocalDateTime.plusMinutes()
方法。Java 并不支持运算符重载,否则在某些支持运算符重载的语言(例如 Kotlin)上,这套 API 能够表现的更优雅一些。
就我我的的使用看法来讲,这部分新的 API 确定是越早引入越好。若是是老旧系统或者和别的不熟悉这套 API 的开发者协同开发,建议直接使用 Instant
,由于这个就是官方用来取代 Date
的类,而且与 Date
间能够相互转化,以后再慢慢引入其它 API。