近期在封裝時間選擇元件的單元測試時,為了構造出Date物件,直接使用了預設Date建構函式。自己本地開發,測試均無問題,push遠端後,某個小夥伴在本地跑測試用例時,卻無法通過,具體報錯如下:
通過截圖資訊,可以初步判斷由於Date建構函式返回了不同日期導致,抱著好奇的態度查閱個各種資料後,竟然發現一個小小的日期建構函式裡面大有文章,平時自己寫起來都是淺嘗輒止,沒有深入瞭解過。下面將詳細介紹這個破案過程,以免各位看客後續重蹈覆轍。
問題排查
按照一貫做法,出問題後先自己本地跑了一次測試用例,沒有任何問題,初步就可以定位是開發環境問題。於是乎就看了下小夥伴nodejs版本號,版本號為6.10.0,而自己本地node版本號為10.3.0,於是在不同nodejs命令列下直接執行如下測試用例。
1 2 3 |
const defaultDate = new Date('1995-12-17T03:24:00'); console.log(defaultDate.toString()); |
執行結果,
Node 6.10.0:
1 2 3 4 |
> const defaultDate = new Date('1995-12-17T03:24:00') > console.log(defaultDate.toString()) Sun Dec 17 1995 11:24:00 GMT +0800(中國標準時間) |
Node 10.3.0:
1 2 3 4 |
const defaultDate = new Date('1995-12-17T03:24:00') undefined console.log(defaultDatae.toString()) Sun Dec 17 1995 03:24:00 GMT+0800 (中國標準時間) |
到此基本確認了該問題是由Nodejs環境導致的問題。但是為什麼會有這樣的問題呢,跟著我繼續深入探祕下Date建構函式。
深入分析
結合問題,提煉出以下小示例,以供深入分析Date建構函式:
1 2 3 4 5 6 |
var d1 = new Date("1995/12/17 00:00:00"); var d2 = new Date("1995-12-17T00:00:00"); var d3 = new Date("1995-12-17T00:00:00Z"); console.log(d1.toString()); console.log(d2.toString()); console.log(d3.toString()); |
nodejs 10.3.0執行結果:
1 2 3 4 5 6 |
> console.log(d1.toString()); Sun Dec 17 1995 00:00:00 GMT+0800 (中國標準時間) > console.log(d2.toString()); Sun Dec 17 1995 00:00:00 GMT+0800 (中國標準時間) > console.log(d3.toString()); Sun Dec 17 1995 08:00:00 GMT+0800 (中國標準時間) |
nodejs 6.10.0執行結果:
1 2 3 4 5 6 |
> console.log(d1.toString()); Sun Dec 17 1995 00:00:00 GMT+0800 (中國標準時間) > console.log(d2.toString()); Sun Dec 17 1995 08:00:00 GMT+0800 (中國標準時間) > console.log(d3.toString()); Sun Dec 17 1995 08:00:00 GMT+0800 (中國標準時間) |
為什麼在不同環境下Nodejs的解析行為不一樣呢?這就要提下JS中涉及到時間的相關規範了。
相關規範
ISO8601標準[參考5]
該標準指定了如果為指定偏移時間就預設為當前時間。
[ES5 規範][參考6]
指出瞭如果沒有指定偏移量,預設偏移量為Z。
[ES6 規範][參考7]
為了和ISO8601標準一致,又對該規範做了更改,如果時區偏移量不存在,日期時間將被解釋為本地時間。
原始碼分析
為了確認該問題是由於不同規範導致的,我們就需要看下V8原始碼裡面的實現了。 獲取不同node版本對應的v8版本號,如下圖所示:
1 2 3 4 5 6 7 |
//node 10.3.0 > process.versions.v8 '6.6.346.32-node.9' //node 6.10.0 > process.versions.v8 '5.1.281.93' |
檢視 v8 的不同版本下git提交記錄可看到在6.6版本上已經增加了對ES6規範的支援 ,實現瞭如果時區偏移量不存在,日期時間將被解釋為本地時間的效果。
問題總結
回頭看文章開頭的用的日期建構函式導致的bug,就可以解釋”1995-12-17T00:00:00″ 在低版本下輸出1995-12-17T08:00:00,而高版本下輸出1995-12-17T00:00:00的問題了。
通過上述規範和原始碼,低版本由於會加預設偏移量Z,預設就解析成0時區的時間,而我們在東八區,所以最終我們本地的時間是1995-12-17T08:00:00,高版本下由於沒有Z,預設會解析成本地時間,輸出結果最終就是1995-12-17T00:00:00。
問題解決方案就是隻需要加上時間偏移量即可,如下new Date(‘1995-12-17T03:24:00+08:00’)。
經驗教訓
由於瀏覽器的差異和不一致,強烈建議不要 使用Date建構函式解析日期字串(並且Date.parse它們是等價的)。
儘可能使用“YYYY / MM / DD”作為日期字串,或者使用年月時分秒的建構函式來構造Date物件,他們得到普遍地支援。有了這種格式,所有的時間都是本地的。
除非您知道自己在做什麼,否則請避免使用帶有連字元號的日期(”YYYY-MM-DD”),只有較新的瀏覽器支援它們。
參考
[1]https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Date/parse
[2]https://codereview.chromium.org/1229903004
[3]https://stackoverflow.com/questions/2587345/why-does-date-parse-give-incorrect-results/20463521#20463521
[4]https://stackoverflow.com/questions/19278797/inconsistant-date-parsing-with-missing-timezone/19279013#19279013
[5]https://en.wikipedia.org/wiki/ISO_8601
[6]http://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.15
[7]http://www.ecma-international.org/ecma-262/6.0/#sec-date-time-string-format