參考:http://www.ibm.com/developerworks/cn/java/j-jodatime.html
簡介: 任何企業應用程序都需要處理時間問題。應用程序需要知道當前的時間點和下一個時間點,有時它們還必須計算這兩個時間點之間的路徑。使用 JDK 完成這項任務將非常痛苦和繁瑣。現在來看看 Joda Time,一個面向 Java™ 平台的易於使用的開源時間/日期庫。正如您在本文中了解的那樣,Joda-Time 輕松化解了處理日期和時間的痛苦和繁瑣。
2000 年 1 月 1 日 0 時 0 分
使用 Joda,代碼應該類似如下所示:
DateTime dateTime = new DateTime(2000, 1, 1, 0, 0, 0, 0);
以 Joda 的方式向某一個瞬間加上 90 天並輸出結果
DateTime dateTime = new DateTime(2000, 1, 1, 0, 0, 0, 0);
System.out.println(dateTime.plusDays(90).toString("YYYY/MM/dd HH:mm:ss.SSS"));
輸出:2000/03/31 00:00:00.000
直接將不同對象傳遞給 DateTime
的構造函數
// 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);
應用程序所需處理的日期問題並不全部都與時間上的某個完整時刻有關,因此您可以處理一個局部時刻。例如,有時您比較關心年/月/日,或者一天中的時間,甚至是一周中的某天。Joda 設計者使用 ReadablePartial
接口捕捉這種表示局部時間的概念,這是一個不可變的局部時間片段。用於處理這種時間片段的兩個有用類分別為 LocalDate
和 LocalTime
:
LocalDate
:該類封裝了一個年/月/日的組合。當地理位置(即時區)變得不重要時,使用它存儲日期將非常方便。例如,某個特定對象的出生日期 可能為 1999 年 4 月 16 日,但是從技術角度來看,在保存所有業務值的同時不會了解有關此日期的任何其他信息(比如這是一周中的星期幾,或者這個人出生地所在的時區)。在這種情況下,應當使用LocalDate
。樣例應用程序使用
SystemClock
來獲取被初始化為系統時間的LocalDate
的實例:LocalDate localDate = SystemFactory.getClock().getLocalDate();
也可以通過顯式地提供所含的每個字段的值來創建
LocalDate
:LocalDate localDate = new LocalDate(2009, 9, 6);// September 6, 2009
LocalDate
替代了在早期 Joda 版本中使用的YearMonthDay
。LocalTime
:這個類封裝一天中的某個時間,當地理位置不重要的情況下,可以使用這個類來只存儲一天當中的某個時間。例如,晚上 11:52 可能是一天當中的一個重要時刻(比如,一個 cron 任務將啟動,它將備份文件系統的某個部分),但是這個時間並沒有特定於某一天,因此我不需要了解有關這一時刻的其他信息。樣例應用程序使用
SystemClock
獲取被初始化為系統時間的LocalTime
的一個實例:LocalTime localTime = SystemFactory.getClock().getLocalTime();
也可以通過顯式地提供所含的每個字段的值來創建
LocalTime
:LocalTime localTime = new LocalTime(13, 30, 26, 0);// 1:30:26PM
了解特定的時刻或是某個局部時間片段將非常有用,但是如果能夠表達一段時間跨度的話,通常也很有用。Joda 提供了三個類來簡化這個過程。您可以選擇用於表示不同跨度的類:
Duration
:這個類表示一個絕對的精確跨度,使用毫秒為單位。這個類提供的方法可以用於通過標准的數學轉換(比如 1 分鍾 = 60 秒,1 天 = 24 小時),將時間跨度轉換為標准單位(比如秒、分和小時)。您只在以下情況使用
Duration
的實例:您希望轉換一個時間跨度,但是您並不關心這個時間跨度在何時發生,或者使用毫秒處理時間跨度比較方便。Period
:這個類表示與Duration
相同的概念,但是以人們比較熟悉的單位表示,比如年、月、周。您可以在以下情況使用
Period
:您並不關心這段時期必須在何時發生,或者您更關心檢索單個字段的能力,這些字段描述由Period
封裝的時間跨度。Interval
:這個類表示一個特定的時間跨度,將使用一個明確的時刻界定這段時間跨度的范圍。Interval
為半開 區間,這表示由Interval
封裝的時間跨度包括這段時間的起始時刻,但是不包含結束時刻。可以在以下情況使用
Interval
:需要表示在時間連續區間中以特定的點開始和結束的一段時間跨度。
現在,您已經了解了如何創建一些非常有用的 Joda 類,我將向您展示如何使用它們執行日期計算。接着您將了解到 Joda 如何輕松地與 JDK 進行互操作。
如果您只是需要對日期/時間信息使用占位符,那么 JDK 完全可以勝任,但是它在日期/時間計算方面的表現十分糟糕,而這正是 Joda 的長處。我將向您展示一些簡單的例子。
假設在當前的系統日期下,我希望計算上一個月的最后一天。對於這個例子,我並不關心一天中的時間,因為我只需要獲得年/月/日,如清單 6 所示:
LocalDate now = SystemFactory.getClock().getLocalDate(); LocalDate lastDayOfPreviousMonth =\ now.minusMonths(1).dayOfMonth().withMaximumValue(); |
您可能對清單 6 中的 dayOfMonth()
調用感興趣。這在 Joda 中被稱為屬性(property)。它相當於 Java 對象的屬性。屬性是根據所表示的常見結構命名的,並且它被用於訪問這個結構,用於完成計算目的。屬性是實現 Joda 計算威力的關鍵。您目前所見到的所有 4 個 Joda 類都具有這樣的屬性。一些例子包括:
yearOfCentury
dayOfYear
monthOfYear
dayOfMonth
dayOfWeek
我將詳細介紹清單 6 中的示例,以向您展示整個計算過程。首先,我從當前月份減去一個月,得到 “上一個月”。接着,我要求獲得 dayOfMonth
的最大值,它使我得到這個月的最后一天。注意,這些調用被連接到一起(注意 Joda ReadableInstant
子類是不可變的),這樣您只需要捕捉調用鏈中最后一個方法的結果,從而獲得整個計算的結果。
當計算的中間結果對我不重要時,我經常會使用這種計算模式。(我以相同的方式使用 JDK 的 BigDecimal
)。假設您希望獲得任何一年中的第 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 的注釋幫助您了解代碼如何獲得結果。.setCopy("Monday")
是整個計算的關鍵。不管中間 LocalDate
值是多少,將其 dayOfWeek
屬性設置為 Monday 總是能夠四舍五入,這樣的話,在每月的開始再加上 6 天就能夠讓您得到第一個星期一。再加上一天就得到第一個星期二。Joda 使得執行此類計算變得非常容易。
下面是其他一些因為使用 Joda 而變得超級簡單的計算:
以下代碼計算從現在開始經過兩個星期之后的日期:
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 天,那又如何呢?)
下面是計算從現在起 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 |
這樣的例子實在太多了,我向您已經知道了如何計算。嘗試操作一下樣例應用程序,親自體驗一下使用 Joda 計算任何日期是多么有趣。
我的許多代碼都使用了 JDK Date
和 Calendar
類。但是幸虧有 Joda,我可以執行任何必要的日期算法,然后再轉換回 JDK 類。這將兩者的優點集中到一起。您在本文中看到的所有 Joda 類都可以從 JDK Calendar
或 Date
創建,正如您在 創建 Joda-Time 對象 中看到的那樣。出於同樣的原因,可以從您所見過的任何 Joda 類創建 JDK Calendar
或 Date
。
清單 8 展示了從 Joda ReadableInstant
子類轉換為 JDK 類有多么簡單:
清單 8. 從 Joda DateTime
類創建 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
子類,您還需要經過額外一步,如清單 9 所示:
清單 9. 創建表示 LocalDate
的 Date
對象
LocalDate localDate = SystemFactory.getClock().getLocalDate(); Date date = localDate.toDateMidnight().toDate(); |
要創建 Date
對象,它表示從清單 9 所示的 SystemClock
中獲得的 LocalDate
,您必須首先將它轉換為一個 DateMidnight
對象,然后只需要將 DateMidnight
對象作為 Date
。(當然,產生的 Date
對象將把它自己的時間部分設置為午夜時刻)。
JDK 互操作性被內置到 Joda API 中,因此您無需全部替換自己的接口,如果它們被綁定到 JDK 的話。比如,您可以使用 Joda 完成復雜的部分,然后使用 JDK 處理接口。
使用 JDK 格式化日期以實現打印是完全可以的,但是我始終認為它應該更簡單一些。這是 Joda 設計者進行了改進的另一個特性。要格式化一個 Joda 對象,調用它的 toString()
方法,並且如果您願意的話,傳遞一個標准的 ISO-8601 或一個 JDK 兼容的控制字符串,以告訴 JDK 如何執行格式化。不需要創建單獨的 SimpleDateFormat
對象(但是 Joda 的確為那些喜歡自找麻煩的人提供了一個 DateTimeFormatter
類)。調用 Joda 對象的 toString()
方法,僅此而已。我將展示一些例子。
清單 10 使用了 ISODateTimeFormat
的靜態方法:
DateTime dateTime = SystemFactory.getClock().getDateTime(); dateTime.toString(ISODateTimeFormat.basicDateTime()); dateTime.toString(ISODateTimeFormat.basicDateTimeNoMillis()); dateTime.toString(ISODateTimeFormat.basicOrdinalDateTime()); dateTime.toString(ISODateTimeFormat.basicWeekDateTime()); |
清單 10 中的四個 toString()
調用分別創建了以下內容:
20090906T080000.000-0500 20090906T080000-0500 2009249T080000.000-0500 2009W367T080000.000-0500 |
您也可以傳遞與 SimpleDateFormat
JDK 兼容的格式字符串,如清單 11 所示:
清單 11. 傳遞 SimpleDateFormat
字符串
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 中有關 joda.time.format.DateTimeFormat
的內容,獲得與 JDK SimpleDateFormat
兼容的格式字符串的更多信息,並且可以將其傳遞給 Joda 對象的 toString()
方法。