The Art of Readable Code && High Performance Comments
Timezones

Timezones

時距

時距 (Timespan) 定義為兩個時間的差值;任一時區與 UTC協調世界時)的的差值,則定義為偏移 (Offset)。

時間

時區

地球上不同經度區域的時間有所差異,因此劃分有 24 個理論時區,且相鄰時區的(當地)時間相差一小時。然而受到國家統治影響,實際(法定)時區分隔線會依照國界、行政分界線區隔調整,隨著疆界與曆法的變動影響,且差距未必為 1 小時的整倍數。

在表示理論或法定時區時,以 UTC 作為基準,並紀錄該時區的偏移值。例如台灣使用的中原標準時間 (CST) 可記為 UTC +8:00。

時間修正

在各種太陽曆中,地球自轉的與公轉的時間還是會有偏差;古代往往要等誤差累積到數十日,才能藉由星象位置發覺並修正。現代人靠著天文與計時技術的進展,已經能夠極精確的測量;然而目前「秒」的定義並非基於太陽日,並且地球自轉速度會因各種因素變動,因此還是要透過閏秒修正兩者間的偏移。

閏秒是在 UTC 6/30 或 12/31 的最後一秒進行調整,若為正閏秒,則在當日 23:59:60 之後,才進入隔日 00:00:00;若為負閏秒,則在 23:59:58 之後進入隔日。

夏時制

除了時區以外,有些地區會使用 DST (夏時制、夏令時間、日光節約時間):在一年中的某個時段(包含夏天)將本地時間撥快(常見為一小時,下文以此為例)。在這段期間內,地區的本地時間會與法定時區相異。因此本地時間在進入 DST 的時候,會有一個小時被跳過;結束 DST 的時候,要重複經歷同一個小時兩次。

電腦時間

Unix Time

電腦有好幾種紀錄時間的方式,最簡單是 Unix time (又稱 POSIX time, Unix timestamp)。Unix time 將 1970/1/1 00:00:00 UTC 定義為 Epoch time,第 0 秒,並計算特定時間與 Epoch time 以秒為單位的時距。Unix time 不使用 DST,因此避免了 DST 結束時,會有一個小時出現兩次的問題。

然而 unix time 忽略閏秒修正,因此正閏秒後,電腦時間會比 UTC 快一秒;若未妥善重校時,並處理因教時帶來時間重複的問題,軟體系統可能會出錯。這又被稱作閏秒蟲。

閏秒蟲的 dirty hack 有幾種,但都很麻煩,因此有些人醞釀取消閏秒制度;這目前仍屬未定。

Timestamp

Timestamp 則比較混亂:它有時作為 unix time 的同義詞,或寫作 unix timestamp;有時被當作 localtime 的一部分(後述);而在另外一些系統裡,它表示的是「參照用戶時區的 unix time」,而字串型態顯示。大部分資料庫系統屬於後者,並且有參數可以指定是否依照連線用戶時區調整。

mysql 裡,timestamp 預設以 local time 格式的字串(連線時區)存取,並以 unix time 儲存;在某些資料庫(如 Oracle、PostgreSQL)中,可以透過參數改變「參照連線時區」的行為。下面是 mysql 的測試範例:

大部分系統以數值型態(整數或浮點數)紀錄 unix time / timestamp;若為浮點數,代表它的精度達到秒以下。

Local time

用於儲存當地時間,稱作 Local time。部分情況下,local time 是以文字方式表述,並且有多種格式標準,例如 ISO 8601 表示為 “2012-10-30 16:48:28Z”,其中 Z 代表 UTC (Zulu, UTC+0) 時區。如果表示格式中缺少時區資訊,則轉換出的 unix time 會參照伺服器或該連線的時區,因而未必正確。

在某些情況下,例如 PHPDateTime 物件,是以 unix time (or timestamp) 加上地區的方式,來儲存 Local time。由於 local time 不具有唯一性,這樣的儲存方式可以保留精確度,在轉換時區時仍然正確。

台北時區資訊,可透過 MySQL 取得

台北時區資訊

地區與時區

為了簡便計算 local time,常以地區取代時區進行運算。例如台灣地區名稱可表示為 Asia/Taipei ,因此系統可由時區資訊得知該時區的 offset 為何,何時改變(因為 DST 等因素)。在電腦計時中,這個地區常以 timezone 表示,又或者整合該地區的語言、金錢、數字、符號表示後,合稱為地區資訊(locale, culture)。

其他問題

除了日光節約時間、閏秒、領土變更以外,曆法變動對於日期計算的影響也是不可忽視。目前電腦曆法雖普遍以「公曆」為準,但各國採用時間在 1582 至 1926 年間不等;在公曆之前,這些地區普遍採行儒略曆,兩者之間有著約 10 天的差異。

普遍來說,在描述這類日期時,應加註採用曆法;但電腦紀年做不到,因此當軟體系統需紀錄這段期間的日期時,需格外注意。

最佳實作

  • 服務設計:
    • 對於限定區域的服務(伺服器與用戶位在同一個地區,資料生命週期不離開這個地區,並且忽略其他地區用戶),可以採用 localtime
    • 其他狀況下,伺服器應設定為採用 UTC 時區,僅在必要時轉換時區
  • 資料庫與程式:
    • 使用 unix time 簡單易懂,但某些實作有表述期間的限制(例如 int32 上限為2038/01/19,uint32 則為 2106/02/07);使用基於 UTC 的 localtime 是種替代方案,但要注意格式與轉換成本
    • 為避免混淆,轉換前的欄位或變數名稱可以加註 utc
    • 對時間進行運算時(增減時距、調整時區、改變格式),可能造成資訊遺失。例如 mysql 的 FROM_UNIXTIME() 不接受負參數;又或者轉換為本地時間時,使用未加註時區縮寫的格式(時區縮寫包含地區,並隱含 DST,但 Offset 丟失上述資訊,但仍保持轉換回 UTC 的精度)。
    • 各資料庫系統對時間的表述方式未必相同;在不適用預設值的場合,由程式端轉換成 unix time 是個好主意
  • 其他狀況:
    • 處理舊日期 (192x 年以前) 的時候,注意函式庫是否處理曆法變動的日期轉換,以及目標地區是否改換曆法。這些日期最好以字串儲存,加註使用曆法,避免解析運算這些日期。
時區偏移分佈,透過 MySQL 取得

時區偏移分佈

For Fun

  • 在時區表中,有六個地區發生過重大時區 ( >= 10 hr) 變動;其中五個地區接近 24hr,代表跨越國際換日線;而南極洲的 Antarctica/DumontDUrville 則在 1952-01-13 至 1956-11-01 短暫的採用 UTC,其他時間則是 +10。

發表迴響

分類

%d 位部落客按了讚: