August 5, 2014
Javascript 處理 Timezone
前陣子處理了一些 timezone 的問題,現在順手紀錄下來,不然我這顆容量只有幾十 mb 的大腦很快就會忘光了。疏誤之處還請鄉親不吝指導結論
簡單來說,除非是夠新的瀏覽器,否則 無法 純粹用計算 timezone offset 的方式,在 javascript 裡面正確處理時區的問題,除非你透過 moment-timezone.js 這些已經正確處理完 timezone 的 3rd party library。原因在於 timezone offset 會隨著日光節約時間變換名詞解釋
雖然大家可能都知道了,還是寫一點名詞解釋,看起來似乎比較認真UTC
世界協調時間/世界標準時間,英文「Coordinated Universal Time」/法文「Temps Universel Cordonné」,也許跟民族自尊這種無法退讓的問題有關,因此折衷簡寫為 UTC。這是透過原子鐘計算出來的時間,全世界看到的都應該一樣。再藉由 Z (Zulu) 來做調整,好比
Local time
如果 UTC 的時間是 02:03,對於設定 UTC+8 的人來說, local time 是 10:03;對於設定 UTC+9 的人來說, local time 是 11:03Local offset
各地區透過 Zulu 來得到這個 offset,目的是希望該區的正午十二點恰好是日當正午。(其實我個人還滿討厭這件事情,既然「12:00 等於中午」是人工教育出來的,幹嘛還把全世界的時間搞得那麼複雜。反正我們能不能星際旅行還不知道,在不考慮相對論的前提下,全球用同一個時間有多好)(阿宅的抱怨)
Daylight Saving Time
問題的元兇,日光節約時間,又稱夏時制。尤其在高緯度地區,夏天的日出時間跟冬天有不少落差,為了鼓勵大家多當晨型人吸收多點紫外線,用非常人工的方式強制在某幾個月內撥快時鐘。(其實我很想要收集七顆龍珠,向神龍許願回到過去把提出這個概念的人毒打一頓)
(直接許願日光節約時間消失不是更好?)
(好像乾脆許願 IE 全部消失最好)
問題
其實問題可以用很簡單的例子來演示,因為我是臺灣人,假設我人在台北,打開瀏覽器裡面的時區就是 Asia/Taipei +08:00- 請問你看文章的當下,日本時間是什麼?
- 請問你看文章的當下,紐約時間是什麼?
- 請問你的時間是 2014/04/01 13:00:00,當時日本的時間是什麼?
- 請問你的時間是 2014/12/01 13:00:00,當時日本的時間是什麼?
- 請問你的時間是 2014/04/01 13:00:00,當時紐約的時間是什麼?
- 請問你的時間是 2014/12/01 13:00:00,當時紐約的時間是什麼?
因為那張表是浮動而非固定的。原因就是討厭的日光節約時間
對臺灣人來說日光節約時間可能很陌生而得到,「這種鬼東西可以不用鳥它啦」的結論,我一開始也是這麼想。可是一查才知道,美國紐約在 2014/03/09 就有實施日光節約時間,持續到 2014/11/2。
2014 年耶!紐約耶!
如果是火奴魯魯或是南極洲這種
理解日光節約時間的行為,可以看這個 Time changes for USA
在實施日光節約時間以前,現在時間是紐約的凌晨 01:59:58
五秒過後,現在時間變成了 03:00:03,中間有一個小時不見了!
用 Linux 的宅友,可以用指令 tzselect 找出某地區的 timezone 字串,然後塞入變數 TZ 來執行 chrome
$ TZ='America/New_York' chrome
這樣就能偽裝成你去美國紐約玩,沒錢出國的時候不失為一個望梅止渴的旅行法。此時從 console 測試可以看見這些結果
new Date(Date.UTC(2014, 2, 9, 06, 01, 0, 0)).toString()
"Sun Mar 09 2014 01:01:00 GMT-0500 (EST)"
new Date(Date.UTC(2014, 2, 9, 06, 59, 0, 0)).toString()
"Sun Mar 09 2014 01:59:00 GMT-0500 (EST)"
new Date(Date.UTC(2014, 2, 9, 07, 00, 0, 0)).toString()
"Sun Mar 09 2014 03:00:00 GMT-0400 (EDT)"
直接跳到三點了!,無法顯示 02:00:00 ~02:59:59
同一年在 11/02 的時候紐約結束實施日光節約時間,因此那一天會有同一時間出現兩次的奇景
new Date(Date.UTC(2014, 10, 2, 04, 1, 0, 0)).toString()
"Sun Nov 02 2014 00:01:00 GMT-0400 (EDT)"
new Date(Date.UTC(2014, 10, 2, 05, 1, 0, 0)).toString()
"Sun Nov 02 2014 01:01:00 GMT-0400 (EDT)"
new Date(Date.UTC(2014, 10, 2, 06, 1, 0, 0)).toString()
"Sun Nov 02 2014 01:01:00 GMT-0500 (EST)"
雖說 01:01 會出現兩次,細看便知一個在 EDT(Estern Daylight Time) 一個在 EST (Estern Standard Time)。一個在 -0400 一個在 -0500
這就是問題所在。要把某一筆時間顯示為某區的 local time 時,你如果不知道該地區當時是否有實施日光節約時間,所以你不會知道 offset 該是多少,也因此無法計算。
而日光節約時間,比我一開始想像還要常見得多,因此無法假裝不存在而不處理。台灣也有實施過日光節約時間,鴨七有相當詳細的台灣日光節約時間之考據
解法
後來 Date object 有一個 method Date.toLocaleString,可以指定 timezone。不過捲到相容性那邊,看見要 IE11 才開始支援,希望我兒子開始學寫程式的時候已經可以放心使用這個 method最簡單的就是拿 moment.js 來用,加上 moment-timezone.js 之後就擁有處理時區的能力。即便如此,還是要下載一大包的 data,內含各地區的日光節約時間資料,這三者加在一起才能正常運作。當然也不用每個地區都抓,你的產品有支援的地區包進去就好了。即便如此我還是隨隨便便就超過 100K 了 (淚)
Subscribe to:
Post Comments (Atom)



No comments:
Post a Comment