本作品採用知識共用署名-非商業性使用-相同方式共用 2.5 中國大陸許可協議進行許可。
在Java中最常用的日期時間操作類有四個:
java.util.Date
java.sql.Date
java.sql.Time
java.sql.Timestamp
為了精確表達商務邏輯,應盡量避免使用父類(java.util.Date)的方法。java.sql包下的三個子類中特有的valueOf()靜態方法與toString()方法可以精確表達商務邏輯。
系統時間與本地時間
在中國地區,如果調用以下代碼,我們會得到“1970-01-01 08:00:00”的結果
// 例1<br />java.util.Date obj1 = new java.util.Date(0L);<br />DateFormat dateFormat =<br /> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");<br />System.out.println(dateFormat.format(obj1));<br />// 列印結果:1970-01-01 08:00:00
看了JDK API之後,您可能會有這樣的疑問,為什麼 new java.util.Date(0L) 得到的時間不是“1970-01-01 00:00:00”而是“1970-01-01 08:00:00”呢?難到JDK API寫錯了?
JDK API沒有錯,關鍵是建立java.util.Date對象所使用的毫秒數是電腦系統時間,該毫秒數指的是自 1970 年 1 月 1 日 00:00:00 GMT 以來的毫秒數,即格林尼治標準時間(GMT)的1970-01-01 00:00:00。但是當電腦以字串方式展示給我們看的時候,這個時間已經成為本地時間了,例如中國地區使用GMT+8時區,所以我們看到的是“1970-01-01 08:00:00”的結果!
將上述代碼修改一下,我們就可以得到一個更清晰的概念。如:
// 例2<br />java.util.Date obj1 = new java.util.Date(0L);<br />DateFormat dateFormat =<br /> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss 'GMT'Z");<br />System.out.println(dateFormat.format(obj1));<br />// 列印結果:1970-01-01 08:00:00 GMT+0800
上面的代碼我們可以看到“1970-01-01 08:00:00 GMT+0800”的結果,證明我們所處的時區在GMT+8區。當我們改變時區時,相同的系統時間將顯示不同的結果。如:
// 例3<br />java.util.Date obj1 = new java.util.Date(0L);<br />TimeZone.setDefault(TimeZone.getTimeZone("GMT+0:00"));<br />DateFormat dateFormat =<br /> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss 'GMT'Z");<br />System.out.println(dateFormat.format(obj1));<br />// 列印結果:1970-01-01 00:00:00 GMT+0000
上面的代碼我們可以看到“1970-01-01 00:00:00 GMT+0000”的結果,證明現在所處的時區在GMT+0區。
總之,通過以上的描述,我們可以得出這樣幾個結論:
- 所有電腦的系統時間概念上是統一的;
- 相同的系統時間會根據不同的時區產生不同的本地時間;
- 當時區為GMT+0時,系統時間與本地時間相同。
精確的商務邏輯
在Java中最常用的日期時間操作類中java.util.Date是其它三個類的父類。因為有這樣的繼承關係,所有四個類均可以通過系統時間產生對象。例如:
java.util.Date obj = new java.sql.Date(0L);
這種使用系統時間作為資料產生的日時對象在商務邏輯上可能存在問題。例如在時區GMT+0環境下運行如下代碼:
// 例4<br />// 將時區設定為GMT+0<br />TimeZone.setDefault(TimeZone.getTimeZone("GMT+0:00"));</p><p>// 使用傳統構造方法產生日時對象<br />java.util.Date obj1 = new java.sql.Date(0L);<br />java.util.Date obj2 = new java.sql.Date(3600000L);</p><p>// 商務邏輯相同<br />System.out.println(obj1.toString()); // "1970-01-01"<br />System.out.println(obj2.toString()); // "1970-01-01"</p><p>// 文法比較不相同<br />System.out.println(obj1.equals(obj2)); // false<br />System.out.println(obj1.compareTo(obj2)); // -1
上述代碼產生的兩個對象在商務邏輯上都表示格林尼治標準時間1970-01-01(00:00:00),但在文法上二者時間卻相差1小時。出現這種現象的根本原因在於使用了不精確的系統時間來反映日時對象的商務邏輯。
所謂日時對象的商務邏輯,就是以本地時間作為標準的日時資訊。例如描述格林尼治標準時間(GMT+0)1970-01-01 00:00:00的精確系統時間是0L毫秒;描述北京時間(GMT+8)1970-01-01 00:00:00的精確系統時間則是-28800000L毫秒。
除java.util.Date外,其它三個類均有屬於自己的業務範圍,例如:
java.sql.Date的有效成份包括年、月、日
java.sql.Time的有效成份包括時、分、秒
java.sql.Timestamp的有效成份包括年、月、日、時、分、秒、納秒(毫微秒)
由於四種日時對象內部均使用細化到毫秒的系統時間作為標準資料,勢必會造成使用不同的系統時間可以表達相同商務邏輯的現象。那麼通過什麼方式可以將一個不精確的系統時間轉換成能夠準確表達商務邏輯的精確系統時間呢?
1. 使用toString()來獲得日時對象的商務邏輯
在繼承自java.util.Date的三個子類中,傳統的構造方法以及getTime()和setTime()兩個方法均使用系統時間來實現,也就註定了它們在商務邏輯上的不精確性。如果已經使用了不精確的系統時間建立java.sql包中的日時對象,則toString()是唯一個可以擷取精確商務邏輯的方法。例4的代碼可以說明這一點。
2. 使用valueOf()構造精確的日時對象
為了能夠從本質上使用精確系統時間來準確表達日時對象中的商務邏輯,除java.util.Date外,其它三個類均提供了第二個產生對象的方法,那就是valueOf()靜態方法。例如:
java.util.Date obj1 = java.sql.Date.valueOf("2000-01-01");
通過傳遞一個準確表達日時資訊的字串,valueOf()將產生一個由精確系統時間構成的可以反映準確商務邏輯的日時對象。同樣,這種精確的日時對象也可以在文法上進行精確的比較判斷,例如:
// 例5<br />// 使用valueOf()產生日時對象<br />java.util.Date obj1 = java.sql.Date.valueOf("2000-01-01");<br />// 使用精確系統時間產生日時對象<br />java.util.Date obj2 = new java.sql.Date(obj1.getTime());</p><p>// 商務邏輯準確<br />System.out.println(obj1.toString()); // "2000-01-01"<br />System.out.println(obj2.toString()); // "2000-01-01"</p><p>// 文法比較準確<br />System.out.println(obj1.equals(obj2)); // true<br />System.out.println(obj1.compareTo(obj2)); // 0
通過對valueOf()和toString()的瞭解,我們自然會想到一種轉換方式,可以將不精確的系統時間轉換成可以準確表達商務邏輯的精確系統時間。如下面的工具類:
public class DateTimeUtils {<br /> /**<br /> * 取得可以精確表達商務邏輯的日時對象<br /> *<br /> * @param obj<br /> * 不精確的日時對象<br /> * @return 精確的日時對象<br /> */<br /> public static java.sql.Date getLocalDate(java.util.Date obj) {<br /> if (obj == null)<br /> return null;</p><p> java.sql.Date tmp = null;<br /> if (java.sql.Date.class.equals(obj.getClass())) {<br /> // 如果原始日時對象的類型是java.sql.Date<br /> // 則轉換過程分兩步:<br /> // 第一步,取得java.sql.Date對象的精確商務邏輯值<br /> String tmpString = obj.toString();<br /> // 第二步,產生能夠精確反映商務邏輯的日時對象<br /> tmp = java.sql.Date.valueOf(tmpString);<br /> } else {<br /> // 如果原始日時對象的類型不是java.sql.Date<br /> // 則轉換過程分三步:<br /> // 第一步,產生一個不能精確表達商務邏輯的java.sql.Date對象<br /> obj = new java.sql.Date(obj.getTime());<br /> // 第二步和第三步與處理java.sql.Date類型的原始日時對象相同<br /> // 第二步,取得java.sql.Date對象的精確商務邏輯值<br /> String tmpString = obj.toString();<br /> // 第三步,產生能夠精確反映商務邏輯的日時對象<br /> tmp = java.sql.Date.valueOf(tmpString);<br /> }<br /> return tmp;<br /> }<br />}
上述方法可以簡化為:
public static java.sql.Date getLocalDate(java.util.Date obj) {<br /> if (obj == null)<br /> return null;</p><p> java.sql.Date tmp = null;<br /> if (java.sql.Date.class.equals(obj.getClass())) {<br /> tmp = java.sql.Date.valueOf(obj.toString());<br /> } else {<br /> tmp = getLocalDate(new java.sql.Date(obj.getTime()));<br /> }<br /> return tmp;<br />}
根據相同道理,也可以得到轉換java.sql.Time與java.sql.Timestamp日時對象的方法。最後的版本如下:
public class DateTimeUtils {<br /> /**<br /> * 取得可以精確表達商務邏輯的日期對象<br><br /> *<br /> * 如果原始日時對象的類型是java.sql.Date <br><br /> * 則轉換過程分兩步:<br><br /> * 第一步,取得java.sql.Date對象的精確商務邏輯值 <br><br /> * 第二步,產生能夠精確反映商務邏輯的日時對象<br><br /> *<br /> * 如果原始日時對象的類型不是java.sql.Date<br><br /> * 則轉換過程分三步:<br><br /> * 第一步,產生一個不能精確表達商務邏輯的java.sql.Date對象<br><br /> * 第二步和第三步與處理java.sql.Date類型的原始日時對象相同<br /> *<br /> * @param obj<br /> * 不精確的日期對象<br /> * @return 精確的日時對象<br /> */<br /> public static java.sql.Date getLocalDate(java.util.Date obj) {<br /> if (obj == null)<br /> return null;</p><p> java.sql.Date tmp = null;<br /> if (java.sql.Date.class.equals(obj.getClass())) {<br /> tmp = java.sql.Date.valueOf(obj.toString());<br /> } else {<br /> tmp = getLocalDate(new java.sql.Date(obj.getTime()));<br /> }<br /> return tmp;<br /> }</p><p> public static java.sql.Time getLocalTime(java.util.Date obj) {<br /> if (obj == null)<br /> return null;</p><p> java.sql.Time tmp = null;<br /> if (java.sql.Time.class.equals(obj.getClass())) {<br /> tmp = java.sql.Time.valueOf(obj.toString());<br /> } else {<br /> tmp = getLocalTime(new java.sql.Time(obj.getTime()));<br /> }<br /> return tmp;<br /> }</p><p> public static java.sql.Timestamp getLocalTimestamp(java.util.Date obj) {<br /> if (obj == null)<br /> return null;</p><p> java.sql.Timestamp tmp = null;<br /> if (java.sql.Timestamp.class.equals(obj.getClass())) {<br /> tmp = java.sql.Timestamp.valueOf(obj.toString());<br /> } else {<br /> tmp = getLocalTimestamp(new java.sql.Timestamp(obj.getTime()));<br /> }<br /> return tmp;<br /> }<br />}
我們可以這樣使用:
TimeZone.setDefault(TimeZone.getTimeZone("GMT+8:00"));<br />// 使用三種方法產生的日時對象<br />java.util.Date obj1 = java.sql.Date.valueOf("1970-01-01");<br />java.util.Date obj2 = new java.util.Date(0L);<br />java.util.Date obj3 = new java.sql.Date(0L);</p><p>// 通過相同的方式轉換<br />obj1 = DateTimeUtils.getLocalDate(obj1);<br />obj2 = DateTimeUtils.getLocalDate(obj2);<br />obj3 = DateTimeUtils.getLocalDate(obj3);</p><p>// 得到了相同的精確商務邏輯<br />System.out.println(obj1.getTime());// -28800000<br />System.out.println(obj2.getTime());// -28800000<br />System.out.println(obj3.getTime());// -28800000</p><p>// 商務邏輯相同<br />System.out.println(obj1.toString()); // 1970-01-01<br />System.out.println(obj2.toString()); // 1970-01-01</p><p>// 文法比較準確<br />System.out.println(obj1.equals(obj2)); // true<br />System.out.println(obj1.compareTo(obj2)); // 0
也可以在不同類型的日時對象之間轉換,如:
TimeZone.setDefault(TimeZone.getTimeZone("GMT+8:00"));<br />java.util.Date obj1 =<br /> java.sql.Timestamp.valueOf("2000-01-01 01:00:00");</p><p>// 將java.sql.Timestamp轉換成java.sql.Time<br />// 日期資訊變成 1970-01-01<br />// 時間資訊保留<br />java.util.Date obj2 = DateTimeUtils.getLocalTime(obj1);<br />System.out.println(obj2.toString());// 01:00:00<br />System.out.println(obj2.getTime());// -25200000</p><p>// 將java.sql.Time轉換成java.sql.Date<br />// 日期資訊保留<br />// 時間資訊變成 00:00:00<br />java.util.Date obj3 = DateTimeUtils.getLocalDate(obj2);<br />System.out.println(obj3.toString());// 1970-01-01<br />System.out.println(obj3.getTime());// -28800000