Java8 日期與時間 API

在 Java 中,想處理日期和時間時,一般都會選用 java.util.Date 這個類進行處理。不過不知道是設計者在當時沒想好仍是其它緣由,在 Java 1.0 中引入的這個類,大部分的 API 在 Java 1.1 中就被標記爲了 Deprecated(已過期),而這些標記爲已過期的接口大部分都是一些 getter 和 setter,它們被移到了 java.util.Calendarjava.text.DateFormat 這些類裏面。這樣就出現了我想操做日期和時間,結果須要同時操做好幾個類,給編程帶來了麻煩。除此以外,java.util.Date 自己還有設計上的缺陷。諸如月份從 0 開始啦、年份從 1900 年開始推算(JavaScript 也是這個尿性),外部能夠隨意更改等。爲了解決這些痛點,也出現了一些第三方的工具包幫助開發者,在 Java 8 中,一組新的日期與時間 API (JSR-310)被引入進來,解決了上面的種種問題。接下來,將簡要介紹這一組 API,並給出我本身的一些使用建議。html

JSR-310(日期與時間 API)簡介

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 method
  • parse - static factory method focussed on parsing
  • get - gets the value of something
  • is - checks if something is true
  • with - the immutable equivalent of a setter
  • plus - adds an amount to an object
  • minus - subtracts an amount from an object
  • to - converts this object to another type
  • at - 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
}

其中當考生開始考試後,須要設置 planStartTimeplanEndTimeactualStartTime 爲相應的值。若是這裏使用 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。

相關文章
相關標籤/搜索