基於java的項目,最大的一個好處是有很多開源,優秀的第三方jar包拿過來直接使用,但是引用第三方包時一定要小心審核,確認包的作者或組織的權威性,以免未知的第三方包對項目的性能,安全和正確性的影響。作為一個java coder,有些包你不得不了解下,它們真的可以幫你在項目中節省很多時間去寫自己的utils包,況且成熟的社區維護的第三方工具包比自己動手寫的專業多了。所以我想寫個系列性的文章,介紹下平時在用,而且值得推薦給大家的一些好的第三方jar包。
時間,日期的處理在應用項目中是經常要用到的一塊,如果你還是個稍微追求感覺的程序員,應該早就受不了jdk中java.util.Date這個類,莫名奇妙的構造方法,再加上一堆的deprecated方法,讓你覺得為何不把這個類給取消算了。舉個例子,現在實例化一個日期對象(2013-1-6):
1 Date today = new Date(2013,1,6);
不好意思,其實返回的日期是:3913-2-6,有病吧......
我們也經常要用到一些時間變換處理的地方,如返回這個月的最后一個星期二,當月的最后一天,每個月的第一天的凌晨三點進行批處理任務,這個時候我們一般會用jdk中的Calendar 。雖然Calendar的出現改善了一些情況,但是為了處理一些時間變換時寫出各種DAY_OF_WEEK,DAY_OF_MONTH,MONTH....和add,set,roll之間的組合調用時,這種不直觀的api仍然使我們容易犯糊塗。
Joda-Time 是一個非常優秀的時間日期處理工具,正是它的強大易用性,作者好像成了JSR310的Leader,下面我們就來大概介紹一下里面強大的api。
實例化日期/時間對象及操作對象
初始化:
DateTime dt = new DateTime(2013,1,7,12,23,58,874);
改變對象:
dt = dt.withDayOfMonth(20);//設置成20號 dt = dt.minusDays(2);//前面兩天 dt = dt.plusWeeks(1);//后面一個星期
注意:「DateTime」對象是不可變對象(Immutable),所以每次操作改變對象時實質上是返回的一個全新對象。
字段屬性:
DateTime dt2 = new DateTime(); Property prop = dt.dayOfMonth();//得到字段屬性 int i = prop.getDifference(dt2);//兩個日期之間相差幾天
格式化(format)和解析(parse)
「格式化和解析日期/時間」這種需求在時間處理中大家都會碰到,在標准jdk中使用的是SimpleDateFormat,但是大家都知道這個玩藝用起來真是愛恨交加。首先SimpleDateFormat的實例化是重量級的,這就會促使我們用單例模式使用它,可是這個類又是「線程不安全的」,所以要synchronized調用。
joda-time對這類需求支持很強大:
DateTimeFormatter fmt = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss SSS"); String s = fmt.print(dt); dt = fmt.parseDateTime("2013-01-07 22:39:13 782");
如果你不想每次格式化輸出的時候都要去關聯一個DateTimeFormatter,可以直接這樣:
DateTime dt = new DateTime(); String str = dt.toString("yyyy-MM-dd HH:mm:ss SSS");
這里面的原理其實就是在調用toString的時候把傳進去的format string參數生成一個DateTimeFormatter,然后再格式化輸出。你可能考慮擔心每次都會實例化DateTimeFormatter對象會影響性能,其實內部已經做了緩存了。
private static final Map<String, DateTimeFormatter> cPatternedCache = new HashMap<String, DateTimeFormatter>(7);
如果你以為joda-time只有這些對相對jdk改善的功能,那么太小看它了,且看里面一些jdk沒有的高級api功能。
Interval/Duration/Period
它們三個之間很有相似性,都有“時間間隔”的意思,我們先看一段代碼,再講下它們之間的差異性。
DateTime start = new DateTime(2011,12,2,15,33);//開始時間 DateTime end = new DateTime(2013,3,1,12,34);//結束時間 Interval inteval = new Interval(start,end); Duration duration = new Duration(start,end); Period period = inteval.toPeriod(); boolean between = inteval.contains(new DateTime(2013,2,1,12,34));//判斷指定時間在這段時間間隔里: True long millSeconds = duration.getMillis();//得到兩個時間相差的豪秒數:39301260000 long millSeconds1 = period.getMillis();//得到兩個時間相差的豪秒數:0
上面的三個返回結果中,第一個好理解,「2013-2-1 12:34」是中「2011-12-2 15:33」和「2013-3-1 12:34」之間的一個時間。第二個返回這兩個時間相差的毫秒數(39301260000),也比較好理解。可是第三個結果返回 0 ,就比較暈了,其實Interval,Duration,Period都有「時間間隔」的意思在里面,但是這里定義三個類肯定是有三種語義在里面的,要理解這三者的區別,只要搞清楚這三個不同的類里包含的「信息」就知道差異了。
- Interval : 它包含了「開始時間」和「結束時間」信息,包括各自的時區。所以可以根據Interval對象得到「開始時間」和「結束時間」這兩個信息。
- Duration : 它沒有任何「開始時間」和「結束時間」的概念,只是保存了一個具體的時間間隔,也就是一個以毫秒為單元的整數。
- Period : 它和「Duration」差不多意思,也是沒有包含「開始時間」和「結束時間」的信息,但是它和「Duration」不同的是,它所指的時間間隔不是以毫秒為單元,而是「年」,「月」,「天」,「小時」,「分」,「秒」,「毫秒」為相對單元。舉個例子:「Duration」就像是“我比你大30天”的語義,而「Period」就是“我大你一個月”。它們兩種語義是不同的,“大你30天”是個絕對值,而“大你一個月”就是相對的,因為「一個月」可能是30天,也可能是31天,也可能會碰到2月分的28天,所以說是相對的。
再看上面代碼中的輸出結果,就知道為什么duration和period調用getMillis會出現不同的結果了:duration.getMillis返回的是絕對相差毫秒數,而period.getMillis返回0是因為「開始時間」和「結束時間」間隔的相對時間是:1年2個月3個星期5天21個小時1分鍾。