告別jodatime!擁抱Java8日期時間類的最佳實踐

1 爲何須要新的日期和時間庫?

Java開發人員的一個長期煩惱是對普通開發人員的日期和時間用例的支持不足。java

例如,現有的類(例如java.util.Date和SimpleDateFormatter)是非線程安全的,從而致使用戶潛在的併發問題,這不是通常開發人員在編寫日期處理代碼時會指望處理的問題。 一些日期和時間類還表現出至關差的API設計。例如,年份java.util.Date從1900開始,月份從1開始,天從0開始,這不是很直觀。數據庫

這些問題以及其餘一些問題致使第三方日期和時間庫(例如Joda-Time)的欣欣向榮。安全

爲了解決這些問題並在JDK內核中提供更好的支持,針對Java SE 8設計了一個新的沒有這些問題的日期和時間API。該項目由Joda-Time(Stephen Colebourne)和Oracle的做者在JSR 310下共同領導,出如今Java SE 8軟件包中java.time。服務器

2 核心思想

不可變值類

Java現有格式化程序的嚴重缺陷之一是它們不是線程安全的。這給開發人員帶來了負擔,使其須要以線程安全的方式使用它們並在其平常處理日期處理代碼的過程當中考慮併發問題。新的API經過確保其全部核心類都是不可變的並表示定義明確的值來避免此問題。markdown

域驅動

新的API模型與表明不一樣的用例類域很是精確Date和Time嚴密。這與之前的Java庫不一樣,後者在這方面不好。例如,java.util.Date在時間軸上表示一個時刻(一個自UNIX紀元以來的毫秒數的包裝器),但若是調用toString(),結果代表它具備時區,從而引發開發人員之間的困惑。併發

這種對域驅動設計的重視在清晰度和易理解性方面提供了長期利益,可是當從之前的API移植到Java SE 8時,您可能須要考慮應用程序的域日期模型。分佈式

按時間順序分隔

新的API令人們可使用不一樣的日曆系統來知足世界某些地區(例如日本或泰國)用戶的需求,而這些用戶不必定遵循ISO-8601。這樣作不會給大多數開發人員帶來額外負擔,他們只須要使用標準的時間順序便可。測試

3 LocalDate、LocalTime、LocalDateTime

3.1 相比 Date 的優點

  • Date 和 SimpleDateFormatter 非線程安全,而 LocalDate 和 LocalTime 和 String 同樣,是final類型 - 線程安全且不能被修改。
  • Date 月份從0開始,一月是0,十二月是11。LocalDate 月份和星期都改爲了 enum ,不會再用錯。
  • Date是一個「萬能接口」,它包含日期、時間,還有毫秒數。若是你只須要日期或時間那麼有一些數據就沒啥用。在新的Java 8中,日期和時間被明確劃分爲 LocalDate 和 LocalTime,LocalDate沒法包含時間,LocalTime沒法包含日期。固然,LocalDateTime才能同時包含日期和時間。
  • Date 推算時間(好比往前推幾天/ 日後推幾天/ 計算某年是否閏年/ 推算某年某月的第一天、最後一天、第一個星期一等等)要結合Calendar要寫好多代碼,十分噁心!

兩個都是本地的,由於它們從觀察者的角度表示日期和時間,例如桌子上的日曆或牆上的時鐘。編碼

還有一種稱爲複合類LocalDateTime,這是一個LocalDate和LocalTime的配對。 spa

時區將不一樣觀察者的上下文區分開來,在這裏放在一邊;不須要上下文時,應使用這些本地類。這些類甚至能夠用於表示具備一致時區的分佈式系統上的時間。

經常使用 API

now()

獲取在默認的時區系統時鐘內的當前日期。該方法將查詢默認時區內的系統時鐘,以獲取當前日期。 使用該方法將防止使用測試用的備用時鐘,由於時鐘是硬編碼的。

方便的加減年月日,而沒必要親自計算!

plusMonths

返回此副本LocalDate添加了幾個月的指定數目。 此方法將分三步指定金額的幾個月字段:

  • 將輸入的月數加到month-of-year字段
  • 校驗結果日期是否無效
  • 調整 day-of-month ,若是有必要的最後有效日期

例如,2007-03-31加一個月會致使無效日期2007年4月31日。並不是返回一個無效結果,而是 2007-04-30纔是最後有效日期。調用實例的不可變性不會被該方法影響。

4 建立對象

工廠方法

新API中的全部核心類都是經過熟練的工廠方法構造。

  • 當經過其構成域構造值時,稱爲工廠of
  • 從其餘類型轉換時,工廠稱爲from
  • 也有將字符串做爲參數的解析方法。

getter約定

  • 爲了從Java SE 8類獲取值,使用了標準的Java getter約定,以下:

更改對象值

也能夠更改對象值以執行計算。由於新API中全部核心類都是不可變的,因此將調用這些方法with並返回新對象,而不是使用setter。也有基於不一樣字段的計算方法。

調整器

新的API還具備調整器的概念—一塊代碼塊,可用於包裝通用處理邏輯。能夠編寫一個WithAdjuster,用於設置一個或多個字段,也可編寫一個PlusAdjuster用於添加或減去某些字段。值類還能夠充當調節器,在這種狀況下,它們將更新它們表示的字段的值。內置調節器由新的API定義,可是若是您有想要重用的特定業務邏輯,則能夠編寫本身的調節器。

import static java.time.temporal.TemporalAdjusters.*;

LocalDateTime timePoint = ...
foo = timePoint.with(lastDayOfMonth());
bar = timePoint.with(previousOrSame(ChronoUnit.WEDNESDAY));

// 使用值類做爲調整器
timePoint.with(LocalTime.now()); 
複製代碼

5 截斷

新的API經過提供表示日期,時間和帶時間的日期的類型來支持不一樣的精確度時間點,可是顯然,精確度的概念比此精確度更高。

該truncatedTo方法存在支持這種使用狀況下,它可讓你的值截斷到字段,以下

LocalTime truncatedTime = time.truncatedTo(ChronoUnit.SECONDS);
複製代碼

6 時區

咱們以前查看的本地類抽象了時區引入的複雜性。時區是一組規則,對應於標準時間相同的區域。大約有40個。時區由它們相對於協調世界時(UTC,Coordinated Universal Time)的偏移量定義。它們大體同步移動,但有必定差別。

時區可用兩個標識符來表示:縮寫,例如「 PLT」,更長的例如「 Asia / Karachi」。在設計應用程序時,應考慮哪一種狀況適合使用時區,何時須要偏移量。

  • ZoneId是區域的標識符。每一個ZoneId規則都對應一些規則,這些規則定義了該位置的時區。在設計軟件時,若是考慮使用諸如「 PLT」或「 Asia / Karachi」之類的字符串,則應改用該域類。一個示例用例是存儲用戶對其時區的偏好。

  • ZoneOffset是格林威治/ UTC與時區之間的差別的時間段。可在特定的ZoneId,在特定時間被解析,如清單7所示。
ZoneOffset offset = ZoneOffset.of("+2:00");
複製代碼

7 時區類

ZonedDateTime是具備徹底限定時區的日期和時間。這樣能夠解決任什麼時候間點的偏移。 最佳實踐:若要表示日期和時間而不依賴特定服務器的上下文,則應使用ZonedDateTime

ZonedDateTime.parse("2007-12-03T10:15:30+01:00[Europe/Paris]");
複製代碼

OffsetDateTime是具備已解決偏移量的日期和時間。這對於將數據序列化到數據庫中頗有用,若是服務器在不一樣時區,則還應該用做記錄時間戳的序列化格式。

OffsetTime 是具備肯定的偏移量的時間,以下:

OffsetTime time = OffsetTime.now();
// changes offset, while keeping the same point on the timeline
OffsetTime sameTimeDifferentOffset = time.withOffsetSameInstant(
    offset);
// changes the offset, and updates the point on the timeline
OffsetTime changeTimeWithNewOffset = time.withOffsetSameLocal(
    offset);
// Can also create new object with altered fields as before
changeTimeWithNewOffset
 .withHour(3)
 .plusSeconds(2);
OffsetTime time = OffsetTime.now();
// changes offset, while keeping the same point on the timeline
OffsetTime sameTimeDifferentOffset = time.withOffsetSameInstant(
    offset);
// changes the offset, and updates the point on the timeline
OffsetTime changeTimeWithNewOffset = time.withOffsetSameLocal(
    offset);
// Can also create new object with altered fields as before
changeTimeWithNewOffset
 .withHour(3)
 .plusSeconds(2);
複製代碼

Java中已有一個時區類,java.util.TimeZone但Java SE 8並無使用它,由於全部JSR 310類都是不可變的而且時區是可變的。

8 時間段(period)

Period表明諸如「 3個月零一天」的值,它是時間線上的距離。這與到目前爲止咱們討論過的其餘類造成了鮮明的對比,它們是時間軸上的重點。

// 3 年, 2 月, 1 天
Period period = Period.of(3, 2, 1);

// 使用 period 修改日期值
LocalDate newDate = oldDate.plus(period);
ZonedDateTime newDateTime = oldDateTime.minus(period);
// Components of a Period are represented by ChronoUnit values
assertEquals(1, period.get(ChronoUnit.DAYS)); 
// 3 years, 2 months, 1 day
Period period = Period.of(3, 2, 1);

// You can modify the values of dates using periods
LocalDate newDate = oldDate.plus(period);
ZonedDateTime newDateTime = oldDateTime.minus(period);
// Components of a Period are represented by ChronoUnit values
assertEquals(1, period.get(ChronoUnit.DAYS)); 
複製代碼

9 持續時間(Duration)

Duration是時間線上按時間度量的距離,它實現了與類似的目的Period,但精度不一樣:

// 3 s 和 5 ns 的 Duration 
Duration duration = Duration.ofSeconds(3, 5);
Duration oneDay = Duration.between(today, yesterday);
// A duration of 3 seconds and 5 nanoseconds
Duration duration = Duration.ofSeconds(3, 5);
Duration oneDay = Duration.between(today, yesterday);
複製代碼

能夠對Duration實例執行常規的加,減和「 with」運算,還可使用修改日期或時間的值Duration。

10 年表

爲了知足使用非ISO日曆系統的開發人員的需求,Java SE 8引入了Chronology,表明日曆系統,並充當日曆系統中時間點的工廠。也有一些接口對應於核心時間點類,但經過

Chronology:
ChronoLocalDate
ChronoLocalDateTime
ChronoZonedDateTime
Chronology:
ChronoLocalDate
ChronoLocalDateTime
ChronoZonedDateTime
複製代碼

這些類僅適用於正在開發高度國際化的應用程序且須要考慮本地日曆系統的開發人員,沒有這些要求的開發人員不該使用它們。有些日曆系統甚至沒有一個月或一週的概念,所以須要經過很是通用的字段API進行計算。

11 其他的API

Java SE 8還具備一些其餘常見用例的類。有一個MonthDay類,其中包含一對Month和Day,對於表示生日很是有用。該YearMonth類涵蓋了信用卡開始日期和到期日期的用例以及人們沒有指定日期的場景。

Java SE 8中的JDBC將支持這些新類型,但不會更改公共JDBC API。現有的泛型setObject和getObject方法就足夠了。

這些類型能夠映射到特定於供應商的數據庫類型或ANSI SQL類型。

12 總結

Java SE 8在java.time中附帶一個新的日期和時間API,爲開發人員提供了大大改善的安全性和功能。新的API很好地建模了該領域,並提供了用於對各類開發人員用例進行建模的大量類。

相關文章
相關標籤/搜索