Java處理時間/時區/普通時間與時間戳的轉換

將時間轉換爲時間戳:

/* 
     * 將時間轉換爲時間戳
     */    
    public static String dateToStamp(String s) throws ParseException{
        String res;
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date date = simpleDateFormat.parse(s);
        long ts = date.getTime();
        res = String.valueOf(ts);
        return res;
    }

日期和時間在程序中應用廣泛,每種程序開發語言都自帶處理日期和時間的相關函數,很多開發者把日期和時間存入數據庫中,但是,一旦涉及到跨時區的日期和時間的處理時,大多數開發者根本就不明白如何正確地處理日期和時間。

首先,我們來看大部分的程序都是這麼創建當前時間並存入數據庫的:

Date date = new Date();
store2db(date);

這麼做的問題在於,數據庫的DateTime類型沒有時區(time zone)信息,因此,存入的是本地時間,並且丟掉了時區信息。如果你把數據庫服務器的時區改了,或者把應用服務器的時區改了,讀出來的日期和時間就是錯誤的。如果以Timestamp類型存儲,各數據庫的實現也不相同,有的進行了內部時區自動轉換,而且,存儲的時間不超過2037年。

如果應用服務器的時區和數據庫服務器的時區不一致,你無法確定數據庫驅動程序會不會自動幫你轉換。

大多數開發者遇到這個問題不是去探索正確的解決方法,而是自作聰明地在存入數據庫之前先來個“調整”,比如把當前時間減掉8小時,在顯示的時候遇到不正確的時間時,又來個“調整”,以“負負得正”的方式來掩蓋錯誤。在遇到夏令時的時區時,還需要寫更復雜的代碼來調整小時。

正確的做法是先理解時間和時區的概念。

時區的概念

之所以有時區的概念是因爲住在地球上不同地方的人看到太陽昇起的時間是不一樣的。我們假設北京人民在早上8:00看到了太陽剛剛升起,而此刻歐洲人民還在夜裏,他們還需要再過7個小時才能看到太陽昇起,所以,此刻歐洲人民的手錶上顯示的是凌晨1:00。如果你強迫他們用北京時間那他們每天看到日出的時間就是下午3點。

也就是說,東8區的北京人民的手錶顯示的8:00和東1區歐洲人民手錶顯示的1:00是相同的時刻:

"2014-10-14 08:00 +8:00" = "2014-10-14 01:00 +1:00"

這就是本地時間的概念。

但是,在計算機中,如果用本地時間來存儲日期和時間,在遇到時區轉換的問題上,即便你非常清楚地知道如何轉換,也非常麻煩,尤其是矯情的美國人還在採用夏令時。

所以我們需要引入“絕對時間”的概念。絕對時間不需要年月日,而是以秒來計時。當前時間是指從一個基準時間(1970-1-1 00:00:00 +0:00),到現在的秒數,用一個整數表示。

當我們用絕對時間表示日期和時間時,無論服務器在哪個時區,任意時刻,他們生成的時間值都是相等的。所有編程語言都提供了方法來生成這個時間戳,Java和JavaScript輸出以毫秒計算的Long型整數,Python等輸出標準的Unix時間戳,以秒計算的Float型浮點數,這兩者轉換隻存在1000倍的關係。

實際上,操作系統內部的計時器也是這個標準的時間戳,只有在顯示給用戶的時候,才轉換爲字符串格式的本地時間。

正確的存儲方式

基於“數據的存儲和顯示相分離”的設計原則,我們只要把表示絕對時間的時間戳(無論是Long型還是Float)存入數據庫,在顯示的時候根據用戶設置的時區格式化爲正確的字符串。

數據的存儲和顯示相分離是非常基本的設計原則,卻常常被大多數開發人員忽略。舉個例子,在Excel中編寫一個表格,表格的數據可視爲數據的存儲格式,你可以把表格的數據以柱狀圖或餅圖表示出來,這些不同的圖表是數據的不同顯示格式,存儲數據的時候,我們應該存儲表格數據,絕不應該存儲柱狀圖等圖片信息。

HTML和CSS也是數據的存儲和顯示相分離的設計思想。

所以,數據庫存儲時間和日期時,只需要把Long或者Float表示的時間戳存到BIGINTREAL類型的列中,完全不用管數據庫自己提供的DATETIMETIMESTAMP,也不用擔心應用服務器和數據庫服務器的時區設置問題,遇到Oracle數據庫你不必去理會with timezonewith local timezone到底有啥區別。

讀取時間時,讀到的是一個Long或Float,只需要按照用戶的時區格式化爲字符串就能正確地顯示出來:

// Java:
long t = System.currentTimeMillis();
System.out.println("long = " + t);
 
// current time zone:
SimpleDateFormat sdf_default = new SimpleDateFormat("yyyy-MM-dd HH:mm");
System.out.println(sdf_default.format(t));
 
// +8:00 time zone:
SimpleDateFormat sdf_8 = new SimpleDateFormat("yyyy-MM-dd HH:mm");
sdf_8.setTimeZone(TimeZone.getTimeZone("GMT+8:00"));
System.out.println("GMT+8:00 = " + sdf_8.format(t));
 
// +7:00 time zone:
SimpleDateFormat sdf_7 = new SimpleDateFormat("yyyy-MM-dd HH:mm");
sdf_7.setTimeZone(TimeZone.getTimeZone("GMT+7:00"));
System.out.println("GMT+7:00 = " + sdf_7.format(t));
 
// -9:00 time zone:
SimpleDateFormat sdf_la = new SimpleDateFormat("yyyy-MM-dd HH:mm");
sdf_la.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
System.out.println("America/Los_Angeles = " + sdf_la.format(t));

輸出:

long = 1413230086802
2014-10-14 03:54
GMT+8:00 = 2014-10-14 03:54
GMT+7:00 = 2014-10-14 02:54
America/Los_Angeles = 2014-10-13 12:54

基於絕對時間戳的時間存儲,從根本上就沒有時區的問題。時區只是一個顯示問題。額外獲得的好處還包括:

  • 兩個時間的比較就是數值的比較,根本不涉及時區問題,極其簡單;

  • 時間的篩選也是兩個數值之間篩選,寫出SQL就是between(?, ?)

  • 顯示時間時,把Long或Float傳到頁面,無論用服務端腳本還是用JavaScript都能簡單而正確地顯示時間。

你唯一需要編寫的兩個輔助函數就是String->LongLong->StringString->Long的作用是把用戶輸入的時間字符串按照用戶指定時區轉換成Long存進數據庫。

唯一的缺點是數據庫查詢你看到的不是時間字符串,而是類似1413266801750之類的數字。

Date對象會和本地時區保持一致,當得到固定時區的時間之後:string類型的字符串,轉換成date之後就會變爲系統的時區。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章