2016年6月20日 星期一

在 Java 中解析、表達與計算日期時間(二):JSR-310 概述

原本在 Java 7 和更之前的版本中,因為 Java 原生並沒有好的時間類別
因此比較合適的方式是使用 Joda-Time 這個套件,用來表示日期與時間。
不過在 Java 8 當中釋出了 JSR-310,也就是 java.time 套件之後,狀況就不同了
Joda-Time 官方網站中也說明了,如果使用 Java 8 以上版本,應轉移至 JSR-310。

Joda-Time is the de facto standard date and time library for Java prior to Java SE 8. Users are now asked to migrate to java.time (JSR-310).

    Date 與 Calendar 有什麼問題?

    在開始使用以前,首先會先想到的問題是,Java 本來就有 Date 和 Calendar 類別
    為什麼不使用 Date 和 Calendar,而需要另外使用別的類別呢?
    這個問題其實可以參考良葛格的文章 [2],其實主要是使用上的問題。
    也就是 Date 與 Calendar 都是可變的,這會導致在使用上常常創造出無謂的 bug
    另外 Date 與 Calendar 也有一些奇妙的設計,在沒有特別理解的情況下很容易誤用。

    其實講白話一點,我個人覺得就是 Date 與 Calendar 的 API 設計不完善,造成使用者使用上不方便、要注意的細節頗多
    另外提供的運算也不夠完整,,有很多使用者必須自行實作的部份。

    Joda-Time 與 JSR-310 的關係

    接著,有些人可能過去就有在使用 Joda-Time 這個套件,後來這個套件的作者也被找去設計 JSR-310
    但為什麼作者不直接將 Joda-Time 移入成為 JSR-310 呢?
    這一樣可以參考良葛格的文章 [3] XD,主要是作者覺得 Joda-Time 有些他想修改的問題。
    Joda-Time 的作者 Stephen Colebourne 在 [4] 中描述了,他在 JSR-310 中想要改進的 Joda-Time 問題:

    • Human/Machine timelines
    • Pluggable chronology
    • Nulls
    • Internal implementation

    細節的描述可以參考良葛格的文章 [3] 或者原文 [4]。

    JSR-310 概觀

    在 Java 8 的 JSR-310 中,主要有幾種不同的元件:

    • 表達時間流上的一個瞬間,如 Instant
    • 表達人類能看懂的部份時間資訊,例如 LocalDate、LocalTime、Duration 等
    • 表達時區資訊,例如 ZoneId、ZoneOffset
    • 表達完整的時間資訊,例如 LocalDateTime、ZonedDateTime

    在多數的使用情境下,這些資訊大多可以合理地互相轉換
    只要在轉換時,把需要補充的資料給補充進去即可。

    表達時間流上的一個瞬間

    在 JSR-310 中,它明確地把「時間」與「曆法」切割成兩種不同的表達
    也就是使用 Instant 這個類別,用來表示時間流上的某一個瞬間。
    這個瞬間,是以 Unix Time 來表示。

    原則上,一個 Instant 因為是以 Unix Time 來表示,因此它同時代表了年月日時分秒
    但它不包含時區資訊,因為 Unix Time 基本上是表示 UTC+0 的時間。
    因此,Instant 可以轉成多數的其他表達方法,但在部份情況下需要額外提供時區資訊。

    表達部份時間資訊

    在人類使用時間的過程中,其實很多情況是不需要完整的資訊
    比如說表示「數天期間」、「某年某月某日」、「幾點幾分」等等。
    在 JSR-310 中,延續了 Joda-Time 在這方面的設計,有了 Duration、LocalDate 與 LocalTime 的設計。
    Duration 是用來表示一段期間、LocalDate 用來表示一個日期、而 LocalTime 則用來表示一個時間。

    表達時區資訊

    時區的資訊,在 JSR-310 中有兩種表達方式:ZoneId 與 ZoneOffset
    具體來說,兩者都可以用來表達時區,但又有一點細節上的不同。
    ZoneId 是根據 Time-zone ID 的標準去表示某一個時區,並且這個時區是指特定國家代表的時區
    而 ZoneOffset 則是表示一個時區的時差。

    在一般狀況底下,ZoneId 跟 ZoneOffset 是類似的東西,但當目標地區存在日光節約時間時就是例外狀況。
    因為在實施日光節約時間的期間,地區的時差會產生變動
    例如墨西哥平時的時區是 UTC-8 到 UTC-5,但在日光節約時間的期間會變成 UTC-7 到 UTC-5。

    表達完整的時間資訊

    除了上述這些表達部份資訊的類別以外,當然最重要的就是要表達完整的日期與時間
    在 JSR-310 中就是使用 LocalDateTime 與 ZonedDateTime。
    兩者的差別在於,LocalDateTime 表示的是單純的日期與時間,並沒有特地對應到某個時區
    因此如果需要表達特定時區的某個時間瞬間時,必須在 LocalDateTime 上面附加 ZoneId 後,變成 ZonedDateTime。

    格式轉換

    在 JSR-310 中,各個格式可以輕易且容易理解地互相轉換。
    當要取得某個類別時,通常可以用 of() 系列的函式來取得
    例如 ZonedDateTime.ofInstant() 可以從 Instant 加上 ZoneId 資訊後獲得 ZonedDateTime。
    另一種方式是讓某個類別,透過 at() 系列的函式附加資訊後,成為另一個類別
    例如 LocalDate 是單純表示日期,只要用 atTime() 加上時間後,就可以產生 LocalDateTime
    而 LocalDateTime 再用 atZone() 加上 ZoneId 後,就可以產生 ZonedDateTime。

    除此之外,當需要從一段字串解析其中的日期時間時,可以使用 DateTimeFormatter 類別。
    這個類別中也有內建的許多 Pattern,可以處理常見的格式,包括已經頻繁被使用的時間表達格式的標準 RFC 1123 和 ISO 8601。

    相關資源

    上篇:在 Java 中解析、表達與計算日期時間(一):時間系統與曆法

    參考資料
    1. Joda-Time
    2. 【Joda-Time 與 JSR310 】(1)Date 與 Calendar 怎麼了?
    3. 【Joda-Time 與 JSR310 】(4)使用 JDK8 日期時間 API
    4. Why JSR-310 isn’t Joda-Time
    5. Time Zone and Offset Classes

    沒有留言: