1. 程式人生 > >java程式設計中遇到的時區與時間問題總結

java程式設計中遇到的時區與時間問題總結

(摘自http://www.cnblogs.com/flying5/archive/2011/12/05/2276578.html)

最近在程式設計中遇到了時間與時區相關的問題,整理在這裡

  我的程式是一個在hadoop上執行的分散式程式,從mysql資料庫中取資料,經過處理之後輸出

一. 基本概念

  時區 :time zone 1884年國際經線會議規定,全球按經度分為24個時區,每區各佔經度15°。

      以本初子午線為中央經線的時區為零時區,由零時區向東、西各分12區,東、西12區都是半時區,共同使用180°經線的地方時。

  CST :China Standard Time UTC+8:00 中國標準時間(北京時間),在東八區

  UTC :Universal Time Coordinated,世界協調時間,又稱世界標準時間、世界統一時間。UTC 提供了一種與時區無關(或非特定於時區)的時間。

      世界上的所有時區都可以表示為 UTC 加上或減去一個偏移量。

      因此,UTC是0時區的時間,如北京為早上八點(東八區),UTC時間就為零點,時間比北京時晚八小時

  GMT :Greenwich Mean Time格林威治標準時間,指位於英國倫敦郊區的皇家格林尼治天文臺的標準時間,因為本初子午線被定義在通過那裡的經線。

  Unix timestamp :Unix時間戳,或稱Unix時間(Unix time)、POSIX時間(POSIX time),是一種時間表示方式,

      定義為從格林威治時間(UTC/GMT的午夜)1970年01月01日00時00分00秒起至現在的總秒數。

  可以這麼說:

  UTC和GMT幾乎是同一概念,兩者的區別是GMT是一個天文上的概念,UTC是基於原子鐘。

  GMT=UTC

  GMT + 8 = UTC + 8 = CST

  UTC+時間差=本地時間 (時間差東為正,西為負,東八區記為 +0800)

 

二. 從資料庫取資料的過程

 

  mysql>select auction_id,startsfrom auctions where

auction_id=88888;

+-------------+---------------------+

| auction_id  | starts |

+-------------+---------------------+

| 88888       | 2011-10-24 20:32:58 |

+-------------+---------------------+

1 rowin set (0.00 sec)

 

mysql> show columnsfrom auctions;

+--------------------------------+---------------+------+-----+---------+-------+

| Field | Type |Null | Key| Default | Extra |

+--------------------------------+---------------+------+-----+---------+-------+

|starts | datetime | YES | MUL |NULL | |

  可見:資料庫的時間欄位starts存的是datetime型別,它是一個和時區相關的string(顯然:string都是和時區相關的)

  而且資料庫是按照CST時區存的時間

     程式中從資料庫取資料用的sql語句:

 

mysql>select auction_id,DATE_FORMAT(starts,'%Y%m%d%H%i%S')from auctions where auction_id=88888;

+-------------+------------------------------------+

| auction_id  | DATE_FORMAT(starts,'%Y%m%d%H%i%S') |

+-------------+------------------------------------+

| 88888       | 20111024203258 |

+-------------+------------------------------------+

1 rowin set (0.00 sec)

  這裡只是簡單的用DATE_FORMAT函式把datetime型別的starts欄位轉換為我們需要的格式 %Y%m%d%H%i%S 而已

 

三、java程式碼

  看這樣一段轉換時間的java程式碼:

 

// 將字串時間轉化為秒數(yyyyMMddHHmmss)

staticpublic long getUnixTimestamp(String srcTime)

{        

          SimpleDateFormat sdf =new SimpleDateFormat("yyyyMMddHHmmss");

          Date result_date;

          longresult_time = 0;

          try

                   result_date = sdf.parse(srcTime);

                   //返回的是毫秒數故除以1000

                   result_time = result_date.getTime()/1000;

          }catch (Exception e) { 

                   //出現異常時間賦值為20000101000000

                   result_time =946684800;

          }

          returnresult_time;

 }

  計算結果:

 

getUnixTimestamp("20111204212224") =1323004944

  說明:java.util.Date中的getTime函式定義如下:

     java.util.Date代表一個時間點,其值為距公元1970年1月1日 00:00:00的毫秒數。所以它是沒有時區和Locale概念的。

     public long getTime() 返回自 1970 年 1 月 1 日 00:00:00 GMT 以來此 Date 物件表示的毫秒數

  java中通過如下形式取得當前時間點: 

 

Date now =new Date(); //這個時間點與本地系統的時區無關

  而正因為其與時區的無關性,才使得我們的儲存資料(時間)是一致的(時區一致性)。
  一般的我們將now儲存於資料庫中,當我們需要展現資料時,將now格式化成想要的格式,如:2011-12-04 21:22:24
  而這個功能一般交由java.text.DateFormat來實現。例如:

 

SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

String snow = sdf.format(now);

  我們發現snow是帶時間(如2011-12-04 21:22:24)的字串,那麼 2011-12-04 21:22:24 這個時間是哪個時區的時間呢?

  預設情況下,SimpleDateFormat 取得本地系統的時區(我的時區為GMT+8北京),然後按照 pattern("yyyy-MM-dd HH:mm:ss")格式化now,
  此時輸出的就是 GMT+8 區的時間了。如果想支援國際化時間,則先指定時區,然後再格式化date資料。例如:

 

SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

sdf.setTimeZone(TimeZone.getTimeZone("GMT+8"));

String snow = sdf.format(now);// snow = 2011-12-04 21:22:24

sdf.setTimeZone(TimeZone.getTimeZone("GMT+7"));

String snow2 = sdf.format(now);// snow2 = 2011-12-04 20:22:24 (可見:東八區比東七區早一個小時)

  另外,你可以通過如下程式碼修改本地時區資訊:

 

TimeZone.setDefault(TimeZone.getTimeZone("GMT+8"));

  在windows作業系統中,是通過桌面右下角,也可以指定作業系統的時區。

  在linux系統中,通過如下命令可以得到當前時區

 

[admin@localhost]$ date -R

Sun,04 Dec 201122:49:00+0800

 

四、結論:

  getTime()返回的已經是一個UTC的unix timestamp秒數了,與時區無關;而轉換為字串後,就和時區相關了
  對於這個秒數,不同時區的人,按照自己所在的時區去解析,就可以得到正確的時間了

 

[admin@localhost]$ date -d@1323004944

20111204日 星期日21:22:24CST

[admin@localhost]$ date -d@1323004944 -u

20111204日 星期日13:22:24UTC

  對於涉及到時間轉換的程式來說,如果程式碼裡面沒有強行指定時區,那就會依賴於作業系統的時區。

  特別是對於分散式程式,如果不同機器上系統時區不一樣,那就會出現不一致的資料了!

 

五、對unix timestamp和時區概念的曲解和誤用

  由於歷史原因,發現程式中有這樣一段程式碼:

 

// 將字串時間轉化為秒數(yyyyMMddHHmmss),有8個小時的時差

  staticpublic long getLongTime(String srcTime)

  {        

            SimpleDateFormat sdf =new SimpleDateFormat("yyyyMMddHHmmss");

            Date result_date;

            longresult_time = 0;

            try

                     result_date = sdf.parse(srcTime);

                     //返回的是毫秒數故除以1000

                     result_time = result_date.getTime()/1000+ 8 * 3600// 這裡加了八個小時

            }catch (Exception e) { 

                     //出現異常時間賦值為20000101000000

                     result_time =946684800;

            }

             

            returnresult_time;

   }

  計算結果:

 

getUnixTimestamp("20111204212224") =1323033744

  顯然,這個時間比上面通過 getUnixTimestamp("20111204212224") = 1323004944 得到的時間多了8個小時

       1323033744 - 1323004944 = 28800 = 8 * 3600 = 8h

  如果使用者將得到的 1323033744 按照自己所在的時區解析後得到的結果是:

 

[admin@localhost]$ date -d@1323033744

20111205日 星期一05:22:24CST

  得到了一個完全錯誤的結果!

  但如果使用者將這個 1323033744 按照UTC時區來解析後得到的結果是:

 

[admin@localhost]$ date -d@1323033744 -u

20111204日 星期日21:22:24UTC

  為了方便對比,把 1323004944 的解析結果也拿來對比

 

[admin@localhost]$ date -d@1323004944

20111204日 星期日21:22:24CST

[admin@localhost]$ date -d@1323004944 -u

20111204日 星期日13:22:24UTC

  可以看到,這個程式碼中得到的秒數時間是比UTC的unix timestamp秒數多了八個小時

    這個時間 1323033744 可以理解為北京時區得到的秒數,但是不是unix timstamp時間!

    unix timestamp秒數是與時區無關的,不管是在哪個時區得到的unix timestamp都是一樣的

  我們可以驗證一下,用北京時間“20111204212224”減去“19700000000000”得到的秒數,就是 1323033744

 

SimpleDateFormat df =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

java.util.Date end = df.parse("2011-12-04 21:22:24");

java.util.Date start = df.parse("1970-01-01 00:00:00");

longdelta = (end.getTime() - start.getTime())/1000;

System.out.println("delta="+ delta); // delta=1323033744

  或者用shell命令來求時間差

 

[admin@localhost]$ date -d"2011-12-04 21:22:24" +%s

1323004944

[admin@localhost]$ date -d"1970-01-01 0:0:0" +%s

-28800

[admin@localhost]$ date -d"2011-12-04 21:22:24" +%s -u

1323033744

[admin@localhost]$ date -d"1970-01-01 0:0:0" +%s -u

0

    1323004944 + 28800 = 1323033744

  對於東八區的人來說,1323033744 這個時間按照UTC時間可以解析正確。不能按照自己所在的時區去解析,不然就是錯的

  但是如果是東七區的人呢?需要按照UTC時間解析後,自己去減1個小時的時差,so ugly!

  所以,使用者在解析1323033744 這個資料的時候:

    (1) 按照UTC時間來解析得到北京時間,然後根據時間差換算成自己所在時區的時間

        (當然,一般都是在北京時區了,所以不用換算,按UTC時間來解析就能得到正確的時間)

    (2) 將這個時間減去8小時得到unix timestamp,然後按照自己所在的時區去解析就可以了

  總結:這段程式碼是對unix timestamp和時區的曲解和誤用。

 

六、從資料庫獲取unix timestamp時間

    其實從資料庫是可以直接獲取到unix timestamp時間的

 

mysql> select auction_id,unix_timestamp(starts)  from auctions where auction_id=88888;                                                   

 +-------------+------------------------+

 | auction_id  | unix_timestamp(starts) |

 +-------------+------------------------+

 |88888       |            1319459578 |

 +-------------+------------------------+

 1row in set (0.02 sec)

 

七、參考

  java.util.Date  http://www.jar114.com/jdk6/zh_CN/api/java/util/Date.html

  關於java Date和時區的問題  http://blog.163.com/[email protected]/blog/static/444125552009101924912981/

八、unix時間戳轉換工具

http://code.aosoo.com/unixtime