在編寫企業(yè)應(yīng)用程序時,我常常需要處理日期。并且在我的最新項目中 — 保險行業(yè) — 糾正日期計算尤其重要。使用java.util.Calendar 讓我有些不安。如果您也曾使用這個類處理過日期/時間值,那么您就知道它使用起來有多麻煩。因此當(dāng)我接觸到 Joda-Time — 面向 Java 應(yīng)用程序的日期/時間庫的替代選擇 — 我決定研究一下。其結(jié)果是:我很慶幸我這么做了。
Joda-Time 令時間和日期值變得易于管理、操作和理解。事實(shí)上,易于使用是 Joda 的主要設(shè)計目標(biāo)。其他目標(biāo)包括可擴(kuò)展性、完整的特性集以及對多種日歷系統(tǒng)的支持。并且 Joda 與 JDK 是百分之百可互操作的,因此您無需替換所有 Java 代碼,只需要替換執(zhí)行日期/時間計算的那部分代碼。
Joda 大型項目
Joda 實(shí)際上是涵蓋眾多用于 Java 語言的替代 API 的大型項目,因此從技術(shù)上講,使用 Joda 和 Joda-Time 名稱表示相同的意思是一種誤稱。但在撰寫本文之際,Joda-Time API 目前似乎是唯一處于活躍開發(fā)狀態(tài)下的 Joda API??紤]到 Joda 大型項目的當(dāng)前狀態(tài),我想將 Joda-Time 簡稱為 Joda 應(yīng)該沒什么問題。
本文將介紹并展示如何使用它。我將介紹以下主題:
日期/時間替代庫簡介
Joda 的關(guān)鍵概念
創(chuàng)建 Joda-Time 對象
以 Joda 的方式操作時間 style
以 Joda 的方式格式化時間
您可以
下載 演示這些概念的樣例應(yīng)用程序的源代碼。
為什么要使用 Joda?考慮創(chuàng)建一個用時間表示的某個隨意的時刻 — 比如,2000 年 1 月 1 日 0 時 0 分。我如何創(chuàng)建一個用時間表示這個瞬間的 JDK 對象?使用 java.util.Date?事實(shí)上這是行不通的,因為自 JDK 1.1 之后的每個 Java 版本的 Javadoc 都聲明應(yīng)當(dāng)使用 java.util.Calendar。Date 中不贊成使用的構(gòu)造函數(shù)的數(shù)量嚴(yán)重限制了您創(chuàng)建此類對象的途徑。
然而,Date 確實(shí)有一個構(gòu)造函數(shù),您可以用來創(chuàng)建用時間表示某個瞬間的對象(除 “現(xiàn)在” 以外)。該方法使用距離 1970 年 1 月 1 日子時格林威治標(biāo)準(zhǔn)時間(也稱為 epoch)以來的毫秒數(shù)作為一個參數(shù),對時區(qū)進(jìn)行校正??紤]到 Y2K 對軟件開發(fā)企業(yè)的重要性,您可能會認(rèn)為我已經(jīng)記住了這個值 — 但是我沒有。Date 也不過如此。
那么 Calendar 又如何呢?我將使用下面的方式創(chuàng)建必需的實(shí)例:
Calendar calendar = Calendar.getInstance(); calendar.set(2000, Calendar.JANUARY, 1, 0, 0, 0);
使用 Joda,代碼應(yīng)該類似如下所示:
DateTime dateTime = new DateTime(2000, 1, 1, 0, 0, 0, 0);
這一行簡單代碼沒有太大的區(qū)別。但是現(xiàn)在我將使問題稍微復(fù)雜化。假設(shè)我希望在這個日期上加上 90 天并輸出結(jié)果。使用 JDK,我需要使用清單 1 中的代碼:
Calendar calendar = Calendar.getInstance(); calendar.set(2000, Calendar.JANUARY, 1, 0, 0, 0); SimpleDateFormat sdf = new SimpleDateFormat("E MM/dd/yyyy HH:mm:ss.SSS"); calendar.add(Calendar.DAY_OF_MONTH, 90); System.out.println(sdf.format(calendar.getTime()));
使用 Joda,代碼如清單 2 所示:
DateTime dateTime = new DateTime(2000, 1, 1, 0, 0, 0, 0); System.out.println(dateTime.plusDays(90).toString("E MM/dd/yyyy HH:mm:ss.SSS");
兩者之間的差距拉大了(Joda 用了兩行代碼,JDK 則是 5 行代碼)。
現(xiàn)在假設(shè)我希望輸出這樣一個日期:距離 Y2K 45 天之后的某天在下一個月的當(dāng)前周的最后一天的日期。坦白地說,我甚至不想使用Calendar 處理這個問題。使用 JDK 實(shí)在太痛苦了,即使是簡單的日期計算,比如上面這個計算。正是多年前的這樣一個時刻,我第一次領(lǐng)略到 Joda-Time 的強(qiáng)大。使用 Joda,用于計算的代碼如清單 3 所示:
DateTime dateTime = new DateTime(2000, 1, 1, 0, 0, 0, 0); System.out.println(dateTime.plusDays(45).plusMonths(1).dayOfWeek() .withMaximumValue().toString("E MM/dd/yyyy HH:mm:ss.SSS");
清單 3 的輸出為:
Sun 03/19/2000 00:00:00.000
如果您正在尋找一種易于使用的方式替代 JDK 日期處理,那么您真的應(yīng)該考慮 Joda。如果不是這樣的話,那么繼續(xù)痛苦地使用Calendar 完成所有日期計算吧。當(dāng)您做到這一點(diǎn)后,您完全可以做到使用幾把剪刀修建草坪并使用一把舊牙刷清洗您的汽車。
JDK Calendar 類缺乏可用性,這一點(diǎn)很快就能體會到,而 Joda 彌補(bǔ)了這一不足。Joda 的設(shè)計者還做出了一個決定,我認(rèn)為這是它取得成功的構(gòu)建:JDK 互操作性。Joda 的類能夠生成(但是,正如您將看到的一樣,有時會采用一種比較迂回的方式)java.util.Date 的實(shí)例(和 Calendar)。這使您能夠保留現(xiàn)有的依賴 JDK 的代碼,但是又能夠使用 Joda 處理復(fù)雜的日期/時間計算。
例如,完成
清單 3 中的計算后。我只需要做出如清單 4 所示的更改就可以返回到 JDK 中:
Calendar calendar = Calendar.getInstance(); DateTime dateTime = new DateTime(2000, 1, 1, 0, 0, 0, 0); System.out.println(dateTime.plusDays(45).plusMonths(1).dayOfWeek() .withMaximumValue().toString("E MM/dd/yyyy HH:mm:ss.SSS"); calendar.setTime(dateTime.toDate());
就是這么簡單。我完成了計算,但是可以繼續(xù)在 JDK 對象中處理結(jié)果。這是 Joda 的一個非常棒的特性。
Joda 使用以下概念,它們可以應(yīng)用到任何日期/時間庫:
不可變性(Immutability)
瞬間性(Instant)
局部性(Partial)
年表(Chronology)
時區(qū)(Time zone)
我將針對 Joda 依次討論每一個概念。
我在本文討論的 Joda 類具有不可變性,因此它們的實(shí)例無法被修改。(不可變類的一個優(yōu)點(diǎn)就是它們是線程安全的)。我將向您展示的用于處理日期計算的 API 方法全部返回一個對應(yīng) Joda 類的新實(shí)例,同時保持原始實(shí)例不變。當(dāng)您通過一個 API 方法操作 Joda 類時,您必須捕捉該方法的返回值,因為您正在處理的實(shí)例不能被修改。您可能對這種模式很熟悉;比如,這正是java.lang.String 的各種操作方法的工作方式。
Instant 表示時間上的某個精確的時刻,使用從 epoch 開始計算的毫秒表示。這一定義與 JDK 相同,這就是為什么任何 JodaInstant 子類都可以與 JDK Date 和 Calendar 類兼容的原因。
更通用一點(diǎn)的定義是:一個瞬間 就是指時間線上只出現(xiàn)一次且唯一的一個時間點(diǎn),并且這種日期結(jié)構(gòu)只能以一種有意義的方式出現(xiàn)一次。
一個局部時間,正如我將在本文中將其稱為局部時間片段一樣,它指的是時間的一部分片段。瞬間性指定了與 epoch 相對的時間上的一個精確時刻,與此相反,局部時間片段指的是在時間上可以來回 “移動” 的一個時刻,這樣它便可以應(yīng)用于多個實(shí)例。比如,6 月 2 日 可以應(yīng)用于任意一年的 6 月份(使用 Gregorian 日歷)的第二天的任意瞬間。同樣,11:06 p.m. 可以應(yīng)用于任意一年的任意一天,并且每天只能使用一次。即使它們沒有指定一個時間上的精確時刻,局部時間片段仍然是有用的。
我喜歡將局部時間片段看作一個重復(fù)周期中的一點(diǎn),這樣的話,如果我正在考慮的日期構(gòu)建可以以一種有意義的方式出現(xiàn)多次(即重復(fù)的),那么它就是一個局部時間。
Joda 本質(zhì) — 以及其設(shè)計核心 — 的關(guān)鍵就是年表(它的含義由一個同名抽象類捕捉)。從根本上講,年表是一種日歷系統(tǒng) — 一種計算時間的特殊方式 — 并且是一種在其中執(zhí)行日歷算法的框架。受 Joda 支持的年表的例子包括:
ISO(默認(rèn))
Coptic
Julian
Islamic
Joda-Time 1.6 支持 8 種年表,每一種都可以作為特定日歷系統(tǒng)的計算引擎。
時區(qū)是值一個相對于英國格林威治的地理位置,用于計算時間。要了解事件發(fā)生的精確時間,還必須知道發(fā)生此事件的位置。任何嚴(yán)格的時間計算都必須涉及時區(qū)(或相對于 GMT),除非在同一個時區(qū)內(nèi)發(fā)生了相對時間計算(即時這樣時區(qū)也很重要,如果事件對于位于另一個時區(qū)的各方存在利益關(guān)系的話)。
DateTimeZone 是 Joda 庫用于封裝位置概念的類。許多日期和時間計算都可以在不涉及時區(qū)的情況下完成,但是仍然需要了解DateTimeZone 如何影響 Joda 的操作。默認(rèn)時間,即從運(yùn)行代碼的機(jī)器的系統(tǒng)時鐘檢索到的時間,在大部分情況下被使用。
現(xiàn)在,我將展示在采用該庫時會經(jīng)常遇到的一些 Joda 類,并展示如何創(chuàng)建這些類的實(shí)例。
可變的 Joda 類
我并不是可變實(shí)用類的粉絲;我只是認(rèn)為它們的用例并不適合廣泛使用。但是如果您認(rèn)為您的確需要使用可變 Joda 類的話,本節(jié)的內(nèi)容應(yīng)當(dāng)會對您的項目有幫助。Readable 和 ReadWritable API 之間的唯一區(qū)別在于 ReadWritable 類能夠改變封裝的日期/時間值,因此我在這里將不再介紹這一點(diǎn)。
本節(jié)中介紹的所有實(shí)現(xiàn)都具有若干構(gòu)造函數(shù),允許您初始化封裝的日期/時間。它們可以分為 4 個類別:
使用系統(tǒng)時間。
使用多個字段指定一個瞬間時刻(或局部時間片段),達(dá)到這個特定實(shí)現(xiàn)所能支持的最細(xì)粒度的精確度。
指定一個瞬間時刻(或局部時間片段),以毫秒為單位。
使用另一個對象(例如,java.util.Date,或者是另一個 Joda 對象)。
我將在第一個類中介紹這些構(gòu)造函數(shù): DateTime。當(dāng)您使用其他 Joda 類的相應(yīng)構(gòu)造函數(shù)時,也可以使用這里介紹的內(nèi)容。
重載方法
如果您創(chuàng)建了一個 DateTime 的實(shí)例,并且沒有提供Chronology 或 DateTimeZone,Joda 將使用ISOChronology(默認(rèn))和 DateTimeZone(來自系統(tǒng)設(shè)置)。然而,Joda ReadableInstant 子類的所有構(gòu)造函數(shù)都包含一個超載方法,該方法以一個 Chronology或 DateTimeZone 為參數(shù)。本文附帶的應(yīng)用程序的的樣例代碼展示了如何使用這些超載方法(參見
下載)。我在這里不會再詳細(xì)介紹它們,因為這些方法使用起來非常簡單。然而,我建議您試著使用一下這個樣例應(yīng)用程序,看看編寫您的應(yīng)用程序代碼有多么簡單,這樣您就可以隨意地在 Joda 的 Chronology 和 DateTimeZone 之間切換,同時不會影響到代碼的其余部分。
Joda 通過 ReadableInstant 類實(shí)現(xiàn)了瞬間性這一概念。表示時間上的不可變瞬間的 Joda 類都屬于這個類的子類。(將這個類命名為ReadOnlyInstant 可能更好,我認(rèn)為這才是設(shè)計者需要傳達(dá)的意思)。換句話說,ReadableInstant 表示時間上的某一個不可修改的瞬間)。其中的兩個子類分別為 DateTime 和 DateMidnight:
DateTime:這是最常用的一個類。它以毫秒級的精度封裝時間上的某個瞬間時刻。DateTime 始終與 DateTimeZone 相關(guān),如果您不指定它的話,它將被默認(rèn)設(shè)置為運(yùn)行代碼的機(jī)器所在的時區(qū)。 可以使用多種方式構(gòu)建 DateTime 對象。這個構(gòu)造函數(shù)使用系統(tǒng)時間:
DateTime dateTime = new DateTime();
一般來講,我會盡量避免使用系統(tǒng)時鐘來初始化應(yīng)用程序的實(shí)際,而是傾向于外部化設(shè)置應(yīng)用程序代碼使用的系統(tǒng)時間。樣例應(yīng)用程序執(zhí)行以下代碼: DateTime dateTime = SystemFactory.getClock().getDateTime();
這使得使用不同日期/時間測試我的代碼變得更加簡單:我不需要修改代碼來在應(yīng)用程序中運(yùn)行不同的日期場景,因為時間是在 SystemClock 實(shí)現(xiàn)的內(nèi)部設(shè)置的,而不是在應(yīng)用程序的內(nèi)部。(我可以修改系統(tǒng)時間,但是那實(shí)在太痛苦了?。?下面的代碼使用一些字段值構(gòu)建了一個 DateTime 對象:
DateTime dateTime = new DateTime( 2000, //year 1, // month 1, // day 0, // hour (midnight is zero) 0, // minute 0, // second 0 // milliseconds );
正如您所見,Joda 可以使您精確地控制創(chuàng)建 DateTime 對象的方式,該對象表示時間上的某個特定的瞬間。每一個 Joda 類都有一個與此類似的構(gòu)造函數(shù),您在此構(gòu)造函數(shù)中指定 Joda 類可以包含的所有字段。您可以用它快速了解特定類在哪一種粒度級別上操作。
下一個構(gòu)造函數(shù)將指定從 epoch 到某個時刻所經(jīng)過的毫秒數(shù)。它根據(jù) JDK Date 對象的毫秒值創(chuàng)建一個 DateTime 對象,其時間精度用毫秒表示,因為 epoch 與 Joda 是相同的:
java.util.Date jdkDate = obtainDateSomehow(); long timeInMillis = jdkDate.getTime(); DateTime dateTime = new DateTime(timeInMillis);
并且這個例子與前例類似,唯一不同之處是我在這里將 Date 對象直接傳遞給構(gòu)造函數(shù):
java.util.Date jdkDate = obtainDateSomehow(); dateTime = new DateTime(jdkDate);
Joda 支持使用許多其他對象作為構(gòu)造函數(shù)的參數(shù),用于創(chuàng)建 DateTime,如清單 5 所示:
// Use a Calendar java.util.Calendar calendar = obtainCalendarSomehow(); dateTime = new DateTime(calendar); // Use another Joda DateTime DateTime anotherDateTime = obtainDateTimeSomehow(); dateTime = new DateTime(anotherDateTime); // Use a String (must be formatted properly) String timeString = "2006-01-26T13:30:00-06:00"; dateTime = new DateTime(timeString); timeString = "2006-01-26"; dateTime = new DateTime(timeString);
注意,如果您準(zhǔn)備使用 String(必須經(jīng)過解析),您必須對其進(jìn)行精確地格式化。參考 Javadoc,獲得有關(guān) Joda 的ISODateTimeFormat 類的更多信息(參見
參考資料)。
DateMidnight:這個類封裝某個時區(qū)(通常為默認(rèn)時區(qū))在特定年/月/日的午夜時分的時刻。它基本上類似于 DateTime,不同之處在于時間部分總是為與該對象關(guān)聯(lián)的特定 DateTimeZone 時區(qū)的午夜時分。
您將在本文看到的其他類都遵循與 ReadableInstant 類相同的模式(Joda Javadoc 將顯示這些內(nèi)容),因此為了節(jié)省篇幅,我將不會在以下小節(jié)介紹這些內(nèi)容。
應(yīng)用程序所需處理的日期問題并不全部都與時間上的某個完整時刻有關(guān),因此您可以處理一個局部時刻。例如,有時您比較關(guān)心年/月/日,或者一天中的時間,甚至是一周中的某天。Joda 設(shè)計者使用 ReadablePartial 接口捕捉這種表示局部時間的概念,這是一個不可變的局部時間片段。用于處理這種時間片段的兩個有用類分別為 LocalDate 和 LocalTime:
LocalDate:該類封裝了一個年/月/日的組合。當(dāng)?shù)乩砦恢茫磿r區(qū))變得不重要時,使用它存儲日期將非常方便。例如,某個特定對象的出生日期 可能為 1999 年 4 月 16 日,但是從技術(shù)角度來看,在保存所有業(yè)務(wù)值的同時不會了解有關(guān)此日期的任何其他信息(比如這是一周中的星期幾,或者這個人出生地所在的時區(qū))。在這種情況下,應(yīng)當(dāng)使用 LocalDate。 樣例應(yīng)用程序使用 SystemClock 來獲取被初始化為系統(tǒng)時間的 LocalDate 的實(shí)例:
LocalDate localDate = SystemFactory.getClock().getLocalDate();
也可以通過顯式地提供所含的每個字段的值來創(chuàng)建 LocalDate:
LocalDate localDate = new LocalDate(2009, 9, 6);// September 6, 2009
LocalDate 替代了在早期 Joda 版本中使用的 YearMonthDay。
LocalTime:這個類封裝一天中的某個時間,當(dāng)?shù)乩砦恢貌恢匾那闆r下,可以使用這個類來只存儲一天當(dāng)中的某個時間。例如,晚上 11:52 可能是一天當(dāng)中的一個重要時刻(比如,一個 cron 任務(wù)將啟動,它將備份文件系統(tǒng)的某個部分),但是這個時間并沒有特定于某一天,因此我不需要了解有關(guān)這一時刻的其他信息。 樣例應(yīng)用程序使用 SystemClock 獲取被初始化為系統(tǒng)時間的 LocalTime 的一個實(shí)例:
LocalTime localTime = SystemFactory.getClock().getLocalTime();
也可以通過顯式地提供所含的每個字段的值來創(chuàng)建 LocalTime:
LocalTime localTime = new LocalTime(13, 30, 26, 0);// 1:30:26PM
了解特定的時刻或是某個局部時間片段將非常有用,但是如果能夠表達(dá)一段時間跨度的話,通常也很有用。Joda 提供了三個類來簡化這個過程。您可以選擇用于表示不同跨度的類:
Duration:這個類表示一個絕對的精確跨度,使用毫秒為單位。這個類提供的方法可以用于通過標(biāo)準(zhǔn)的數(shù)學(xué)轉(zhuǎn)換(比如 1 分鐘 = 60 秒,1 天 = 24 小時),將時間跨度轉(zhuǎn)換為標(biāo)準(zhǔn)單位(比如秒、分和小時)。 您只在以下情況使用 Duration 的實(shí)例:您希望轉(zhuǎn)換一個時間跨度,但是您并不關(guān)心這個時間跨度在何時發(fā)生,或者使用毫秒處理時間跨度比較方便。
Period:這個類表示與 Duration 相同的概念,但是以人們比較熟悉的單位表示,比如年、月、周。 您可以在以下情況使用 Period:您并不關(guān)心這段時期必須在何時發(fā)生,或者您更關(guān)心檢索單個字段的能力,這些字段描述由 Period 封裝的時間跨度。
Interval:這個類表示一個特定的時間跨度,將使用一個明確的時刻界定這段時間跨度的范圍。Interval 為半開 區(qū)間,這表示由 Interval 封裝的時間跨度包括這段時間的起始時刻,但是不包含結(jié)束時刻。 可以在以下情況使用 Interval:需要表示在時間連續(xù)區(qū)間中以特定的點(diǎn)開始和結(jié)束的一段時間跨度。
現(xiàn)在,您已經(jīng)了解了如何創(chuàng)建一些非常有用的 Joda 類,我將向您展示如何使用它們執(zhí)行日期計算。接著您將了解到 Joda 如何輕松地與 JDK 進(jìn)行互操作。
如果您只是需要對日期/時間信息使用占位符,那么 JDK 完全可以勝任,但是它在日期/時間計算方面的表現(xiàn)十分糟糕,而這正是 Joda 的長處。我將向您展示一些簡單的例子。
假設(shè)在當(dāng)前的系統(tǒng)日期下,我希望計算上一個月的最后一天。對于這個例子,我并不關(guān)心一天中的時間,因為我只需要獲得年/月/日,如清單 6 所示:
LocalDate now = SystemFactory.getClock().getLocalDate(); LocalDate lastDayOfPreviousMonth = now.minusMonths(1).dayOfMonth().withMaximumValue();
您可能對清單 6 中的 dayOfMonth() 調(diào)用感興趣。這在 Joda 中被稱為屬性(property)。它相當(dāng)于 Java 對象的屬性。屬性是根據(jù)所表示的常見結(jié)構(gòu)命名的,并且它被用于訪問這個結(jié)構(gòu),用于完成計算目的。屬性是實(shí)現(xiàn) Joda 計算威力的關(guān)鍵。您目前所見到的所有 4 個 Joda 類都具有這樣的屬性。一些例子包括:
yearOfCentury
dayOfYear
monthOfYear
dayOfMonth
dayOfWeek
我將詳細(xì)介紹清單 6 中的示例,以向您展示整個計算過程。首先,我從當(dāng)前月份減去一個月,得到 “上一個月”。接著,我要求獲得dayOfMonth 的最大值,它使我得到這個月的最后一天。注意,這些調(diào)用被連接到一起(注意 Joda ReadableInstant 子類是不可變的),這樣您只需要捕捉調(diào)用鏈中最后一個方法的結(jié)果,從而獲得整個計算的結(jié)果。
當(dāng)計算的中間結(jié)果對我不重要時,我經(jīng)常會使用這種計算模式。(我以相同的方式使用 JDK 的 BigDecimal)。假設(shè)您希望獲得任何一年中的第 11 月的第一個星期二的日期,而這天必須是在這個月的第一個星期一之后。清單 7 展示了如何完成這個計算:
LocalDate now = SystemFactory.getClock().getLocalDate(); LocalDate electionDate = now.monthOfYear() .setCopy(11) // November .dayOfMonth() // Access Day Of Month Property .withMinimumValue() // Get its minimum value .plusDays(6) // Add 6 days .dayOfWeek() // Access Day Of Week Property .setCopy("Monday") // Set to Monday (it will round down) .plusDays(1); // Gives us Tuesday
清單 7 的注釋幫助您了解代碼如何獲得結(jié)果。.setCopy("Monday") 是整個計算的關(guān)鍵。不管中間 LocalDate 值是多少,將其dayOfWeek 屬性設(shè)置為 Monday 總是能夠四舍五入,這樣的話,在每月的開始再加上 6 天就能夠讓您得到第一個星期一。再加上一天就得到第一個星期二。Joda 使得執(zhí)行此類計算變得非常容易。
下面是其他一些因為使用 Joda 而變得超級簡單的計算:
以下代碼計算從現(xiàn)在開始經(jīng)過兩個星期之后的日期:
DateTime now = SystemFactory.getClock().getDateTime(); DateTime then = now.plusWeeks(2);
您可以以這種方式計算從明天起 90 天以后的日期:
DateTime now = SystemFactory.getClock().getDateTime(); DateTime tomorrow = now.plusDays(1); DateTime then = tomorrow.plusDays(90);
(是的,我也可以向 now 加 91 天,那又如何呢?)
下面是計算從現(xiàn)在起 156 秒之后的時間:
DateTime now = SystemFactory.getClock().getDateTime(); DateTime then = now.plusSeconds(156);
下面的代碼將計算五年后的第二個月的最后一天:
DateTime now = SystemFactory.getClock().getDateTime(); DateTime then = now.minusYears(5) // five years ago .monthOfYear() // get monthOfYear property .setCopy(2) // set it to February .dayOfMonth() // get dayOfMonth property .withMaximumValue();// the last day of the month
這樣的例子實(shí)在太多了,我向您已經(jīng)知道了如何計算。嘗試操作一下樣例應(yīng)用程序,親自體驗一下使用 Joda 計算任何日期是多么有趣。
我的許多代碼都使用了 JDK Date 和 Calendar 類。但是幸虧有 Joda,我可以執(zhí)行任何必要的日期算法,然后再轉(zhuǎn)換回 JDK 類。這將兩者的優(yōu)點(diǎn)集中到一起。您在本文中看到的所有 Joda 類都可以從 JDK Calendar 或 Date 創(chuàng)建,正如您在
創(chuàng)建 Joda-Time 對象中看到的那樣。出于同樣的原因,可以從您所見過的任何 Joda 類創(chuàng)建 JDK Calendar 或 Date。
清單 8 展示了從 Joda ReadableInstant 子類轉(zhuǎn)換為 JDK 類有多么簡單:
DateTime dateTime = SystemFactory.getClock().getDateTime(); Calendar calendar = dateTime.toCalendar(Locale.getDefault()); Date date = dateTime.toDate(); DateMidnight dateMidnight = SystemFactory.getClock() .getDateMidnight(); date = dateMidnight.toDate();
對于 ReadablePartial 子類,您還需要經(jīng)過額外一步,如清單 9 所示:
LocalDate localDate = SystemFactory.getClock().getLocalDate(); Date date = localDate.toDateMidnight().toDate();
要創(chuàng)建 Date 對象,它表示從清單 9 所示的 SystemClock 中獲得的 LocalDate,您必須首先將它轉(zhuǎn)換為一個 DateMidnight 對象,然后只需要將 DateMidnight 對象作為 Date。(當(dāng)然,產(chǎn)生的 Date 對象將把它自己的時間部分設(shè)置為午夜時刻)。
JDK 互操作性被內(nèi)置到 Joda API 中,因此您無需全部替換自己的接口,如果它們被綁定到 JDK 的話。比如,您可以使用 Joda 完成復(fù)雜的部分,然后使用 JDK 處理接口。
使用 JDK 格式化日期以實(shí)現(xiàn)打印是完全可以的,但是我始終認(rèn)為它應(yīng)該更簡單一些。這是 Joda 設(shè)計者進(jìn)行了改進(jìn)的另一個特性。要格式化一個 Joda 對象,調(diào)用它的 toString() 方法,并且如果您愿意的話,傳遞一個標(biāo)準(zhǔn)的 ISO-8601 或一個 JDK 兼容的控制字符串,以告訴 JDK 如何執(zhí)行格式化。不需要創(chuàng)建單獨(dú)的 SimpleDateFormat 對象(但是 Joda 的確為那些喜歡自找麻煩的人提供了一個 DateTimeFormatter 類)。調(diào)用 Joda 對象的 toString() 方法,僅此而已。我將展示一些例子。
清單 10 使用了 ISODateTimeFormat 的靜態(tài)方法:
DateTime dateTime = SystemFactory.getClock().getDateTime(); dateTime.toString(ISODateTimeFormat.basicDateTime()); dateTime.toString(ISODateTimeFormat.basicDateTimeNoMillis()); dateTime.toString(ISODateTimeFormat.basicOrdinalDateTime()); dateTime.toString(ISODateTimeFormat.basicWeekDateTime());
清單 10 中的四個 toString() 調(diào)用分別創(chuàng)建了以下內(nèi)容:
20090906T080000.000-0500 20090906T080000-0500 2009249T080000.000-0500 2009W367T080000.000-0500
您也可以傳遞與 SimpleDateFormat JDK 兼容的格式字符串,如清單 11 所示:
DateTime dateTime = SystemFactory.getClock().getDateTime(); dateTime.toString("MM/dd/yyyy hh:mm:ss.SSSa"); dateTime.toString("dd-MM-yyyy HH:mm:ss"); dateTime.toString("EEEE dd MMMM, yyyy HH:mm:ssa"); dateTime.toString("MM/dd/yyyy HH:mm ZZZZ"); dateTime.toString("MM/dd/yyyy HH:mm Z"); 09/06/2009 02:30:00.000PM 06-Sep-2009 14:30:00 Sunday 06 September, 2009 14:30:00PM 09/06/2009 14:30 America/Chicago 09/06/2009 14:30 -0500
查看 Javadoc 中有關(guān) joda.time.format.DateTimeFormat 的內(nèi)容,獲得與 JDK SimpleDateFormat 兼容的格式字符串的更多信息,并且可以將其傳遞給 Joda 對象的 toString() 方法。
談到日期處理,Joda 是一種令人驚奇的高效工具。無論您是計算日期、打印日期,或是解析日期,Joda 都將是工具箱中的便捷工具。在本文中,我首先介紹了 Joda,它可以作為 JDK 日期/時間庫的替代選擇。然后介紹了一些 Joda 概念,以及如何使用 Joda 執(zhí)行日期計算和格式化。
Joda-Time 衍生了一些相關(guān)的項目,您可能會發(fā)現(xiàn)這些項目很有用?,F(xiàn)在出現(xiàn)了一個針對 Grails Web 開發(fā)框架的 Joda-Time 插件。joda-time-jpox 項目的目標(biāo)就是添加一些必需的映射,以使用 DataNucleus 持久化引擎持久化 Joda-Time 對象。并且,一個針對 Google Web Toolkit(也稱為 Goda-Time)的 Joda-Time 實(shí)現(xiàn)目前正在開發(fā)當(dāng)中,但是在撰寫本文之際因為許可問題而被暫停。訪問
參考資料 獲得更多信息。
學(xué)習(xí)
Joda:在 SourceForge 中訪問 Joda 項目,并查閱
Javadoc 獲得有關(guān) Joda-Time 庫的內(nèi)容。
Joda-Time Plugin for Grails:了解面向 Grails 的 Joda-Time 插件。
joda-time-jpox:了解有關(guān) joda-time-jpox 項目的更多內(nèi)容。
Goda-Time:關(guān)注針對 Google Web Toolkit 的 Joda-Time 移植項目。
技術(shù)書店:瀏覽有關(guān)這些主題和其他技術(shù)主題的圖書。
developerWorks Java 技術(shù)專區(qū):提供了數(shù)百篇有關(guān) Java 編程各個方面的文章。
獲得產(chǎn)品和技術(shù)
Joda-Time:下載 Joda-Time 庫。
討論
Joda-Time 郵件列表:訂閱 Joda-Time 郵件列表或查看列表歸檔。
加入
My developerWorks 社區(qū)。
J Steven Perry 是一名軟件開發(fā)人員、架構(gòu)師和全能 Java 專家,他從 1991 年起就從事專業(yè)的軟件開發(fā)。他的專業(yè)興趣包括 JVM 的內(nèi)部工作原理和 UML 建模,以及介于兩者之間的所有內(nèi)容。Steve 編寫了從技術(shù)文檔到 Java 代碼等各種內(nèi)容,并且對教學(xué)和培訓(xùn)也充滿了熱情。Steve 是 Java Management Extensions(O’Reilly)的作者,Java Enterprise Best Practices(O’Reilly)的合著者,并撰寫了有關(guān)軟件開發(fā)主題和 O'Reilly ShortCut: Log4J 的雜志文章。