new Date()?沒那麼簡單!!

相信不少人都覺得Date對象很簡單,不就是new一下,然後調用下原型方法嘛,so easy啦。恰巧我也是其中一員,平時上手即用,沒怎麼關注過這一塊,直到最近一個小需求點把自己坑了,才醒悟過來,new Date()沒那麼簡單。哎,血淚史,任重而道遠。

問題的表現

請先思考一下,下面代碼的輸出分別是什麼?

new Date('2020-06-05')

new Date('2020/06/05')

new Date('2020-6-5')

我之前一直認爲,輸出的都是Fri Jun 05 2020 00:00:00 GMT+0800 (中國標準時間),然而實際並不如我想。
在這裏插入圖片描述
這正是我踩坑的地方,直接使用Date構造函數解析日期字符串,然後與指定日期比大小,像下面的方式:

// 比如當前時間爲2020年6月5日凌晨0點到早上8點之前
new Date('2020-06-05').getTime() < new Date().getTime() // 期望返回true,實際返回false

定位到問題後,遂查閱了相關資料,才發現Date()真沒我想的這麼簡單。

問題的本質

ECMA262-RFC中的Date Objects詳細規定了如何利用Date構造函數創建Date對象實例,因爲本文使用的new Date(value),所以重點關注這一方式,其他方式在拓展部分會稍微講一講。

new Date(value)

  1. 生成一個新對象,對象的原型繼承Date構造函數的原型對象;
  2. 新對象內部的[[class]]屬性設置爲Date
  3. 新對象內部的[[Extensible]]屬性設置爲true
  4. 新對象內部的[[PrimitiveValue]]屬性按照下面方式設置:
    • 調用ToPrimitivevalue轉成基本數據;
    • 如果基本數據是String類型,在行爲上和Date.parse方法相同(下面講);
    • 如果是其他類型則轉換成Number,然後調用TimeClip方法,該方法是數字轉換成合法的 Unix 時間戳,否則返回NaN

Unix 時間戳(Unix Time Stamp),是一個整數值,表示自1970年1月1日00:00:00 UTC 以來的毫秒數,忽略了閏秒。

在看一看Date.parse方法。

Date.parse(dateString)

這個方法作用是解析一個表示日期的字符串,並返回從 1970-01-01 00:00:00 UTC 所經過的毫秒數。但在實現過程中,每個瀏覽器不太一樣。因爲RFC中有這樣一段比較模糊的描述:

The function first attempts to parse the format of the String according to the rules called out in Date Time String Format (15.9.1.15). If the String does not conform to that format the function may fall back to any implementation-specific heuristics or implementation-specific date formats.

意思是如果日期字符串不符合ISO 8601標準,可以使用具體實現(比如瀏覽器)提供的非標準日期格式。但實際上各個實現提供的非標準日期格式不盡相同,從而導致不兼容。

針對瀏覽器上的實現,MDN也特別給出提示:

由於瀏覽器之間的差異與不一致性,強烈不推薦使用Date構造函數來解析日期字符串 (或使用與其等價的Date.parse)。對 RFC 2822 格式的日期僅有約定俗稱的支持。 對 ISO 8601 格式的支持中,形如"1970-01-01"僅有日期的字符串的會被處理爲 UTC 而不是本地時間。

Date.parse()的例子中也額外指出了這一問題。

所以鍋在於我使用的日期字符串格式爲"2020-06-05"

解決辦法

'-'替換成'/'

const getDate = (dateString) => {
  // '-'分隔符全部轉換成'/'
  dateString = dateString.replace(/-/g, '/')
  return new Date(dateString)
}

拓展

  1. new Date()

創建一個實例化時刻的日期和時間的Date對象。

  1. 生成一個新對象,對象的原型繼承Date構造函數的原型對象;
  2. 新對象內部的[[class]]屬性設置爲Date
  3. 新對象內部的[[Extensible]]屬性設置爲true
  4. 新對象內部的[[PrimitiveValue]]屬性設置爲當前時間對應的UTC(世界協調時)。
  1. new Date(year, month [, date [, hours [, minutes [,seconds [, ms ]]]]])

前三步與new Date()一樣,第四步不一樣,主要是轉換參數爲Number類型或設置默認值,最終調用TimeClip方法。這些參數的含義如下:

描述 是否必選
year 表示年份的整數值。 0到99會被映射至1900年至1999年,其它值代表實際年份。
month 表示月份的整數值,從 0(表示1月份)到 11(表示12月份)。
date 表示一個月中的第幾天的整數值,從1開始。默認值爲1。
hours 表示一天中的小時數的整數值 (24小時制)。默認值爲0(凌晨)。
minutes 表示一個完整時間(如 01:10:00)中的分鐘部分的整數值。默認值爲0。
seconds 表示一個完整時間(如 01:10:00)中的秒部分的整數值。默認值爲0。
ms 表示一個完整時間的毫秒部分的整數值。默認值爲0。

值得注意的有兩點:

  • month是從0開始計算,0表示1月份,11表示12月份
  • 當日00:00:00UNIX時間戳等於上一日的24:00:00,例如2020年6月5日24:00:00 等同於 2020年6月6日00:00:00
  1. Date.UTC(year, month [, date [, hours [, minutes [, seconds [, ms ]]]]])

接受和構造函數最長形式的參數相同的參數,並返回從 1970-01-01 00:00:00 UTC 開始所經過的毫秒數。

雖然Date.UTC看上去和New Date的第三種方式相似,但實際上有兩點區別:

  • 前者返回的是一個Unix時間戳,而後者返回的是一個Date實例對象;
  • 前者以UTC來解析參數,而後者以本地時區解析參數;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章