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

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

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

Date()與new Date()的區別

Date()直接返回當前時間字符串,無論參數是number仍是任何stringjava

Date();
Date('sssss');
Date(1000);
//Fri Aug 21 2015 15:46:21 GMT+0800 (中國標準時間)

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

new Date();
//Fri Aug 21 2015 15:51:55 GMT+0800 (中國標準時間)

new Date(1293879600000);
new Date('2011-01-01T11:00:00')
new Date('2011/01/01 11:00:00')
new Date(2011,0,1,11,0,0)
new Date('jan 01 2011,11 11:00:00')
new Date('Sat Jan 01 2011 11:00:00')
//Sat Jan 01 2011 11:00:00 GMT+0800 (中國標準時間)

new Date('sss');
new Date('2011/01/01T11:00:00');
new Date('2011-01-01-11:00:00')
new Date('1293879600000');
//Invalid Date

new Date('2011-01-01T11:00:00')-new Date('1992/02/11 12:00:12')
//596069988000

從上面幾個測試結果能夠很容易發現chrome

  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 標準日期字符串

    YYYY/MM/DD HH:MM:SS ± timezon(時區用4位數字表示)
    // eg 1992/02/12 12:23:22+0800

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

  1. ISO 8601標準日期字符串

    YYYY-MM-DDThh:mm:ss ± timezone(時區用HH:MM表示)
    
     1997-07-16T08:20:30Z
     // 「Z」表示UTC標準時區,即"00:00",因此這裏表示零時區的`1997年7月16日08時20分30秒`
     //轉換成位於東八區的北京時間則爲`1997年7月17日16時20分30秒`
    
     1997-07-16T19:20:30+01:00
     // 表示東一區的1997年7月16日19時20秒30分,轉換成UTC標準時間的話是1997-07-16T18:20:30Z
  1. 日期和時間中間的T不能夠被省略,一省略就出錯。

  2. 雖然在chrome瀏覽器上時區也能夠用+0100這種RFC2822的形式來表示,然而IE上不支持這種混搭寫法,因此用ISO8601標準形式表示的時候時區要用+HH:MM

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

//RFC2822:
new Date('1992/02/13') //Thu Feb 13 1992 00:00:00 GMT+0800 (中國標準時間)
//ISO8601:
new Date('1992-02-13') //Thu Feb 13 1992 08:00:00 GMT+0800 (中國標準時間)

然而上面這個只是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/

核心測試函數:

function test(dateString,times,func){
    var startTime=window.performance.now();
    // console.log('start='+startTime.getTime());
    for (var i = 0; i < times; i++) {
        func(dateString);//這裏填寫具體的解析函數
    };
    var endTime=window.performance.now();
    // console.log('endTime='+endTime.getTime());
    var gapTime=endTime-startTime;
      console.log('一共耗時:'+gapTime+'ms');
    // console.log('時間字符串'+dateString);
    return gapTime;
}

之因此這裏用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.toLocaleDateStringDate.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的時候結果也是同樣),

window.navigator.language
//"nl-NL"
window.navigator.systemLanguage
//"zh-CN"(設置中的非unicode程序所使用語言選項)
window.navigator.userLanguage
//"nl-NL"
window.navigator.browserLanguage
//"ja-JP"(系統菜單界面語言)
window.navigator.languages
//undefined

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

window.navigator.language
//'zh-CN'
window.navigator.languages
//["en-US", "en", "zh-CN", "zh", "ja", "zh-TW", "de-LI", "de", "pl"]
//當界面語言改成"en-US"時
window.navigator.language
//'en-US'(瀏覽器界面語言)

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

window.navigator.language
//'en-US'
window.navigator.languages
//["en-US", "zh-CN", "de", "zh", "en"]
//當界面語言改成"en-US",`accept-language`首位爲`zh-CN`的時候
window.navigator.language
//'zh-CN'(`accept-language`首選值)
window.navigator.languages
//["zh-CN", "de", "zh", "en-US", "en"]
  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函數的解析)

相關文章
相關標籤/搜索