寫在前面
Date 類型平常用得不是很多,但一用到,對它的使用就感到不是很熟悉,每次都是強行百度一波,可以看出自己的基礎不是很牢。是以最近決定靜下心來好好回顧一下以前自己忽視的基礎,下面是我對 Date 類型的一些總結和看法。
UTC 和 GMT 及 中原標準時間的關系
在介紹Date類型前,我們先來了解一下 UTC 和 GMT 及 中原標準時間的關系。
- GMT 即「格林威治标準時間」(Greenwich Mean Time,簡稱G.M.T.),指位于英國倫敦郊區的皇家格林威治天文台的标準時間,因為本初子午線被定義為通過那裡的經線。然而由于地球的不規則自轉,導緻GMT時間有誤差,是以目前已不被當作标準時間使用。
- UTC 是最主要的世界時間标準,是經過平均太陽時(以格林威治時間GMT為準)、地軸運動修正後的新時标以及以「秒」為機關的國際原子時所綜合精算而成的時間。UTC 比 GMT 來得更加精準。其誤內插補點必須保持在0.9秒以内,若大于0.9秒則由位于巴黎的國際地球自轉事務中央局釋出閏秒,使 UTC 與地球自轉周期一緻。不過日常使用中,GMT 與 UTC 的功能與精确度是沒有差别的,我們在文章中提到的 GMT 時間與 UTC 時間是一樣的。
- GMT = UTC
- 因為時區的問題中原標準時間和UTC時間有這樣的關系
, 這個公式有助于我們後面了解 Date 類型為什麼在不同方法下的轉換結果不同。UTC + 8 = 中原標準時間
定義
ECMAScript 中的 Date 類型是在早期 Java 中的 java.util.Date 類基礎上建構的。為此 Date 類型使用自 UTC ( Coordinated Universal Time, 國際協調時間)1970年1月1日午夜(零時)開始經過的毫秒數來儲存日期。在使用這種資料存儲格式的條件下,Date()類型儲存的日期能夠精确到1970年1月1日之前或之後的100 000 000年。
我們可以這樣了解,我們建立的一個 Date 對象中儲存有一個 value,這個 value 的大小是從 UTC 時間1970年1月1日午夜至指定時間經過的毫秒數的大小。
這個值其實就是我們經常使用到的時間戳,需要注意的是js内的時間戳指的是指定時間到1970年1月1日00:00:00 UTC對應的毫秒數,和unix時間戳不是一個概念,後者表示秒數,差了1000倍。是以我們在轉換時經常會遇到精度丢失的問題(暫時采用這種說法)。
建立方式
要建立一個日期對象,使用 new 操作符和 Date 構造函數即可,如下所示。
var now = new Date(); // 獲得目前時間
在調用 Date 構造函數而不傳遞參數的情況下,新建立的對象自動擷取目前日期和時間。如果想要根據指定的日期和時間建立對象,必須傳入該日期的毫秒數(即從 UTC 時間1970年1月1日午夜至指定時間經過的毫秒數。)聽起來是不是有點頭大,難道我們還要自己計算好毫秒數才能建立相應的時間對象嗎?這樣豈不是太麻煩了?
Date.parse() 和 Date.UTC()
針對上面的問題ECMAScript提供了兩個方法 Date.parse() 和 Date.UTC() ,以此來簡化這一計算過程。它們會根據我們傳入的參數來自動計算出毫秒數的大小。下面我們來分别介紹一下這兩個方法。
Date.parse()
Date.parse()方法接受一個表示日期的字元串參數,然後嘗試根據這個字元串傳回相應的毫秒數,如果傳入的字元串不能表示将日期則傳回NaN。因為ECMA-262沒有定義這個方法應該支援那種日期格式,是以這個方法的行為通常是因地區而異。例如将地區設定為美國的浏覽器通常都接受下列日期格式:
- “月/日/年”,如6/13/2004;
- “英文月名日,年”,如January12,2004;
- “英文星期幾 英文月名 日 年 時:分:秒 時區”,如Tue May 25 2004 00:00:00 GMT-0700。
- ISO 8601擴充格式YYYY-MM-DDTHH:mm:ss.sssZ(例如2004-05-25T00:00:00)。隻有相容ECMAScript 5的實作支援這種格式。
例如,要為2004年5月25日建立一個日期對象,可以使用下面的代碼:
var someDate = new Date(Date.parse("May 25, 2004"));
Date.UTC()
Date.UTC()方法同樣也傳回表示日期的毫秒數。但它需要的參數不是字元串,它的參數分别是年份,基于0的月份(0到11),日(1到31),小時(0到23),分鐘,秒以及毫秒數。這些參數裡邊隻有前兩個參數是必需的,如果沒有提供日值,則預設日值為1,其餘參數未指定則預設為0。如下面的例子所示:
//GMT時間2000年1月1日午夜零時:
var y2k = new Date(Date.UTC(2000, 0));
//GMT時間2005年5月5日下午5:55:55:
var allFives = new Date(Date.UTC(2005, 4, 5, 17, 55, 55));
注意
- UTC 日期指的是在沒有時區偏差的情況下(将日期轉換為GMT時間)的日期值。Date.parse() 方法是基于本地時區建立的,而 Date.UTC() 方法是基于無時區偏差建立的。是以如果我們對兩個方法傳入相同的時間,我們會發現 Date.parse() 方法得到的毫秒數相對于 Date.UTC() 方法得到的毫秒數會多八個小時的毫秒數(這裡的本地時區指的是中原標準時間)。
//假設我們傳入相同的時間2018年3月18日
Date.parse("3/18/2018"); // 1521302400000
Date.UTC(2018,2,18); //1521331200000
// 1521302400000 - 1521331200000 = 28800000 = 8 x 60 x 60 x 1000
- 如果我們輸入的日期值超過了正常的範圍,在不同的浏覽器中的會有不同的處理方式。例如在解析
時,有的浏覽器會将其解析為"January 32,2007"
。而Opera浏覽器則傾向于插入目前月份的目前日期值,傳回"February 1,2007"
。"January 目前日期值,2007"
- 其實我們沒有必要在建立一個 Date 對象的時候顯式調用 Date.parse() 和 Date.UTC() 方法,因為将相應的參數傳入構造函數後,它會根據參數的類型在背景自動調用Date.parse() 或 Date.UTC() 方法,這樣得到日期和時間都是基于本地時區的,就算你存入的參數類型是 Date.UTC() 方法所需的參數類型,最後得到的結果還是基于本地時區的結果。
Date.now()
ES5添加了Date.now()方法,用來傳回表示調用這個方法時的日期和時間的毫秒數。這個方法可以用來分析函數的運作時間,如下。
// 取得開始時間
var start = Date.now();
// 調用函數
doSomething();
// 擷取結束時間
var end = Date.now();
// 得到函數運作時間
var
在不支援它的浏覽器中,我們可以通過
+
操作符擷取Date對象的時間戳,也可以達到同樣的目的。
// 取得開始時間
var start = +new Date();
// 調用函數
doSomething();
// 擷取結束時間
var end = +new Date();
// 得到函數運作時間
var
繼承的方法
和其他引用類型一樣,Date 類型也重寫了 toLocaleString() 、toString() 和 valueOf() 方法,但這些方法的傳回值與其他類型中的方法不同。
- Date 類型的 toLocalString() 方法會按照浏覽器設定的時區相适應的格式傳回日期和時間。這意味着時間格式中會包含AM和PM,但不會包含時區資訊,具體的格式會因浏覽器而異。
var now = new Date();
console.log(now.toLocaleString()); // 2018/3/20 上午10:33:32
- Date 類型的 toString() 方法會傳回帶有時區資訊的日期和時間,其中時間一般以軍用時間(範圍0到23)表示。
var now = new Date();
console.log(now.toString()); // Tue Mar 20 2018 10:33:32 GMT+0800 (中國标準時間)
- Date 類型的 valueOf() 方法會傳回日期的毫秒表示,也就是時間戳。是以我們可以使用比較操作符來比較日期。
var date1 = new Date(2018,1,1);
var data2 = new Date(2018,3,18);
console.log(data1 < data2); // true
在使用比較操作符,會隐式地調用 Date 對象的 valueOf() 方法,然後根據得到的毫秒數來進行比較。
其實在實際應用中,使用 toLocaleString() 和 toString() 來顯示日期時間是沒有什麼價值的,因為它們的傳回的日期格式在不同的浏覽器裡大相徑庭,無法得到一緻化的顯示結果,而且得到的格式對使用者的互動效果也不是很友好。
日期格式化方法
Date類型還有一些專門用來将日期轉化為字元串的方法,不過與 toLocaleString() 和 toString() 的缺點一樣,在平常的使用中沒有多大價值,是以僅做一下了解就好。
- toDateString()——以特定于實作的格式顯示星期幾、月、日和年;
- toTimeString()——以特定于實作的格式顯示時、分、秒和時區;
- toLocaleDateString()——以特定于地區的格式顯示星期幾、月、日和年;
- toLocaleTimeString()——以特定于實作的格式顯示時、分、秒;
- toUTCString()——以特定于實作的格式完整的UTC日期。
日期/時間元件方法
上面我們已經提到了,Date類型本身的字元串格式化方法很雞肋,在日常使用中用處不大,是以一般我們隻有自己編寫适用于項目的 format 方法,這時我們一般需要用到擷取日期中特定部分的方法。方法有點多,如下:
-
getTime()
傳回表示日期的毫秒數;與valueOf()方法傳回的值相同
-
setTime(毫秒)
以毫秒數設定日期,會改變整個日期
-
getFullYear()
取得4位數的年份(如2007而非僅07)
-
getUTCFullYear()
傳回UTC日期的4位數年份
-
setFullYear(年)
設定日期的年份。傳入的年份值必須是4位數字(如2007而非僅07)
-
setUTCFullYear(年)
設定UTC日期的年份。傳入的年份值必須是4位數字(如2007而非僅07)
-
getMonth()
傳回日期中的月份,其中0表示一月,11表示十二月
-
getUTCMonth()
傳回UTC日期中的月份,其中0表示一月,11表示十二月
-
setMonth(月)
設定日期的月份。傳入的月份值必須大于0,超過11則增加年份
-
setUTCMonth(月)
設定UTC日期的月份。傳入的月份值必須大于0,超過11則增加年份
-
getDate()
傳回日期月份中的天數(1到31)
-
getUTCDate()
傳回UTC日期月份中的天數(1到31)
-
setDate(日)
設定日期月份中的天數。如果傳入的值超過了該月中應有的天數,則增加月份
-
setUTCDate(日)
設定UTC日期月份中的天數。如果傳入的值超過了該月中應有的天數,則增加月份
-
getDay()
傳回日期中星期的星期幾(其中0表示星期日,6表示星期六)
-
getUTCDay()
傳回UTC日期中星期的星期幾(其中0表示星期日,6表示星期六)
-
getHours()
傳回日期中的小時數(0到23)
-
getUTCHours()
傳回UTC日期中的小時數(0到23)
-
setHours(時)
設定日期中的小時數。傳入的值超過了23則增加月份中的天數
-
setUTCHours(時)
設定UTC日期中的小時數。傳入的值超過了23則增加月份中的天數
-
getMinutes()
傳回日期中的分鐘數(0到59)
-
getUTCMinutes()
傳回UTC日期中的分鐘數(0到59)
-
setMinutes(分)
設定日期中的分鐘數。傳入的值超過59則增加小時數
-
setUTCMinutes(分)
設定UTC日期中的分鐘數。傳入的值超過59則增加小時數
-
getSeconds()
傳回日期中的秒數(0到59)
-
getUTCSeconds()
傳回UTC日期中的秒數(0到59)
-
setSeconds(秒)
設定日期中的秒數。傳入的值超過了59會增加分鐘數
-
setUTCSeconds(秒)
設定UTC日期中的秒數。傳入的值超過了59會增加分鐘數
-
getMilliseconds()
傳回日期中的毫秒數
-
getUTCMilliseconds()
傳回UTC日期中的毫秒數
-
setMilliseconds(毫秒)
設定日期中的毫秒數
-
setUTCMilliseconds(毫秒)
設定UTC日期中的毫秒數
-
getTimezoneOffset()
傳回本地時間與 UTC 時間相差的分鐘數。例如,美國東部标準時間傳回300。在某地進入夏令時的情況下,這個值會有所變化
時間戳的擷取
在平時我們需要用到最多的地方還是時間戳的擷取,上面我們也提到了不少可以的得到時間戳的方法,下面做一個歸納。
- 使用 Date.now() 擷取目前時間的毫秒數,隻适用于目前時間。
- 使用 Date.parse() 擷取指定時間的毫秒數,隻适用于指定時間。
- 使用 Date.UTC() 擷取指定時間的毫秒數,隻适用于指定時間。
- 使用操作符
擷取Date對象表示日期的毫秒數,都适用取決于Date對象。+
- 使用 valueOf() 擷取Date對象表示日期的毫秒數,都适用取決于Date對象。
- 使用 getTime() 擷取Date對象表示日期的毫秒數,都适用取決于Date對象。
寫在最後
花了一些時間将 Date 類型的基礎知識,稍微總結了一下。這一塊還有很多内容,以後有時間還會對 Date 對象的格式化這個一塊進行補充。通過總結發現了很多自己以前習慣性忽略的一些東西。還是希望通過這樣的方式,把 JavaScript 這一塊的基礎打牢一點。不着急慢慢來 :)