2013/5/26

JavaScript 中的日期比較問題

在我們在處理 JSON 的日期轉換時, 總會遇到轉得過去, 卻轉不回來的問題。這時候, 我們必須另外把日期字串進行重新格式化的動作。這個問題可以在 Stack Overflow 網站上找到現成的解答,  就是使用一個 dateReviver 函數, 當作 JSON.parse 方法的第二個參數。

後來, 我發現黑暗執行緒在他的「JavaScript端的JSON日期轉換」一文中也採用了相同的解法。而且他在文章裡己做過很詳細的解釋; 所以我就不在這裡針對這個日期轉換的主題做重複說明了。

然而, 這個解法雖然可以解決日期轉換的問題, 但是我發現, 如果我們把一個日轉換過去, 再轉換回來, 兩個日期卻不相等! 這實在是一個很詭譎的問題。

在我的實驗中, 假設原來的日期 (使用 new Date() 函數) 是 "Sun May 26 2013 01:44:00 GMT+0800 (台北標準時間)", 那麼, 使用 JSON.stringify() 函數將這個日期轉換成字串, 然後再將這個字串以 JSON.parse() 套上 dateReviver 函數再轉換回來, 那麼, 明明這個日期同樣是 "Sun May 26 2013 01:44:00 GMT+0800 (台北標準時間)", 但是, 如果你將這兩個日期做 == 或者 === 比較, 二者就是不相等。

後來, 我試著把這兩個日期以 getTime() 函數求取其數字格式, 發現二者竟然是不相等的! 這時候, 我才發現原來函數中的 dateReviver() 函數並沒有把毫秒數加上去。換句話說, 經過 dateReviver() 轉換過的日期, 是沒有毫秒的, 但是原來的日期有毫秒。所以, 這兩個日期有千分之九百九十九的機率是不可能相等的。

知道問題何在, 就好辦了! 我把原來的 dateReviver 函數修改了一下, 讓它變成如下:

function DateReviver (key, value) {
    if (typeof value === 'string') {
        var a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.(\d*))?)Z$/.exec(value);
        if (a) return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6], +a[7]));
    }
    return value;
}

我把我的完整的測試程式放在 jsFiddle (如下), 有興趣的朋友可以自己操作測試:

如果你把註解行取消, 就會使用我修改過的 DateReviver() 函數, 反之則是使用原來的 dateReviver() 函數。你可以比較一下差異之處。

在程式中, 你可以發現, 如果我們要比較兩個日期, 假設 dt 和 dtParsed 兩個值都指向同一個時間, 但是不管 (dt == dtParsed) 或者 (dt === dtParsed), 都會傳回 false。為什麼? 這是因為在 JavaScript 中日期型別其實是 object 型別, 而 object 型別是參考型別; 它們必須要指向同一個記憶體位址, 才算相等; 即使指向的值本身一樣, 它們仍然是不相等的。

因此, 如果你要比較兩個日期是否相等, 直接拿兩個日期變數來做比較是沒有意義的; 就算二者日期和時間確實一模一樣, 它們也絕對不可能相等。你必須像我在程式中所寫的, 套上 getTime() 函數之後再去做比較, 這才是正確而有意義的做法。

沒有留言:

張貼留言