Joda-Time是一個使用java開發的強大易用的日期和時間庫。易於使用是 Joda 的主要設計目標。其餘目標包括可擴展性、完整的特性集以及對多種日曆系統的支持。而且 Joda 與 JDK 是百分之百可互操做的,所以您無需替換全部 Java 代碼,只須要替換執行日期/時間計算的那部分代碼。html
先給你們看幾個例子,感覺一下joda的易用和強大:java
public static void main(String[] args) throws ParseException { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String stime = "2016-10-29 11:53:50"; String etime = "2016-10-29 12:48:34"; //1.計算兩個日期相差的秒數,輸出:3284 System.out.println(Seconds.secondsBetween(new DateTime(sdf.parse(stime)), new DateTime(sdf.parse(etime))).getSeconds()); //2.計算兩個日期相差的分鐘數,輸出:54 System.out.println(Minutes.minutesBetween(new DateTime(sdf.parse(stime)), new DateTime(sdf.parse(etime))).getMinutes()); //3.計算兩個日期相差時間,輸出:0D 0H 54M 44S Interval interval = new Interval(sdf.parse(stime).getTime(), sdf.parse(etime).getTime()); Period p = interval.toPeriod(); System.out.println(p.getDays()+"D " + p.getHours()+ "H "+p.getMinutes()+"M "+p.getSeconds()+"S"); //4.計算90天后的時間,輸出:2017-01-27 11:53:50 DateTime dateTime = new DateTime(sdf.parse(stime)); System.out.println(sdf.format(dateTime.plusDays(90).toDate())); //5.時間格式化,輸出:2016/10/29 11:53:50 星期六 System.out.println(DateTime.parse(stime, DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss")).toString("yyyy/MM/dd HH:mm:ss EE")); }
幾乎都是一行代碼便可準確無誤的輸出想要的結果。算法
下面詳細介紹一下Joda-Time。安全
jar包下載地址(直接引入項目便可):框架
http://central.maven.org/maven2/joda-time/joda-time/2.9.4/joda-time-2.9.4.jarmaven
maven倉庫:函數
<dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>2.9.4</version> </dependency>
Joda 使用如下概念,它們能夠應用到任何日期/時間庫:工具
我將針對 Joda 依次討論每個概念。測試
我在本文討論的 Joda 類具備不可變性,所以它們的實例沒法被修改。(不可變類的一個優勢就是它們是線程安全的)。我將向您展現的用於處理日期計算的 API 方法所有返回一個對應 Joda 類的新實例,同時保持原始實例不變。當您經過一個 API 方法操做 Joda 類時,您必須捕捉該方法的返回值,由於您正在處理的實例不能被修改。您可能對這種模式很熟悉;好比,這正是 java.lang.String
的各類操做方法的工做方式。spa
Instant
表示時間上的某個精確的時刻,使用從 epoch 開始計算的毫秒錶示。這必定義與 JDK 相同,這就是爲何任何 Joda Instant
子類均可以與 JDK Date
和 Calendar
類兼容的緣由。
更通用一點的定義是:一個瞬間 就是指時間線上只出現一次且惟一的一個時間點,而且這種日期結構只能以一種有意義的方式出現一次。
一個局部時間,正如我將在本文中將其稱爲局部時間片斷同樣,它指的是時間的一部分片斷。瞬間性指定了與 epoch 相對的時間上的一個精確時刻,與此相反,局部時間片斷指的是在時間上能夠來回 「移動」 的一個時刻,這樣它即可以應用於多個實例。好比,6 月 2 日 能夠應用於任意一年的 6 月份(使用 Gregorian 日曆)的次日的任意瞬間。一樣,11:06 p.m. 能夠應用於任意一年的任意一天,而且天天只能使用一次。即便它們沒有指定一個時間上的精確時刻,局部時間片斷仍然是有用的。
我喜歡將局部時間片斷看做一個重複週期中的一點,這樣的話,若是我正在考慮的日期構建能夠以一種有意義的方式出現屢次(即重複的),那麼它就是一個局部時間。
Joda 本質 — 以及其設計核心 — 的關鍵就是年表(它的含義由一個同名抽象類捕捉)。從根本上講,年表是一種日曆系統 — 一種計算時間的特殊方式 — 而且是一種在其中執行日曆算法的框架。受 Joda 支持的年表的例子包括:
Joda-Time 1.6 支持 8 種年表,每一種均可以做爲特定日曆系統的計算引擎。
時區是值一個相對於英國格林威治的地理位置,用於計算時間。要了解事件發生的精確時間,還必須知道發生此事件的位置。任何嚴格的時間計算都必須涉及時區(或相對於 GMT),除非在同一個時區內發生了相對時間計算(即時這樣時區也很重要,若是事件對於位於另外一個時區的各方存在利益關係的話)。
DateTimeZone
是 Joda 庫用於封裝位置概念的類。許多日期和時間計算均可以在不涉及時區的狀況下完成,可是仍然須要瞭解 DateTimeZone
如何影響 Joda 的操做。默認時間,即從運行代碼的機器的系統時鐘檢索到的時間,在大部分狀況下被使用。
如今,我將展現在採用該庫時會常常遇到的一些 Joda 類,並展現如何建立這些類的實例。
我並非可變實用類的粉絲;我只是認爲它們的用例並不適合普遍使用。可是若是您認爲您的確須要使用可變 Joda 類的話,本節的內容應當會對您的項目有幫助。Readable
和 ReadWritable
API 之間的惟一區別在於 ReadWritable
類可以改變封裝的日期/時間值,所以我在這裏將再也不介紹這一點。
本節中介紹的全部實現都具備若干構造函數,容許您初始化封裝的日期/時間。它們能夠分爲 4 個類別:
java.util.Date
,或者是另外一個 Joda 對象)。我將在第一個類中介紹這些構造函數: DateTime
。當您使用其餘 Joda 類的相應構造函數時,也可使用這裏介紹的內容。
若是您建立了一個 DateTime
的實例,而且沒有提供 Chronology
或 DateTimeZone
,Joda 將使用 ISOChronology
(默認)和 DateTimeZone
(來自系統設置)。然而,Joda ReadableInstant
子類的全部構造函數都包含一個超載方法,該方法以一個 Chronology
或 DateTimeZone
爲參數。本文附帶的應用程序的的樣例代碼展現瞭如何使用這些超載方法。我在這裏不會再詳細介紹它們,由於這些方法使用起來很是簡單。然而,我建議您試着使用一下這個樣例應用程序,看看編寫您的應用程序代碼有多麼簡單,這樣您就能夠隨意地在 Joda 的 Chronology
和 DateTimeZone
之間切換,同時不會影響到代碼的其他部分。
ReadableInstant
Joda 經過 ReadableInstant
類實現了瞬間性這一律念。表示時間上的不可變瞬間的 Joda 類都屬於這個類的子類。(將這個類命名爲 ReadOnlyInstant
可能更好,我認爲這纔是設計者須要傳達的意思)。換句話說,ReadableInstant
表示時間上的某一個不可修改的瞬間)。其中的兩個子類分別爲 DateTime
和 DateMidnight
:
DateTime
:這是最經常使用的一個類。它以毫秒級的精度封裝時間上的某個瞬間時刻。DateTime
始終與 DateTimeZone
相關,若是您不指定它的話,它將被默認設置爲運行代碼的機器所在的時區。 可使用多種方式構建 DateTime
對象。這個構造函數使用系統時間:
DateTime dateTime = new DateTime();
DateTime dateTime = SystemFactory.getClock().getDateTime();
SystemClock
實現的內部設置的,而不是在應用程序的內部。(我能夠修改系統時間,可是那實在太痛苦了!) 下面的代碼使用一些字段值構建了一個 DateTime
對象:
DateTime dateTime = new DateTime( 2000, //year 1, // month 1, // day 0, // hour (midnight is zero) 0, // minute 0, // second 0 // milliseconds );
正如您所見,Joda 可使您精確地控制建立 DateTime
對象的方式,該對象表示時間上的某個特定的瞬間。每個 Joda 類都有一個與此相似的構造函數,您在此構造函數中指定 Joda 類能夠包含的全部字段。您能夠用它快速瞭解特定類在哪種粒度級別上操做。
下一個構造函數將指定從 epoch 到某個時刻所通過的毫秒數。它根據 JDK Date
對象的毫秒值建立一個 DateTime
對象,其時間精度用毫秒錶示,由於 epoch 與 Joda 是相同的:
java.util.Date jdkDate = obtainDateSomehow(); long timeInMillis = jdkDate.getTime(); DateTime dateTime = new DateTime(timeInMillis);
而且這個例子與前例相似,惟一不一樣之處是我在這裏將 Date
對象直接傳遞給構造函數:
java.util.Date jdkDate = obtainDateSomehow(); dateTime = new DateTime(jdkDate);
Joda 支持使用許多其餘對象做爲構造函數的參數,用於建立 DateTime
,如清單 5 所示:
清單 1. 直接將不一樣對象傳遞給 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);
注意,若是您準備使用 String
(必須通過解析),您必須對其進行精確地格式化。參考 Javadoc,得到有關 Joda 的 ISODateTimeFormat
類的更多信息。
DateMidnight
:這個類封裝某個時區(一般爲默認時區)在特定年/月/日的午夜時分的時刻。它基本上相似於 DateTime
,不一樣之處在於時間部分老是爲與該對象關聯的特定 DateTimeZone
時區的午夜時分。您將在本文看到的其餘類都遵循與 ReadableInstant
類相同的模式(Joda Javadoc 將顯示這些內容),所以爲了節省篇幅,我將不會在如下小節介紹這些內容。
ReadablePartial
應用程序所需處理的日期問題並不所有都與時間上的某個完整時刻有關,所以您能夠處理一個局部時刻。例如,有時您比較關心年/月/日,或者一天中的時間,甚至是一週中的某天。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 的長處。我將向您展現一些簡單的例子。
假設在當前的系統日期下,我但願計算上一個月的最後一天。對於這個例子,我並不關心一天中的時間,由於我只須要得到年/月/日,如清單 2 所示:
清單 2. 使用 Joda 計算日期
LocalDate now = SystemFactory.getClock().getLocalDate(); LocalDate lastDayOfPreviousMonth =\ now.minusMonths(1).dayOfMonth().withMaximumValue();
您可能對清單 2 中的 dayOfMonth()
調用感興趣。這在 Joda 中被稱爲屬性(property)。它至關於 Java 對象的屬性。屬性是根據所表示的常見結構命名的,而且它被用於訪問這個結構,用於完成計算目的。屬性是實現 Joda 計算威力的關鍵。您目前所見到的全部 4 個 Joda 類都具備這樣的屬性。一些例子包括:
yearOfCentury
dayOfYear
monthOfYear
dayOfMonth
dayOfWeek
我將詳細介紹清單 2 中的示例,以向您展現整個計算過程。首先,我從當前月份減去一個月,獲得 「上一個月」。接着,我要求得到 dayOfMonth
的最大值,它使我獲得這個月的最後一天。注意,這些調用被鏈接到一塊兒(注意 Joda ReadableInstant
子類是不可變的),這樣您只須要捕捉調用鏈中最後一個方法的結果,從而得到整個計算的結果。
當計算的中間結果對我不重要時,我常常會使用這種計算模式。(我以相同的方式使用 JDK 的 BigDecimal
)。假設您但願得到任何一年中的第 11 月的第一個星期二的日期,而這天必須是在這個月的第一個星期一以後。清單 3 展現瞭如何完成這個計算:
清單 3. 計算 11 月中第一個星期一以後的第一個星期二
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
清單 3 的註釋幫助您瞭解代碼如何得到結果。.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
。
清單 4 展現了從 Joda ReadableInstant
子類轉換爲 JDK 類有多麼簡單:
清單 4. 從 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
子類,您還須要通過額外一步,如清單 5 所示:
清單 5. 建立表示 LocalDate
的 Date
對象
LocalDate localDate = SystemFactory.getClock().getLocalDate(); Date date = localDate.toDateMidnight().toDate();
要建立 Date
對象,它表示從清單 5 所示的 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()
方法,僅此而已。我將展現一些例子。
清單 6 使用了 ISODateTimeFormat
的靜態方法:
清單 6. 使用 ISO-8601
DateTime dateTime = SystemFactory.getClock().getDateTime(); dateTime.toString(ISODateTimeFormat.basicDateTime()); dateTime.toString(ISODateTimeFormat.basicDateTimeNoMillis()); dateTime.toString(ISODateTimeFormat.basicOrdinalDateTime()); dateTime.toString(ISODateTimeFormat.basicWeekDateTime());
清單 6 中的四個 toString()
調用分別建立了如下內容:
20090906T080000.000-0500 20090906T080000-0500 2009249T080000.000-0500 2009W367T080000.000-0500
您也能夠傳遞與 SimpleDateFormat
JDK 兼容的格式字符串,如清單 11 所示:
清單 7. 傳遞 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()
方法。
談到日期處理,Joda 是一種使人驚奇的高效工具。不管您是計算日期、打印日期,或是解析日期,Joda 都將是工具箱中的便捷工具。在本文中,我首先介紹了 Joda,它能夠做爲 JDK 日期/時間庫的替代選擇。而後介紹了一些 Joda 概念,以及如何使用 Joda 執行日期計算和格式化。