前端工程師們,在拿到一個日期/時間資料的時候,你是怎麼處理的呢?有沒有考慮時區的問題呢?
也許你會說:嗯,沒怎麼關注時區,不過我測過,沒問題。
我能怎麼說呢?可能你還沒遇到國外的使用者吧!
被偷走的一天
如果拿到一個日期字串 "2021-11-17"
,為了方便計算和處理資料,可能會先把它封成一個 Date
物件:
const s = "2021-11-17";
const d = new Date(s);
現在使用 d.getDate()
看會得到什麼?—— 是 17
,沒毛病!
其實 —— 怎麼說呢 —— 這是因為你在中國。如果這段程式在瀏覽器執行,而瀏覽器正好位於加拿大,還會是 17
嗎?不妨試試。
在 Windows 下進入時間設定,把時間改為“太平洋時間(美國和加拿大)”或者其他 UTC-XX:00
的時區。再到瀏覽器裡去看看,會發現 d.getDate()
得到了 16
!
如果是在 Linux 下,也可以重新設定系統時間,然後用 Node 控制檯來檢視,如下圖:
為什麼少了一天?
瀏覽器始終使用本地時間
要問為什麼少一天,不妨看看 d
到底是個什麼樣的資料(記得先把時區改回來哦!)
d.toString(); // Wed Nov 17 2021 08:00:00 GMT+0800 (中國標準時間)
d.toLocaleString(); // 2021/11/17 上午8:00:00
你看,new Date("2021-11-17")
建立日期物件的時候,是把 2021-11-17
當作 UTC 00:00:00
來建立的物件。而這個時間在中國 ,就是 2021-11-17 08:00:00
。同理,如果是在 -06:00
的加拿大中部,它會是 2021-11-16 18:00:00
,這時候 .getDate()
,當然會得到 16
。
在處理時間這個問題上,瀏覽器處理得簡單粗暴,就是根據系統的時區設定來按照本地時間進行處理。如果我們給的是一個精確的時間,瀏覽器這樣處理沒毛病。畢竟時間是世界的,在這個時間點上,中國是早晨,加拿大是黃昏 —— 問題在於,Date
物件不能只描述日期,還要描述時間,它自動按 UTC 補了時間資料之後,我們就可能得到非預期的結果。
帶時間部分的字串
那麼,如果拿到的表示時間的字串帶時間的呢?是不是會準確一點 …… 試試吧:
// 為了方便檢視,註釋裡的結果資料採用了簡潔的描述
new Date("2021-11-17"); // 2021-11-17 08:00 +08:00
new Date("2021-11-17 00:00:00"); // 2021-11-17 00:00 +08:00
new Date("2021-11-17 09:00:00"); // 2021-11-17 09:00 +08:000
注意第 2 條。在我們的常識中,2021-11-17 00:00:00
和 2021-11-17
應該是同一個時間吧?後者不帶時間部分,所以預設它是一天的開始,也就是 00:00:00
—— 問題就在這裡,Date
物件預設它是 UTC 的 0 時,而不是本地時間的 0 時。但如果給了時間,Date
會把它按本地時間來處理 —— 莫名其妙地就出現了時差。
該如何做到無差別
因為 new Date()
的時候是按本地時間來解析的,所以在不同地區的瀏覽器裡,同一個表示日期時間的字串會被解析成不同的時間 —— 你看,中國的早晨 9 點和加拿大的早晨 9 點絕對不是同一個時間對吧!
如果我們希望 new Date()
拿到的是同一個時間怎麼辦?
說起來也挺簡單的,只要拿到字串包含時區資訊就行了,比如符合 RFC3339 標準的時間表示 2021-11-17T09:00:00+08:00
。RFC3339 這裡就不詳述了,只需要簡單地理解為
- 日期部分是
yyyy-MM-dd
格式 - 時間部分是
HH:mm:ss
或HH:mm:ss.sss
格式 - 日期和時間之間使用
T
連線 時區部分是
Z
或者±HH:mm
格式,其中Z
表示無時差,即 UTC 時間。比如下面兩個時間是在同一個時間點上2021-11-17T09:00:00+08:00
2021-11-17T01:00:00Z
很幸運 Date
能識別符合 RFC3339 標準的時間描述,所以用上例兩個字串建立的時間是相同的
const d1 = new Date("2021-11-17T09:00:00+08:00");
const d2 = new Date("2021-11-17T01:00:00Z");
console.log(d1.getTime() === d2.getTime());
還要注意什麼
道理已經講明白了,但是實際操作中還是會出現一些奇奇怪怪的問題。而出現這些問題的原因,歸根結底還是沒在注意到“時區”。比如,我問幾個問題,看能不能搞得明白?
- 拿到簡單表示日期/時間的字串,不符合 RFC3339 標準,比如前面提到的
"2021-11-17 09:00:00"
。請問他是 UTC 時間還是本地時間? - 如果是本地時間 —— 它是應用伺服器的本地時間?還是資料庫伺服器的本地時間?還是錄入時間的使用者所在時區的本地時間……?
你看,簡單的一個時區問題,並不只是前端單方面的問題,它可能是整個系統設計的時候就因為忽略了時區概念而造成的問題。
如果在系統設計的時候就把時區問題考慮進去,所有儲存和傳輸都使用相同時區的時間描述,事件就會變得好辦得多。當然,如果直接使用 UTC 時間,或者 Unix Time Stamp 之類精確的描述會更理想。
也許有人覺得,瀏覽器確實只支援本地時間,但是我們可以用 Moment.js 庫,可以用 Day.js 庫,可以用 date-fns 庫 …… 是的,你可以用各種各樣的工具,但是還是得搞明白拿到的時間描述是準確無歧義的。再強調一次,準確的時間描述有:
- 帶時間資訊的字串,比如符合 RFC3339 標準的字串描述;
- 基於某個特定時間的秒級、毫秒級或更細微級別的時間偏移值,比如 Unix Time Stamp。
我們的程式生在中國,但要面向世界 —— 你考慮時區了嗎?