簡介
在Java中處理日期和時間是很常見的需求,基礎的工具類就是我們熟悉的Date和Calendar,然而這些工具類的api使用並不是很方便和強大,於是就誕生了Joda-Time這個專門處理日期時間的庫。
由於Joda-Time很優秀,在Java 8出現前的很長時間內成為Java中日期時間處理的事實標准,用來彌補JDK的不足。在Java 8中引入的java.time
包是一組新的處理日期時間的API,遵守JSR 310
。值得一提的是,Joda-Time的作者Stephen Colebourne
和Oracle一起共同參與了這些API的設計和實現。
值得注意的是,Java 8中的java.time
包中提供的API和Joda-Time並不完全相同。比如,在Joda-Time中常用的Interval
(用來表示一對DateTime),在JSR 310
中並不支持。因此,另一個名叫Threeten的第三方庫用來彌補Java 8的不足。Threeten翻譯成中文就是310的意思,表示這個庫與JSR 310
有關。它的作者同樣是Joda-Time的作者Stephen Colebourne
。
Threeten主要提供兩種發行包:ThreeTen-Backport和ThreeTen-Extra。前者的目的在於對Java 6和Java 7的項目提供Java 8的date-time類的支持;后者的目的在於為Java 8的date-time類提供額外的增強功能(比如:Interval
等)。
由於剛接觸Joda-Time,並且目前的工作環境還未涉及到Java 8。因此,關於Java 8的date-time和Threeten的API,將在以后合適的時候介紹。這篇文章關注Joda-Time的使用。
總之,作為一種解決某一問題領域的工具庫,我認為有以下幾個方面值得關注:
- 功能是否全面,以能夠滿足生產需要,並用它解決這個問題領域中的絕大多數的問題
- 是否是主流工具。用的人越多,意味着該庫經受了更多生產實踐的驗證,效率安全等方面都已被證明是可靠的
- 自己是否已經熟練掌握。會的多不如會的精,如果能夠用一個工具快速熟練可靠地解決問題,在時間成本有限的情況下,就不用刻意追求學習其它可替代的庫
引入MAVEN依賴
<dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>2.9.2</version> </dependency>
核心類介紹
下面介紹5個最常用的date-time類:
- Instant - 不可變的類,用來表示時間軸上一個瞬時的點
- DateTime - 不可變的類,用來替換JDK的Calendar類
- LocalDate - 不可變的類,表示一個本地的日期,而不包含時間部分(沒有時區信息)
- LocalTime - 不可變的類,表示一個本地的時間,而不包含日期部分(沒有時區信息)
- LocalDateTime - 不可變的類,表示一個本地的日期-時間(沒有時區信息)
注意:不可變的類,表明了正如Java的String類型一樣,其對象是不可變的。即,不論對它進行怎樣的改變操作,返回的對象都是新對象。
Instant比較適合用來表示一個事件發生的時間戳。不用去關心它使用的日歷系統或者是所在的時區。
DateTime的主要目的是替換JDK中的Calendar類,用來處理那些時區信息比較重要的場景。
LocalDate比較適合表示出生日期這樣的類型,因為不關心這一天中的時間部分。
LocalTime適合表示一個商店的每天開門/關門時間,因為不用關心日期部分。
DateTime類
作為Joda-Time很重要的一個類,詳細地看一下它的用法。
構造一個DateTime實例
如果查看Java Doc,會發現DateTime有很多構造方法。這是為了使用者能夠很方便的由各種表示日期時間的對象構造出DateTime實例。下面介紹一些常用的構造方法:
- DateTime():這個無參的構造方法會創建一個在當前系統所在時區的當前時間,精確到毫秒
- DateTime(int year, int monthOfYear, int dayOfMonth, int hourOfDay, int minuteOfHour, int secondOfMinute):這個構造方法方便快速地構造一個指定的時間,這里精確到秒,類似地其它構造方法也可以傳入毫秒。
- DateTime(long instant):這個構造方法創建出來的實例,是通過一個long類型的時間戳,它表示這個時間戳距
1970-01-01T00:00:00Z
的毫秒數。使用默認的時區。 - DateTime(Object instant):這個構造方法可以通過一個Object對象構造一個實例。這個Object對象可以是這些類型:ReadableInstant, String, Calendar和Date。其中String的格式需要是
ISO8601
格式,詳見:ISODateTimeFormat.dateTimeParser()
下面舉幾個例子:
DateTime dateTime1 = new DateTime(); System.out.println(dateTime1); // out: 2016-02-26T16:02:57.582+08:00 DateTime dateTime2 = new DateTime(2016,2,14,0,0,0); System.out.println(dateTime2); // out: 2016-02-14T00:00:00.000+08:00 DateTime dateTime3 = new DateTime(1456473917004L); System.out.println(dateTime3); // out: 2016-02-26T16:05:17.004+08:00 DateTime dateTime4 = new DateTime(new Date()); System.out.println(dateTime4); // out: 2016-02-26T16:07:59.970+08:00 DateTime dateTime5 = new DateTime("2016-02-15T00:00:00.000+08:00"); System.out.println(dateTime5); // out: 2016-02-15T00:00:00.000+08:00
訪問DateTime實例
當你有一個DateTime實例的時候,就可以調用它的各種方法,獲取需要的信息。
- with開頭的方法(比如:withYear):用來設置DateTime實例到某個時間,因為DateTime是不可變對象,所以沒有提供setter方法可供使用,with方法也沒有改變原有的對象,而是返回了設置后的一個副本對象。下面這個例子,將2000-02-29的年份設置為1997。值得注意的是,因為1997年沒有2月29日,所以自動轉為了28日。
DateTime dateTime2000Year = new DateTime(2000,2,29,0,0,0); System.out.println(dateTime2000Year); // out: 2000-02-29T00:00:00.000+08:00 DateTime dateTime1997Year = dateTime2000Year.withYear(1997); System.out.println(dateTime1997Year); // out: 1997-02-28T00:00:00.000+08:00
- plus/minus開頭的方法(比如:plusDay, minusMonths):用來返回在DateTime實例上增加或減少一段時間后的實例。下面的例子:在當前的時刻加1天,得到了明天這個時刻的時間;在當前的時刻減1個月,得到了上個月這個時刻的時間。
DateTime now = new DateTime(); System.out.println(now); // out: 2016-02-26T16:27:58.818+08:00 DateTime tomorrow = now.plusDays(1); System.out.println(tomorrow); // out: 2016-02-27T16:27:58.818+08:00 DateTime lastMonth = now.minusMonths(1); System.out.println(lastMonth); // out: 2016-01-26T16:27:58.818+08:00
注意,在增減時間的時候,想象成自己在翻日歷,所有的計算都將符合歷法,由Joda-Time自動完成,不會出現非法的日期(比如:3月31日加一個月后,並不會出現4月31日)。
- 返回Property的方法:Property是DateTime中的屬性,保存了一些有用的信息。Property對象中的一些方法在這里一並介紹。下面的例子展示了,我們可以通過不同Property中get開頭的方法獲取一些有用的信息:
DateTime now = new DateTime(); // 2016-02-26T16:51:28.749+08:00 now.monthOfYear().getAsText(); // February now.monthOfYear().getAsText(Locale.KOREAN); // 2월 now.dayOfWeek().getAsShortText(); // Fri now.dayOfWeek().getAsShortText(Locale.CHINESE); // 星期五
有時我們需要對一個DateTime的某些屬性進行置0操作。比如,我想得到當天的0點時刻。那么就需要用到Property中round開頭的方法(roundFloorCopy)。如下面的例子所示:
DateTime now = new DateTime(); // 2016-02-26T16:51:28.749+08:00 now.dayOfWeek().roundCeilingCopy(); // 2016-02-27T00:00:00.000+08:00 now.dayOfWeek().roundFloorCopy(); // 2016-02-26T00:00:00.000+08:00 now.minuteOfDay().roundFloorCopy(); // 2016-02-26T16:51:00.000+08:00 now.secondOfMinute().roundFloorCopy(); // 2016-02-26T16:51:28.000+08:00
- 其它:還有許多其它方法(比如dateTime.year().isLeap()來判斷是不是閏年)。它們的詳細含義,請參照Java Doc,現查現用,用需求驅動學習。
日歷系統和時區
Joda-Time默認使用的是ISO的日歷系統,而ISO的日歷系統是世界上公歷的事實標准。然而,值得注意的是,ISO日歷系統在表示1583年之前的歷史時間是不精確的。
Joda-Time默認使用的是JDK的時區設置。如果需要的話,這個默認值是可以被覆蓋的。
Joda-Time使用可插拔的機制來設計日歷系統,而JDK則是使用子類的設計,比如GregorianCalendar。下面的代碼,通過調用一個工廠方法獲得Chronology的實現:
Chronology coptic = CopticChronology.getInstance();
時區是作為chronology的一部分來被實現的。下面的代碼獲得一個Joda-Time chronology在東京的時區:
DateTimeZone zone = DateTimeZone.forID("Asia/Tokyo");
Chronology gregorianJuian = GJChronology.getInstance(zone);
Interval和Period
Joda-Time為時間段的表示提供了支持。
- Interval:它保存了一個開始時刻和一個結束時刻,因此能夠表示一段時間,並進行這段時間的相應操作
- Period:它保存了一段時間,比如:6個月,3天,7小時這樣的概念。可以直接創建Period,或者從Interval對象構建。
- Duration:它保存了一個精確的毫秒數。同樣地,可以直接創建Duration,也可以從Interval對象構建。
雖然,這三個類都用來表示時間段,但是在用途上來說還是有一些差別。請看下面的例子:
DateTime dt = new DateTime(2005, 3, 26, 12, 0, 0, 0); DateTime plusPeriod = dt.plus(Period.days(1)); DateTime plusDuration = dt.plus(new Duration(24L*60L*60L*1000L));
因為當時那個地區執行夏令時的原因,在添加一個Period的時候會添加23個小時。而添加一個Duration,則會精確地添加24個小時,而不考慮歷法。所以,Period和Duration的差別不但體現在精度上,也同樣體現在語義上。因為,有時候按照有些地區的歷法 1天 不等於 24小時。
結語
這篇文章參考了Joda-Time的官方文檔:Quick Start,並加上了自己的理解。
涉及到更多的需求和用法(比如“日期時間的格式化”等),可以參考官方文檔:User Guide。
作者:JohnShen
鏈接:https://www.jianshu.com/p/efdeda608780
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。