還不知道這個可不能夠 copy, 只是看到了 Joda-Time 與 JSR310 的概念,可是並不清楚,因此記錄一下。java
日期與時間處理 API,在各種語言中,可能都只是個不起眼的 API,若是你沒有較複雜的時間處理需求,可能只是利用日期與時間處理 API 取得系統時間,簡單地作些顯示罷了,然而若是真的要認真看待日期與時間,其複雜程度可能會遠超過你的想像,天文、地理、歷史、政治、文化等因素,都會影響到你對時間的處理。less
在 Java 的世界中,處理日期最基本的 API 就是 java.util.Date,這個遠從 JDK 1.0 就已存在的古老 API,若是你只是要取得日期時間做些顯示,也許還沒有問題,若是你打算以它做為基礎進行日期時間,那就極可能落人陷阱,來舉個實際遇過的例子:ide
import java.util.*; import java.text.*; public class Main { public static void main(String[] args) throws Exception { DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); Date birthDate = dateFormat.parse(args[0]); Date nowDate = new Date(); long lifeMillis = nowDate.getTime() - birthDate.getTime(); System.out.printf("你今年的歲數為:%d%n", lifeMillis / (365 * 24 * 60 * 60 * 1000)); } }
看來沒什麼問題?從命令列引數取得日期格式字串並剖析為 Date 實例,取得 1970 年 1 月 1 日起始至今的毫秒數進行相減,就是目前你目前活了毫秒數,真的嗎?一年的毫秒數真的是 (365 * 24 * 60 * 60 * 1000) 嗎?別的不說,我輸入 "1975-05-26",算出來但是 800 多歲呢!… 緣由?(365 * 24 * 60 * 60 * 1000) 溢位(Overflow)了 … 詳細緣由能夠參考 Promotion 與 Cast,若是單是解決溢版問題,就在分母加個 L 就能夠了:this
System.out.printf("你今年的歲數為:%d%n", lifeMillis / (365 * 24 * 60 * 60 * 1000L));
其實在建構 Date 實例時也有些陷阱,例如,你想表示 "2013-08-02",這麼寫就不對了 …code
Date date = new Date(13, 8, 2); DateFormat dateFormat = DateFormat.getDateInstance(); System.out.printf("Taiwan Java Developer Day is %s.%n", dateFormat.format(date));
若是真要用 Date 建構式創建實例,那年份的部份,必須是西元年減去 1900,也就是說,若是你想表示的是 2013 年,那要 new Date(113, 8, 2) 才對,很奇怪對吧!實際上這個建構式早就被廢棄(Deprecated),編譯時你應該看一下警示訊息(不過應該不少人根本不看的吧!)…orm
應該會有人告訴你,若是你要設置日期,應該使用 java.util.Calendar,像是這樣 …ip
Calendar calendar = Calendar.getInstance(); calendar.set(2013, 8, 2); DateFormat dateFormat = DateFormat.getDateInstance(); System.out.printf("Taiwan Java Developer Day is %s.%n", dateFormat.format(calendar.getTime()));
若是你想表達的還是 "2013-08-02",這麼寫就又不對了 … Calendar 的 set 方法第二個參數,數字起算是從 0 開始,因此要表示 8 月應該給 7 這個數字才對,不想搞混的話,就用個列舉常數吧!ci
calendar.set(2013, Calendar.AUGUST, 2);
Calendar 這個東西,是有一些計算日期時間的方法,不過還是得當心有些陷阱,下面這個程式是用來計算兩個日期之間的天數:rem
public static void main(String[] args) { Calendar birth = Calendar.getInstance(); birth.set(1975, Calendar.MAY, 26); Calendar now = Calendar.getInstance(); System.out.println(daysBetween(birth, now)); System.out.println(daysBetween(birth, now)); // 顯示 0? } public static long daysBetween(Calendar begin, Calendar end) { long daysBetween = 0; while(begin.before(end)) { begin.add(Calendar.DAY_OF_MONTH, 1); daysBetween++; } return daysBetween; }
daysBetween 有點問題,若是連續計算兩個 Date 實例的話,第二次會取得 0,什麼情況下,會想要連續計算呢?總是會有這類的情況 … 無論如何,因為 Calendar 狀態是可變的,考慮會重複計算的場合,最好是複製出一個新的 Calendar:get
public static long daysBetween(Calendar begin, Calendar end) { Calendar calendar = (Calendar) begin.clone(); // 複製 long daysBetween = 0; while(calendar.before(end)) { calendar.add(Calendar.DAY_OF_MONTH, 1); daysBetween++; } return daysBetween; }
陷阱還蠻多的 … 實際要探討下去,其實問題會更多,總之 …
雖然叫做 Date,然而 Date 實例真正表明的並不是日期,若是你認真去解讀它,Date 實例真正表明的概念,最接近的應該是特定的瞬時(a specific instant in time),時間精度是毫秒,例如 … 1375430498832 表明從 「the epoch",也就是 UTC 時間 1970 年 1 月 1 日零時零分零毫秒至今經過 1375430498832 毫秒數的特定瞬時,嗯 … 「the epoch" 與 UTC 這兩個玩意兒後面也都還要說明 …
總之,在 JDK 1.1 以前,Date 可用來將特定的瞬時剖析為年、月、日、時、分、秒等,不過值的表示有些奇怪,像是 …
後來,從 JDK 1.1 開始,將特定的瞬時剖析為年、月、日、時、分、秒等的方法都被廢棄了,看來 Date 單純能夠表示瞬時了?事實上並非如此,Date 的 setTime 方法沒有被廢棄,也就是說,Date 的狀態還是可變的,若是你在 API 之間傳遞它而不想改變它的狀態,就得祈禱 API 別去動它,另外一方面,要手動利用 Date 計算日期時間,實在是太麻煩也容易出錯 …
Calendar 是提供了一些計算日期時間的方法,不過 … 使用 Calendar 實在是太麻煩、太痛苦了,你得用一堆列舉常數,像是 YEAR、MONTH、DAY_OF_MONTH、HOUR 等,否則就得當心那些從 0 開始計算的日期時間,像是月份,另外一方面,Calendar 狀態可變,有時也會形成前述的類似問題 …
至於 Calendar 相關的 API 為什麼這麼難用,這當中還有一些八掛歷史 … 查看一下 Calendar 的原始碼,你會看到這個註解:
/* * (C) Copyright Taligent, Inc. 1996-1998 - All Rights Reserved * (C) Copyright IBM Corp. 1996-1998 - All Rights Reserved * * The original version of this source code and documentation is copyrighted * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These * materials are provided under terms of a License Agreement between Taligent * and Sun. This technology is protected by multiple US and International * patents. This notice and attribution to Taligent may not be removed. * Taligent is a registered trademark of Taligent, Inc. * */
有些人可能知道,Calendar 相關 API 是 IBM 捐出的,只是 Taligent 是什麼單位?有興趣的,能夠查看 The Seven Habits of Highly Dysfunctional Design 這篇文章中 Useless data types: Date, Date, Time and Timestamp 談到的一些歷史 …
實際上,無論你是使用 Date、Calendar,或者是使用後面要介紹的 Joda-Time、JSR310 等,都得瞭解更多日期時間的歷史與概念,才能清楚知道那些 API 表明了什麼,這會是下篇要討論的內容 …