在 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。