2016年6月19日 星期日

在 Java 中解析、表達與計算日期時間(一):時間系統與曆法

對大多數人(包括我)來說,大概都不會覺得日期時間有什麼特別的
在參加之前的 Java TWO 以前,其實我也沒有認真看待過這個東西,一直到在 Java TWO 上聽到良葛格談到這個議題
才知道~其實背後隱藏了許多魔鬼,導致這個議題事實上是個相當複雜的問題。

在開始研究如何正確地寫日期與時間相關的程式之前,先了解一下何謂時間、以及時間和曆法的關係為何。

    時間與曆法

    PS. 這段是我個人的理解與看法,不是標準的時間與曆法的定義。只是我個人以這種形式去理解它們而已。

    在討論如何計算時間之前,必須先思考,什麼是時間?
    時間是一個不會停止流逝的尺度,時間本身並沒有特定的表達方法或者意涵
    因為它就只是一個像河流一樣,一直流過去的東西而已。
    然而,人們在溝通中需要能夠表達過去、現在及未來
    因此必須透過某個規則去定義、表示任意一個時間流上的瞬間
    在古時一直流傳至今的表達方式,就是曆法。

    在過去,「時間」就等於是「曆法」,都是以自然界的特定物體來定義某個時間的瞬間
    例如陽曆、陰曆 [1],各自是以太陽和月亮的位置來定義時間。
    其中「陽曆」表示的是一年基本跟地球繞太陽的週期一致;「陰曆」則表示一個月基本跟月亮繞行地球的週期一致
    「陰陽曆」則是同時具有陽曆與陰曆的特徵。

    不過時至今日,「時間」與「曆法」開始變得不同了,因為曆法雖然沒變,但時間的定義改變了。

    時間系統:GMT、UTC 和 Unix 時間

    GMT(格林威治標準時間,Greenwich Mean Time)和 UTC(世界協調時間,Coordinated Universal Time)
    這兩者在現代是常看到的兩種時間系統。這兩種時間系統,都是用來定義「時間」。
    兩者的共同之處,就是兩者都遵循著古老的方法,都沒有自己定義自己的時間表達方法
    而是直接以曆法作為時間的表達方法。
    換言之,GMT 與 UTC 雖然以各自的方式去定義 1 秒的長度,但在呈現時,依然會以公曆的表達方式呈現。

    至於 GMT 和 UTC 在電腦科學中,差別是什麼呢?
    先講結論,簡單來說 GMT 是舊的時間系統,UTC 比 GMT 更精準
    但 GMT 系統要表達時間時,因為規則跟公曆一致,因此可以毫無疑義地轉換
    UTC 則因為定義上的問題,以曆法來表達時需要適時修正。

    至於 Unix 時間,則是基於 UTC 時間系統發展的另一種時間系統。
    這個系統的特徵是,它跟 GMT 和 UTC 不同,它自己定義了自己的時間表達方法
    因此在 Unix 時間之下,可以很明確地定義出「時間」與「曆法」的差別。
    因為 Unix 時間就是一大串數字,這串數字就表示時間流上的某一個瞬間
    而這個瞬間對應的是公曆上的何時,就可以透過轉換來達成。

    GMT(格林威治標準時間,Greenwich Mean Time)

    GMT 是小時候就有教過的格林威治標準時間 [2](又稱為世界時 [3])
    是以太陽經過格林威治子午線作為正午,並每隔一個小時將時間發佈給全世界。
    這個定義跟目前習以為常的公曆 [4] 的定義基本上是一致的
    因為 GMT 跟公曆都是以太陽位置為基礎的定義。

    不過依照太陽的位置去判斷時間的方式存在一些問題:地球實際上是橢圓形的,而且地球自轉的速度正在減慢
    這導致了在這樣的定義底下,「1 秒」的長度其實是不固定的
    例如今天的 1 秒,跟 100 年前的 1 秒,長度是不一樣的。
    這樣其實對人們很不方便,基本上人們比較習慣認定每個 1 秒應該都是一樣長的
    因此後來改用了更精準的時間的決定方法,也就是 UTC 時間。

    UTC(世界協調時間,Coordinated Universal Time)

    UTC 時間中,一秒的長度定義如下:(節錄自 Wikipedia [6])

    不受外場干擾的銫-133的原子基態的兩個超精細結構能階間躍遷所對應的輻射的9,192,631,770個周期的持續時間
    在這個定義底下,一秒的長度已經跟地球的自轉問題脫勾,成為到目前為止是最精準表達時間的方法。
    在這個時間系統底下,秒和毫秒等的時間長度都會成為固定值。

    到這裡感覺 UTC 好像很美好不是嗎?但世界並不是這麼美妙的,因為時間的表示還必須對應回曆法
    當前公認的曆法(格里曆 [4])定義一天 24 小時、一小時 60 分鐘、….、一年定義為 365 天
    其中一年的定義,是依照「太陽回歸週期」去定義的
    太陽回歸週期,指的是太陽轉一圈後回來直射到某個點需要花費的時間
    也就是說,「公曆」這個曆法依然是跟地球自轉和公轉有關的。
    但 UTC 時間的定義,已經不再跟地球、太陽的位置有關聯了
    這也就表示,當使用 UTC 去表達曆法上的某個瞬間時,「時間」跟「日期」的對應會慢慢錯開
    亦即,以 UTC 去表示公曆上某一天的某一個瞬間,跟公曆本來認定該天的該瞬間(或者說 GMT 表達的同一個時間瞬間)
    實際上是兩個不同的時間瞬間。

    「國際地球自轉服務組織(IERS)」後來為了解決改制 UTC 後產生的這個問題,通過了閏秒 [5, 8] 的決定
    即根據地球自轉的狀況動態決定插入閏秒。
    當需要時,IERS 將在至少 6 個月前會通知未來某個時候會插入閏秒,藉以抵銷 UTC 與 GMT 的時間差距。
    而這個決定同時也代表著,實際上是不可能正確預測太久以後的精確時間
    因為 IERS 只約定了最少 6 個月前通知,意味著最短的狀況,6 個月後的時間有可能會跟現在相差 1 秒。
    更具體一點來說,例如我想預測一年後的現在的時間,不一定會是 365 * 24 * 60 * 60 * 1000
    因為它有可能會是 (365 * 24 * 60 * 60 + 1) * 1000 或者 (365 * 24 * 60 * 60 – 1) * 1000,端看 IERS 有沒有公佈需要插入閏秒。
    換言之,在 UTC 的時間系統之下,雖然秒和毫秒的長度是固定的,但分鐘和大於分鐘的單位長度是不固定的
    因為一分鐘有可能是 60 秒、61 秒或者 59 秒。

    註:UTC 的全文是 Coordinated Universal Time,縮寫為何是 UTC 而不是 CUT 呢?
    這個問題可以參考 Wikipedia [5] ,也是因為政治上的考量。
    因為不同語言表示同一個句子的縮寫是不同的,但國際電信聯盟希望不同語言都以相同縮寫來表示這個時制
    因此最後在遵循世界時(UT0、UT1 等)的規則下,向英、法提出的建議妥協,採用了 UTC 這個縮寫。

    Unix 時間

    Unix 時間(又稱 Epoch Time)[9-10] 是自 UTC 時間的 1970 年 1 月 1 日 0 點 0 分 0 秒開始計算至現在的總秒數
    根據 Wikipedia 對於 Unix 時間的定義,Unix 時間是沒有閏秒的。
    不過我自己看起來覺得,因為它還是會對應 UTC 時間去調整,不太懂為什麼定義是「沒有閏秒」….。

    總而言之,首先,在 Unix 時間的定義當中,一天永遠都是 86,400 秒。
    而根據 [10] 提到的 Unix 時間的文件定義,似乎是交由實作 Unix 時間的人自行定義。
    以 NTP 協定來說,Unix 時間是透過在尾端補上其他識別符號,來識別閏秒產生的時間重複問題。

    • UTC=2008-12-31T23:59:59.0 → gettimeofday() returns 1230940799.0
    • UTC=2008-12-31T23:59:59.5 → gettimeofday() returns 1230940799.5
    • UTC=2008-12-31T23:59:60.0 → gettimeofday() returns 1230940800.0
    • UTC=2008-12-31T23:59:60.5 → gettimeofday() returns 1230940800.000001
    • UTC=2009-01-01T00:00:00.0 → gettimeofday() returns 1230940800.000002
    • UTC=2009-01-01T00:00:00.5 → gettimeofday() returns 1230940800.5

    以上面的例子來說,本來在 2008 年 12 月 31 日有增加一個閏秒,因此 UTC 時間存存在 23 點 59 分 60 秒這個時間
    反應在 Unix 時間的話,本來會造成 1230940800 這一秒
    同時表達了 2008 年 12 月 31 日 23 點 59 分 60 秒、以及 2009 年 1 月 1 日 0 點 0 分 0 秒這兩個時間瞬間
    但透過在最後面加上了 000001 和 000002,讓系統得以區分出這兩個時間的不同。

    註:但我覺得這樣依然表示了它處理了閏秒不是嗎,而且這樣的確還是存在一天不是 86400 秒的狀況了呀 @@?

    什麼是日光節約時間?

    在台灣,因為近年都沒有實施過日光節約時間,也普遍沒有這類的困擾,因此對這個議題比較無感
    不過通常在談到日期與時間時,大多都會談到日光節約時間 [11] 這個詞。
    這個制度是代表,在實施日光節約時間的日子(通常是夏天),時間會提早一個小時。

    這麼做的好處是什麼呢?例如台灣大多數冬天時是大概早上六點天才開始亮,但夏天大概四五點就開始天亮了
    如果在夏天實施日光節約時間,即實施日光節約時間時的早上六點,實際上是未實施的早上五點
    可以讓人們提早一個小時起床、工作等,當然也會讓人們提早一個小時去睡覺
    這樣可以讓這一個小時時差產生的能源消耗減少
    因為人們的時間提早進入晚上,也就是能夠利用日光照明的時間增加一小時、而使用其他能源照明的時間減少一小時。
    因此,日光節約時間其實是一種夾帶了政治、經濟等因素的政策。

    不過事實上,日光節約時間在現代並不見得仍然具備節約能源的效果
    同時它帶來了一些額外的困擾,包括日光節約時間開始和結束時,會有某個時間不見了或多出現一次等等。
    詳情可以參考 [11]。

    時區

    在有了時間與曆法之後,為什麼要有時區 [13]呢?
    就我的認知來說,基本上是因為國際化了的關係。
    在過去,各個地區和國家還沒有開始交流與溝通之前,各國自己是以自己的方式去定義時間的
    每個國家的人民也都對此習以為常
    但當不同地區開始串連起來(像是長途鐵路、甚至航海到其他地區),就會開始需要有個統一的時間定義
    因此造就了「時區」的產生。

    不過時區本身也跟日光節約時間一樣,是具有政治背景的東西
    因為一個很大的國家裡,大家的時間都是不同的,其實某種程度上也會造成一些困擾
    因此不同國家對於時區的定義是不同的,例如美國橫跨了四個時區,但四個時區並非依照經度線去切割的
    而中國與印度則是整個國家全都屬於同一個時區。

    相關資源

    下篇:在 Java 中解析、表達與計算日期時間(二):JSR-310 概述

    參考資料
    1. Wikipedia:陰陽曆
    2. Wikipedia:格林威治標準時間
    3. 世界時(UT)及格林尼治平時(GMT)
    4. Wikipedia:格里曆
    5. Wikipedia:世界協調時間
    6. Wikipedia:銫-133
    7. 到底是 GMT+8 還是 UTC+8 ?
    8. Wikipedia:閏秒
    9. Wikipedia:Unix 時間
    10. The Unix leap second mess
    11. Wikipedia:日光節約時間
    12. 【Joda-Time 與 JSR310 】(2)時間的 ABC
    13. Wikipedia:時區

    沒有留言: