在正式認識 Joda-Time 與 JSR310 之前,得先來瞭解一些時間、日期的時空歷史等議題,如此你才會知道,時間日期確實是個很複雜的議題,而使用程式來處理時間日期,也不單只是使用 API 的問題 …
度量時間
想度量時間,得先有個時間基準,大多數人知道格林威治(Greenwich)時間,那麼就先從這個時間基準開始認識 …
格林威治標準時間
格林威治標準時間(Greenwich Mean Time),經常簡稱 GMT 時間,一開始是參考自格林威治皇家天文台的標準太陽時間,格林威治標準時間的正午是太陽抵達天空最高點之時,由於後面既將述及的一些源由,GMT 時間常不嚴謹(且有爭議性)地當成是 UTC 時間。
GMT 透過觀察太陽而得,然而地球公轉軌道為橢圓形且速度不一,本身自轉亦緩慢減速中,因而會越來越大的時間誤差,現在 GMT 已不作為標準時間使用。
世界時
世界時(Universal Time, UT)是藉由觀測遠方星體跨過子午線(meridian)而得,這會比觀察太陽來得準確一些,西元 1935 年,International Astronomical Union 建議使用更精確的 UT 來取代 GMT,而在 1972 年導入 UTC 之前,GMT 與 UT 是相同的。
國際原子時
雖然觀察遠方星體會比觀察太陽來得精確,不過 UT 基本上仍受地球自轉速度影響而會有誤差。1967 年定義的國際原子時(International Atomic Time, TAI),將秒的國際單位(International System of Units, SI)定義為銫(caesium)原子輻射振動 9192631770 周耗費的時間,時間從 UT 的 1958 年開始同步。
世界協調時間
由於基於銫原子振動定義的秒長是固定的,然而地球自轉會越來越慢,這會使得實際上 TAI 時間,會不斷超前基於地球自轉的 UT 系列時間,為了保持 TAI 與 UT 時間不要差距過大,因而提出了具有折衷修正版本的世界協調時間(Coordinated Universal Time),常簡稱為 UTC。
UTC 經過了幾次的時間修正,為了簡化日後對時間的修正,1972 年 UTC 採用了閏秒(leap second)修正(1 January 1972 00:00:00 UTC 實際上為 1 January 1972 00:00:10 TAI),確保 UTC 與 UT 相差不會超過 0.9 秒,加入閏秒的時間通常會在 6 月底或 12 月底,由巴黎的 International Earth Rotation and Reference Systems Service 負責決定何時加入閏秒。
撰寫本文的這個時間點,最近一次的閏秒修正為 2012 年 6 月 30 日,當時 TAI 實際上已超前 UTC 有 35 秒之長。
Unix 時間
Unix 系統的時間表示法,定義為 UTC 時間 1970 年(Unix 元年)1 月 1 日 00:00:00 為起點而經過的秒數,不考慮閏秒修正,用以表達時間軸上某一瞬間(instant)。
epoch 日
某個特定時代的開始,時間軸上某一瞬間。例如 Unix epoch 選為 UTC 時間 1970 年 1 月 1 日 00:00:00,不少發源於 Unix 的系統、平台、軟體等,也都選擇這個時間作為時間表示法的起算點,例如 Java 的 java.util.Date 封裝的時間資訊,就是 January 1, 1970, 00:00:00 GMT(實際上是 UTC)經過的毫秒數。
以上關於時間日期只是挑些重要的部份來說明,有機會(又有時間)的話,你應該在維基百科上詳細認識,就以這些說明來說有幾個重點:
- 就目前來說,即使標註為 GMT(無論是文件說明,或者是 API 的日期時間字串描述),實際上談到時間指的是 UTC 時間。
- 秒的單位定義是基於 TAI,也就是銫原子輻射振動次數。
- UTC 考量了地球自轉越來越慢而有閏秒修正,確保 UTC 與 UT 相差不會超過 0.9 秒。最近一次的閏秒修正為 2012 年 6 月 30 日,當時 TAI 實際上已超前 UTC 有 35 秒之長。
- Unix 時間是 1970 年 1 月 1 日 00:00:00 為起點而經過的秒數,不考慮閏秒,不少發源於 Unix 的系統、平台、軟體等,也都選擇這個時間作為時間表示法的起算點。
年曆
度量時間是一回事,表達日期又是另一回事,前面談到時間起點,都是使用公曆,中文世界又常稱為陽曆或西曆,在談到公曆之前,得稍微往前談一下其他曆法…
儒略曆
儒略曆(Julian calendar)是現今西曆的前身,用來取代羅馬曆(Roman calendar),於西元前 46 年被 Julius Caesar 採納,西元前 45 年實現,約於西元 4 年至 1582 年之間廣為各地採用。儒略曆修正了羅馬曆隔三年設置一閏年的錯誤,改採四年一閏。
格里高利曆
格里高利曆(Gregorian calendar)改革了儒略曆,由教宗 Pope Gregory XIII 於 1582 年頒行,將儒略曆 1582 年 10 月 4 日星期四的隔天,訂為格里高利曆 1582 年 10 月 15 日星期五。
不過各個國家改曆的時間並不相同,像英國、大英帝國(包含現今美國東部)改曆的時間是在 1752 年 9 月初,因此在 Unix/Linux 中查詢 1752 年月曆,會發現 9 月平白少了 11 天。
Java 中 Calendar
的預設實作類別 GregorianCalendar
,雖然開頭有 Gregorian 的名稱,然而實際上是儒略曆與格里高利曆的混合,同時支援了儒略曆與格里高利曆,預設的改曆時間為格里高利曆 1582 年 10 月 15 日星期五,因此如果你實行以下的運算:
Calendar calendar = Calendar.getInstance();
calendar.set(1582, Calendar.OCTOBER, 15);
out.println(calendar.getTime()); // 顯示格里高利曆 1582 年 10 月 15 日
calendar.add(Calendar.DAY_OF_MONTH, -1); // 往前一天
out.println(calendar.getTime()); // 顯示儒略曆 1582 年 10 月 4 日
使用 GregorianCalendar
對格里高利曆 1582 年 10 月 15 日作減去一天的運算,實際上會來到儒略曆 1582 年 10 月 4 日。改曆時間可以使用 GregorianCalendar
的 setGregorianChange
來修改,設為 Date(Long.MAX_VALUE)
就是純儒略曆,設為 Date(Long.MIN_VALUE)
,就是純綷的格里高利曆。
ISO8601 標準
在一些相對來說較新的時間日期 API 應用場合中,你可能會看過 ISO8601,嚴格來說 ISO8601 並非年曆系統,而是時間日期表示方法的標準,用以統一時間日期的資料交換格式,像是 yyyy-mm-ddTHH:MM:SS.SSS、yyyy-dddTHH:MM:SS.SSS、yyyy-Www-dTHH:MM:SS.SSS 之類的標準格式。
ISO8601 在資料定義上大部份與格里高利曆相同,因而有些處理時間日期資料的程式或 API,為了符合時間日期資料交換格式的標準,會採用 ISO8601。不過還是有些輕微差別。像是在 ISO8601 的定義中,19 世紀是指 1900 至 1999 年(包含該年),而格里高利曆的 19 世紀是指 1801 年至 1900 年(包含該年)。
時區
在各種時間日期的議題中,時區(Time zones)也許是最複雜的,每個地區的標準時間各不相同,因為這牽涉到地理、法律、經濟、社會甚至政治等問題。
從地理上來說,由於地球是圓的,基本上一邊白天另一邊就是夜晚,為了讓人們對時間的認知符合作息,因而設置了 UTC 偏移(offset),大致上來說,經度每 15 度是偏移一小時,考量了 UTC 偏移的時間表示上,通常會標註 Z 符號。
不過有些國家的領土橫跨的經度很大,一個國家有多個時間反而造成困擾,因而不每 15 度是偏移一小時的作法,像美國僅有四個時區,而中國、印度只採單一時區。
除了時區考量之外,有些高緯度國家,夏季、冬季日照時間差異很大,為了節省能源會儘量利用夏季日照,因而實施日光節約時間(Daylight saving time),也稱為夏季時間(Summer time),基本上就是在實施的第一天,讓白天的時間增加一小時,而結束後再調整一小時回來。
台灣也曾實施過日光節約時間,後來因為沒太大實質作用而取消,舉例來說,如果你執行以下程式碼:
TimeZone taiwan = TimeZone.getTimeZone("Asia/Taipei");
Calendar calendar = Calendar.getInstance(taiwan);
calendar.set(1975, Calendar.MARCH, 31, 0, 0, 0);
out.println(calendar.getTime());
calendar.add(Calendar.DAY_OF_MONTH, 1);
out.println(calendar.getTime());
設置的時間為台灣曾實施日光節約的時間,從執行結果可以看到,1975 年 3 月 31 日 0 時加了一天,卻變成了 1975 年 4 月 1 日 1 時,這就是要大家少睡一小時,天亮了就早點起床幹活的意思 … XD
既然時區會牽涉到地理、法律、經濟、社會甚至政治等問題,這也就表示隨著時間的推進,不同時區的定義就得修正,像是某個國家後來決定取消日光節約時間之類的,像 JDK 的時區資訊,會隨著不同版本的 JDK 出貨而更新,你也可以透過 Timezone Updater Tool 來進行更新。
呼 … 好不容易講完這些基本的時間日期資訊了,如果你得認真面對時間日期處理,認識這些就是必要的,至少現在你應該知道,一年的毫秒數絕對不是單純的 (365 * 24 * 60 * 60 * 1000)
…