JS原生Date型別方法的一些冷知識

發表於2015-09-07

一個多月沒更新了- -偷懶中。這個東西其實很早之前就在整理了,不過後來發現自己不少地方沒弄明白,然後就一直卡那邊了(其實就是不想寫吧),想了下反正是給自己熟悉js的原生API而已,所以也沒必要太鑽牛角尖,也不一定要多完整,因此就當是Date()函式的一個冷門知識點小補充吧。這篇文章主要講Date()的字串與時間戳轉換以及使用者時間本地化,可能內容上比較亂(不然也不會卡我一個月時間了),見諒

ps:由於 Date() 是js原生函式,不同瀏覽器的解析器對其實現方式並不同,所以返回值也會有所區別。本文測試未特別申明瀏覽器的情況下,均是指 win7 x64+chrome 44.0.2403.155 (正式版本) m 32 位) 版本

Date()與new Date()的區別

Date() 直接返回當前時間字串,不管引數是number還是任何string

new Date() 則是會根據引數來返回對應的值,無引數的時候,返回當前時間的字串形式;有引數的時候返回引數所對應時間的字串。 new Date() 對引數不管是格式還是內容都要求,且只返回字串,

從上面幾個測試結果可以很容易發現

  1. new Date() 在引數正常的情況只會返回當前時間的字串(且是當前時區的時間)
  2. new Date() 在解析一個具體的時間的時候,對引數有較嚴格的格式要求,格式不正確的時候會直接返回Invalid Date,比如將 number 類的時間戳轉換成 string 類的時候也會導致解析出錯
  3. 雖然 new Date() 的返回值是字串,然而兩個 new Date() 的結果字串是可以直接相減的,結果為相差的毫秒數。

那麼, new Date() 能接受的引數格式到底是什麼標準呢?(相對於嚴格要求的多引數傳值方法。非嚴格的單引數(數字日期表示格式)更常用且更容易出錯,所以下文只考慮單引數數字時間字串轉換的情況)

表示格式)更常用且更容易出錯,所以下文只考慮單引數數字時間字串轉換的情況)


new Date()解析所支援的引數格式標準

時間戳格式

這個是最簡單的也是最不容易出錯的。當然唯一的缺點大概就是對開發者不直觀,無法一眼看出具體日期。
需要注意的以下兩點:

  1. js內的時間戳指的是當前時間到1970年1月1日00:00:00 UTC對應的毫秒數,和unix時間戳不是一個概念,後者表示秒數,差了1000倍
  2. new Date(timestamp) 中的時間戳必須是number格式, string 會返回Invalid Date。所以比如new Date('11111111')這種寫法是錯的

時間數字字串格式

不大清楚這種該怎麼描述,就是類似YYYY/MM/DD HH:mm:SS這種。下文以dateString代指。
new Date(dateString)所支援的字串格式需要滿足RFC2822標準或者ISO 8601標準
這兩種標準對應的格式分別如下:

  1. RFC2822 標準日期字串

     RFC2822還有別的格式,不過上面這個是比較常用的(另外這標準太難啃了,實在沒耐心啃完,所以也就沒太深入)。RFC2822標準本身還有其他的非數字日期表達方式,不過不在這個話題討論範圍內了,略過

  2. ISO 8601標準日期字串
  1. 日期和時間中間的T不可以被省略,一省略就出錯。
  2. 雖然在chrome瀏覽器上時區也可以用+0100這種RFC2822的形式來表示,然而IE上不支援這種混搭寫法,所以用ISO8601標準形式表示的時候時區要用+HH:MM

單單從格式上來說,兩者的區別主要在於分隔符的不同。不過需要注意的是,ISO 8601標準的相容性比RFC2822差得多(比如IE8和iOS均不支援前者。我知道IE8很多人會無視,不過iOS也有這個坑的話,各位或多或少會謹慎點了吧?),所以一般情況下建議用RFC 2822格式的。
不過需要注意的是,在未指定時區的前提下,對於只精確到day的日期字串,RFC 2822返回結果是以當前時區的零點為準,而ISO8601返回結果則會以UTC時間的零點為標準進行解析。
例如:

 

然而上面這個只是ES5的標準而已,在ES6裡這兩種形式都會變成當前時區的零點為基準1
不管你們崩潰沒,反正我是已經想死了
關於跨瀏覽器的dataString解析情況,還可以參考這個頁面:
JavaScript and Dates, What a Mess!

所以對於時間字串物件,個人意見是要麼用RFC2822形式,要麼自己寫個解析函式然後隨便你傳啥格式進來。


時間格式化函式的效率

這裡的時間格式化值得是將時間字串轉換成毫秒數的過程。js原生的時間格式化函式有Date.parseDate.prototype.valueOfDate.prototype.getTimeNumber(Date)+Date(還有個Date.UTC方法,然而對引數要求嚴格,不能直接解析日期字串,所以略過)
這5個函式從功能上來說一模一樣,但是具體的效率如何呢?我寫了個檢測頁面,諸位也可以自己測試下。
http://codepen.io/chitanda/pen/NqeZag/

核心測試函式:

 

之所以這裡用window.performance.now()而不用new Date(),是因為前者精確度遠比後者高。後者只能精確到ms。會對結果造成較大影響

測試結果:

單次執行50W次時間格式化函式,並重複測試100次,最後的結果如下:
(表格中的數字為單次執行50W次函式的平均結果。單位為毫秒)

函式 chrome IE Firefox
Date.parse() 151.2087 55.5811 315.0446
Date.prototype.getTime() 19.5452 21.3423 14.0169
Date.prototype.valueOf() 20.1696 21.7192 13.8096
+Date() 20.0044 31.3511 22.7861
Number(Date) 23.0900 24.8838 23.3775

從這個表格可以很容易得出以下結論:

  1. 從計算效率上來說,Date.prototype.getTime()Date.prototype.valueOf()>+DateNumber(Date)>>Date.parse()
  2. 從程式碼書寫效率上來說,對於少量的時間格式化計算,用+Date()或者Number(Date)即可。而若頁面內有大量該處理,則建議用Date原生的函式Date.prototype.getTime()或者Date.prototype.valueOf().只有Date.parse,找不到任何使用的理由。
  3. 這個結果和計算機的計算效能以及瀏覽器有關,所以具體數字可能會有較大偏差,很正常。然而幾個函式結果的時間差大小順序並不會變。
  4. codepen的線上demo限制比較大,對於這個測驗個人建議最好將原始碼複製到本地檔案然後進行測試

UTC,GMT時間的區別

這個不是啥重要東西,單純當課外知識吧。

格林威治標準時間GMT

GMT即「格林威治標準時間」(Greenwich Mean Time,簡稱G.M.T.),指位於英國倫敦郊區的皇家格林威治天文臺的標準時間,因為本初子午線被定義為通過那裡的經線。然而由於地球的不規則自轉,導致GMT時間有誤差,因此目前已不被當作標準時間使用。

世界協調時間UTC

UTC是最主要的世界時間標準,是經過平均太陽時(以格林威治時間GMT為準)、地軸運動修正後的新時標以及以「秒」為單位的國際原子時所綜合精算而成的時間。UTC比GMT來得更加精準。其誤差值必須保持在0.9秒以內,若大於0.9秒則由位於巴黎的國際地球自轉事務中央局釋出閏秒,使UTC與地球自轉週期一致。不過日常使用中,GMT與UTC的功能與精確度是沒有差別的。
協調世界時區會使用“Z”來表示。而在航空上,所有使用的時間劃一規定是協調世界時。而且Z在無線電中應讀作“Zulu”(可參見北約音標字母),協調世界時也會被稱為“Zulu time”。

瀏覽器獲取使用者當前時間以及喜好語言

首先需要注意一點,瀏覽器獲取當前使用者所在的時區等資訊只和系統的日期和時間設定裡的時區以及時間有關。區域和語言設定影響的是瀏覽器預設時間函式(Date.prototype.toLocaleString等)顯示的格式,不會對時區等有影響。以window為例,控制皮膚\時鐘、語言和區域中的兩個子設定專案的區別如下:

日期和時間:設定當前使用者所處的時間和時區,瀏覽器獲取到的結果以此為準,哪怕使用者的設定時間和時區是完全錯誤的。比如若東八區的使用者將自己的時區設定為東9區,瀏覽器就會將視為東9區;時間資料上同理。這裡的設定會影響Date.prototype.getTimezoneOffsetnew Date()的值

區域和語言:主要是設定系統預設的時間顯示方式。其子設定的格式會影響Date.prototype.toLocaleString方法返回的字串結果

瀏覽器判斷使用者本地字串格式

Date有個 Date.prototype.toLocaleString() 方法可以將時間字串返回使用者本地字串格式,這個方法還有兩個子方法 Date.prototype.toLocaleDateString 和 Date.prototype.toLocaleTimeString ,這兩個方法返回值分別表示日期時間,加一起就是 Date.prototype.toLocaleString 的結果。
這個方法的預設引數會對時間字串做一次轉換,將其轉換成使用者當前所在時區的時間,並按照對應的系統設定時間格式返回字串結果。然而不同瀏覽器對使用者本地所使用的語言格式的判斷依據是不同的。
IE:獲取系統當前的區域和語言格式中設定的格式,依照其對應的格式來顯示當前時間結果;IE瀏覽器實時查詢該系統設定(即你在瀏覽器視窗開啟後去更改系統設定也會引起返回格式變化)
FF:獲取方式和結果與IE瀏覽器相同,區別在於FF只會在瀏覽器程式第一次啟動的時候獲取一次系統設定,中間不管怎麼系統設定怎麼變化,FF都無法獲取到當前系統設定。除非重啟FF瀏覽器。
Chrome:獲取方式和以上兩個都不同。chrome無視系統的區域和語言格式格式,只依照自己瀏覽器的介面設定的選單語言來處理。(比如英文介面則按系統’en-US’格式返回字串,中文介面則按系統’zh-CN’格式返回結果)
綜上可得:

chrome下瀏覽器語言設定優先系統語言設定。而IE和FF則是系統語言設定優先瀏覽器語言設定,不管瀏覽器介面語言是什麼,他們只依照系統設定來返回格式。(沒有MAC,所以不知道safari是啥情況,等以後看情況補充吧)
另外,不同瀏覽器對toLocaleString返回的結果也是不同的,IE瀏覽器嚴格遵守系統設定,而chrome和FF會有自己內建的格式來替換。

瀏覽器介面語言設定和語言設定的區別

這小節貌似有點跑題,然而不說明下的很容易和上面提到的瀏覽器設定的語言混淆,所以也拿出來說一下。
需要注意瀏覽器的語言設定和介面語言設定不是一回事
瀏覽器的語言設定設定的是瀏覽器傳送給伺服器的Request Header裡的Accept-Language的值,這個值可以告訴伺服器使用者的喜好語言,對於某些跨國網站,伺服器可以以此為依舊來返回對應語言的頁面(不過實際應用上這個限制比較大,大部分網站還是根據IP來判斷使用者來源的,或者直接讓使用者自己選擇)
對於各大瀏覽器而言,這個設定的更改也是比較顯性,容易找到的。
IE: Internet選項語言
FF: 選項內容語言
chrome:設定顯示高階設定語言語言和輸入設定...
上面這裡的設定不會影響到瀏覽器的介面語言設定,以國內大部分使用者而言,即不管你怎麼設定這裡的語言選項,瀏覽器選單等預設都會是以中文顯示的.
瀏覽器的介面語言設定一般來說則藏的深得多,沒那麼容易找到。
IE:
解除安裝前面安裝過的瀏覽器語言包,去微軟官網下載對應的IE瀏覽器語言包安裝。(和安裝的語言包有關。系統介面語言和該語言包相同的情況下,變為該語言。否則以安裝的語言包為準。)
FF:位址列輸入about:config,然後找到general.useragent.locale欄位,修改對應欄位即可。
chrome:設定顯示高階設定語言語言和輸入設定...

利用js獲取使用者瀏覽器語言喜好

對於獲取這兩種設定,js原生方法支援度都比較一般:
IE下的 navigator 方法有四種和language有關的方法,區別如下:
假設系統語言為  ja-JP ,系統unicode語言為 zh-CN 日期格式為nl-NL,瀏覽器語言設定(accept-language)為 de ,瀏覽器介面語言為 en-US (其他條件不變,瀏覽器介面語言改為 zh-CN 的時候結果也是一樣),

chrome下,當瀏覽器介面語言為 zh-CN, accept-language首位為 en-US 的時候:

FF下,當瀏覽器介面語言為 zh-CN ,accept-language首位為 en-US 的時候:

  1. 從上面的測試結果可以很明顯的發現IE瀏覽器的這幾個函式都是獲取系統資訊的,無法獲取到前面提到的兩個瀏覽器層面上的設定。(這幾個函式具體含義還有疑問的可以參考MSDN官方文件
  2. window.navigator.language 這個函式雖然三個瀏覽器都可以相容,然而代表的意義完全不同。IE下該函式返回系統設定的時間顯示格式所遵守的標準的地區程式碼;chrome下返回瀏覽器介面語言;FF下返回accept-language的首選語言值

由此:

  1. 瀏覽器設定的語言accept-language值,IE瀏覽器無法利用JS獲取。chrome和FF瀏覽器都可以利用 window.navigator.languages 來獲取,而FF還可以直接用  window.navigator.language 直接獲取accept-language的首選語言值。所以對於accept-language,相容性最好的獲取方法應該是利用後端,發起一個ajax請求,分析header。而不是直接js來處理。
  2. 瀏覽器介面語言,IE和FF都無法利用js來獲取,chrome可以用 window.navigator.language 來獲取
  3. 系統級別的語言設定(系統選單介面語言,系統設定的時間顯示格式),chrome和FF都無法用JS獲取到

總結

這篇文章斷斷續續地寫了一個多月,不過由於對 Date() 函式的掌握不足因此個人感覺其實還是思路有點亂,所以文章看起來可能稍微有點跳躍性。不過使用者本地化那塊內容確實用了不少心思去寫,希望對看到這篇文章的人有點幫助。

參考文獻

  1. Date and Time Formats
  2. Date and Time Specification(RFC2822)
  3. Date.parse()-Differences in assumed time zone
  4. JavaScript and Dates, What a Mess!
  5. navigator object(IE瀏覽器私有language函式的解析)

相關文章