2016年4月25日 星期一

Java : SimpleDateFormat、TimeZone和Date -- 觀念釐清

在這篇裡主要紀錄了在Java中,對於SimpleDateFormat、TimeZone和Date的理解,釐清之前對它們有誤會及不太清楚的地方。

java.util.Date是一個早期Java中專門用來紀錄日期的一個類別,同時也是常用java.sql.Date的父類別,它並沒有儲存時區的資訊,當我們要對其進行不同時區的值(String或Date)轉換時,可以利用SimpleDateFormat及TimeZone來幫助我們。

TimeZone是個儲存時區資訊的類別,而SimpleDateFormat是一個用來進行日期格式轉換的類別,可以利用SimpleDateFormat.setTimeZone()來設定TimeZone,而常用的為以下兩個方法:


  1. String SimpleDateFormat.format(Date)
  2. Date SimpleDateFormat.parse(String)

SimpleDateFormat.format(Date)
可以送入一個Date物件,並返回一個表示日期的String。
如果SimpleDateFormat沒有設定TimeZone的話,
等同設定了TimeZone.getDefault()。
SimpleDateFormat.format(Date) 會將送入的Date參數轉成用設定的TimeZone來看時,正確的日期String。
例如:
如果 Date 是 2022-01-01T23:00:00+08
Timezone 設定 UTC-08
那 format() 出來的 String 就會是
2022-01-01T07:00:00

如果你的 SimpleDateFormat 建構子的字串有設定代表時區的 Z 符號的話,就可以印出時區,
例如:
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
上例的結果會是 2022-01-01T07:00:00-08

SimpleDateFormat.parse(String) 
可以送入一個表示日期的String,並返回一個Date物件。
如果SimpleDateFormat沒有設定TimeZone的話,
等同設定了TimeZone.getDefault()。
SimpleDateFormat.parse(String) 會將送入的 String 轉成當用設定的 Timezone 來看時
正確的 Date 物件。
例如:
如果 String 是 2022-01-01T07:00:00
Timezone 設定 UTC+08
那 parse() 出來的 Date 就會是 
2022-01-01T07:00:00+08

========================
接著下面舉一個例子:

如果我們有一個表示Local TimeZone(假設程式放在台灣server上)日期的Date物件,例如:
System.our.println(dateObject_local);   //2016/4/25 16:47:59


我們想把它轉成在TimeZone為America/Los_Angeles時,看到這個日期應看到的Date物件,例如
System.our.println(dateObject_America);   //2016/4/25 01:47:59


應該要如何做呢?
首先宣告TimeZone並設定給SimpleDateFormat
TimeZone timeZone_America = TimeZone.getTimeZone("America/Los_Angeles");
TimeZone timeZone_default = TimeZone.getDefault();

SimpleDateFormat simpleDateFormat_America = new SimpleDateFormat();
simpleDateFormat_America.setTimeZone(timeZone_America);

SimpleDateFormat simpleDateFormat_Taiwan = new SimpleDateFormat();
simpleDateFormat_Taiwan.setTimeZone(timeZone_default);

Date dateObject_local = new Date();
System.out.println("dateObject_local:" + dateObject_local);  //dateObject_local:Mon Apr 25 18:11:27 CST 2016

接著我們用simpleDateFormat_America去format上面那個Date,
String dateString_America = simpleDateFormat_America.format(dateObject_local);
System.out.println("dateString_America:" + dateString_America); //dateString_America:2016/4/25 上午 3:11

會看到時間被減了15小時,這正是美國比台灣慢15小時的時差。
如果我們想得到輸出是時間 3:11的Date物件,下面這樣是錯的:
Date Wrong_dateObject = simpleDateFormat_America.parse(dateString_America);
System.out.println("Wrong_dateObject:" + Wrong_dateObject); //Wrong_dateObject:Mon Apr 25 18:16:00 CST 2016

原因是當用simpleDateFormat_America去parse上面那個String, dateString_America時,它會用simpleDateFormat_America設定的TimeZone去看,也就是說,它認為這是美國時間3:11,所以要parse回local time的Date時,它會將時間加回15小時,所以得到非我們預期的結果。

正確的做法是下面這樣:
Date Correct_dateObject = simpleDateFormat_Taiwan.parse(dateString_America);
System.out.println("Correct_dateObject:" + Correct_dateObject); //Correct_dateObject:Mon Apr 25 03:16:00 CST 2016

我們用simpleDateFormat_Taiwan去parse,讓它以為這是本地時間3:11,再parse成local time的Date時,就不會進行時差的動作了(因為時差為0)。

只要記得,Date本身沒有時區的資訊,基本上都用Local TimeZone去對待得到一個無時區概念的毫秒數,不管是被format還是由parse得到。

而String則會用SimpleDateFormat設定的TimeZone去對待,不管是由format得到還是被parse。

沒有留言 :

張貼留言