Joda-Time 是由 Stephen Colebourne 於 2002 年開始創建,版本 1.0 於 2005 年釋出,而 2007 年釋出版本 2.0,撰寫此文件的時候,最新版本為 2.3。java
有鑑於 Date 與 Calendar 的問題,Joda-Time 抽取了時間處理時幾個重要觀念,做為實做與使用 API 時的重要考量 …code
連續時間軸上的某個瞬間,採用 UTC 1970 年 1 月 1 日 00:00:00 至該瞬間歷經的毫秒數(與 Unix 時間相同)…orm
在實做上,使用 ReadableInstant 介面來定義 Instant 的行為,其實做類別包括有:blog
Joda-Time 記取了 Date 與 Calendar 實例為可變時可能發生的問題,於是 Instant、DateTime 都設計為不可變。ReadableInstant 介面的實例,行為上可基於毫秒數來進行時間運算,若要取得年、月、日、時、分、秒等欄位資訊,必須提供時區與年曆資訊。例如,若要取得目前時間,並使用預設年曆與時區(稍後說明)來取得月份資訊,如下是個簡單範例:get
DateTime dt = new DateTime(); // 使用預設年曆與時區 int month = dt.getMonthOfYear(); // 取得目前月份,1 就是一月 month = dt.monthOfYear().get(); // 取得目前月份的另外一方式 String monthDesc = dt.monthOfYear().getAsText(); // 取得月份的文字描述
人類在平常生活上使用時間,一般不須要完整的時間概念,只須要片斷的時間資訊。例如,我們會說某人的生日是「5 月 26 日」,現在的時間是「下午 1 點 6 分」… 實際上,「5 月 26 日」這樣的片斷時間資訊,能夠指任何一年的「5 月 26 日」,而「下午 1 點 6 分」這樣的片斷時間資訊,也能夠用於任何一天的「下午 1 點 6 分」…io
片斷時間資訊的概念,在 Joda-Time 中定義為 Partial,實做上由 ReadablePartial 定義出 Partial 的行為,其實做包括有如下類別,實例皆為不可變,從名稱上應可一目瞭然各自的做用:table
既然是片斷的時間資訊,那麼只要補齊不全的部份,就能夠轉為時間軸上確切的某個瞬間…form
具體來說,也就是 ReadablePartial 的實例能夠組合以產生 ReadableInstant 的實例。例如:class
LocalDate date = new LocalDate(2004, 12, 25); // 年、月、日的日期片斷資訊 LocalTime time = new LocalTime(12, 20); // 時、分的時間片斷資訊 DateTime dt = date.toDateTime(time); // 使用預設時區
反過來說,若是你有個 ReadableInstant 實例,像是 DateTime,也能夠取得其中的時間片斷。例如:date
DateTime dt = new DateTime(); LocalDate date = new LocalDate(dt); // 只取出日期片斷資訊
有時你會想要表達時間上某個區段,像是西元 1975 年 5 月 26 日到 西元 1978 年 7 月 23 日,像這類包括開始瞬間與結束瞬間的區段,Joda-Time 定義為 Interval …
實做上由 ReadableInterval 介面定義行為,實做類別有:
創建 Interval 實例的程式片斷之一以下:
DateTime start = new DateTime(1975, 5, 26, 0, 0, 0); DateTime end = new DateTime(1978, 7, 23, 0, 0, 0); Interval interval = new Interval(start, end);
現在的時間「再 1000 毫秒」後會是多?這類再持續多久的期間概念,就是 Joda-Time 中定義的 Interval,不過持續的時間單位是毫秒。實做上使用 ReadableDuration 介面定義,實做類別有 Duration。
天然地,若是在某個瞬間加上 Duration,就會獲得另外一個瞬間:
具體來說,能夠給予 ReadableInstant 的實例某個 ReadableDuration 實例,從而獲得另外一個 ReadableInstant,例如 …
DateTime start = new DateTime(1975, 5, 26, 0, 0, 0); Duration oneThousandMillis = new Duration(1000); DateTime end = start.plus(oneThousandMillis);
使用毫秒?那要表示「再三分鐘」、「再兩天」的概念不是很麻煩嗎?是的!沒有錯!人類一般不多使用毫秒,所以,Joda-Time 把人類慣用的時間持續定義為 Period,實做上使用 ReadablePeriod 介面定義行為,實做的類別有如下幾個:
天然地,若是在某個瞬間加上 Period,也會獲得另外一個瞬間:
只不過這次是人類經常使用的時間概念,例如,想知道兩個時間軸上的瞬間究竟是多少天嗎?能夠這麼撰寫程式來得知:
DateTime start = new DateTime(1975, 5, 26, 0, 0, 0); DateTime end = new DateTime(1978, 7, 23, 0, 0, 0); Days days = Days.daysBetween(start, end);
以上是有關於使用 Joda-Time 時,應該知道的幾個重要觀念,其中我們看到了年曆與時區 …
在 Joda-Time 的 API 設計上,年曆系統是能夠抽換的,用以從某個瞬間計算日期時間的各個欄位,Chronology 是年曆實做類別的抽象父類別,許多情況下若沒有指定,會使用 ISOChronology 做為預設,這是依 ISO8601 的實做類別。
在 【Joda-Time 與 JSR310 】(2)時間的 ABC 中談過,JDK 中 Calendar 的實做類別 GregorianCalendar,其實是個儒略曆與格里高利曆的混合年曆,在 Joda-Time 中對應的實做類別是 GJChronology,若是你要純綷的儒略曆,在 Joda-Time 中是 JulianChronology,若是你要純綷的格里高利曆,在 Joda-Time 中是 GregorianChronology。除此以外,Joda-Time 中還有…
DateTimeZone zone = DateTimeZone.forID("Europe/London"); Chronology coptic = CopticChronology.getInstance(zone); DateTime dt = new DateTime(coptic); int year = dt.getYear(); int month = dt.getMonthOfYear();
Joda-Time 會將時區資料編譯包裝為單一 JAR 檔案,你能夠按照 Time zone update 資訊,隨時更新與從新編譯 JAR 檔案,Available Time Zones 中則列出了 Joda-Time 中全部的時區資訊。
談了這麼多觀念,該來端出些牛肉,看看使用 Joda-Time 與使用 JDK 的日期時間 API,到底有什麼不一樣,先來看看原先 Date 與 Calendar 怎麼了? 中有問題的這個片斷:
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); Date birthDate = dateFormat.parse(args[0]); Date nowDate = new Date(); long lifeMillis = nowDate.getTime() - birthDate.getTime(); System.out.printf("你今年的歲數為:%d%n", lifeMillis / (365 * 24 * 60 * 60 * 1000));
不但囉嗦並且出錯了,改用 Joda-Time 之後是這樣的:
Years years = Years.yearsBetween( DateTime.parse("1975-05-26"), DateTime.now()); System.out.printf("你今年的歲數為:%d%n", years.getYears());
接下來這個片斷要表示的日期不正確:
Date date = new Date(13, 8, 2); DateFormat dateFormat = DateFormat.getDateInstance(); System.out.printf("Taiwan Java Developer Day is %s.%n", dateFormat.format(date));
這個也不對:
Calendar calendar = Calendar.getInstance(); calendar.set(2013, 8, 2); DateFormat dateFormat = DateFormat.getDateInstance(); System.out.printf("Taiwan Java Developer Day is %s.%n", dateFormat.format(calendar.getTime()));
其實,這兩個程式碼想要的結果,並不須要完整的時間概念,它沒有要時、分、秒的資訊,於是只須要使用 Joda-Time 中的 Partial 概念,也就是 ReadablePartial 的實例之一 LocalDate:
LocalDate javaTwoDate = new LocalDate(2013, 8, 2); System.out.printf("Taiwan Java Developer Day is %s.%n", javaTwoDate);
那原先這個方法呢?
public static long daysBetween(Calendar begin, Calendar end) { long daysBetween = 0; while(begin.before(end)) { begin.add(Calendar.DAY_OF_MONTH, 1); daysBetween++; } return daysBetween; }
其實你須要的只是 Joda-Time 中 Days.daysBetween 之類的方法:
再來看個需求好了,若是要知道某個日期起加上 5 天、6 個月、3 週後會的日期時間是什麼,並使用指定的格式輸出。使用 JDK 的話,會須要以下的計算:
Calendar calendar = Calendar.getInstance(); calendar.set(1975, Calendar.MAY, 26, 0, 0, 0); calendar.add(Calendar.DAY_OF_MONTH, 5); calendar.add(Calendar.MONTH, 6); calendar.add(Calendar.WEEK_OF_MONTH, 3); SimpleDateFormat df = new SimpleDateFormat("E MM/dd/yyyy"); System.out.println(df.format(calendar.getTime()));
Joda-Time 實現了 流暢 API 的概念,寫來會輕鬆且流暢易讀:
LocalDate birthDate = new LocalDate(1975, 5, 26); System.out.println(birthDate .plusDays(5) .plusMonths(6) .plusWeeks(3).toString("E MM/dd/yyyy"));
那麼,既然 Joda-Time 這麼好,為什麼還要有 JSR310,幹嘛不把 Joda-Time 納入 JDK 就行了呢?JSR310 的設計上又有什麼不一樣呢?這會是下篇文章要探討的主題!