QQ瀏覽器Date相關函數實現不符合ECMA規範

前言

沒錯,標題起的有點唬人,但吸引你進來了,你不妨瞭解一下。本文源於用戶反饋的在QQ瀏覽器上組件的日期與星期數沒法對應的Bug,測試發現是QQ瀏覽器未正確按照ECMA標準實現Date相關的函數,本文給出了ECMA規範的定義及實際測試中的結果,分析Bug產生的緣由及規避Bug的方法,最後再介紹了處理時間相關業務時須要注意的時區問題, 但願對讀者能有所幫助。html

Bug描述

用戶反饋在QQ瀏覽器上,組件的日期與時間沒法匹配。例如2018年11月14日爲週三,組件在QQ瀏覽器上展現週二。git

Bug復現環境

  • 系統macOS 10.14 系統時區 杭州-中國
  • QQ瀏覽器版本 Mac(4.4.119.400)(64-bit)
  • Chrome瀏覽器版本 Mac 70.0.3538.77(正式版本) (64 位)

Bug產生的緣由

QQ瀏覽器上github

  • Date.prototype.toString()
  • Date.prototype.getDay()
  • Date.prototype.getTimezoneOffset()
  • new Date(Time String)

等函數返回的值未按ECMA標準實現,共同的問題是沒有參照系統當前時區計算返回結果。chrome

ECMA標準定義

ECMC規範連接瀏覽器

15.9.5.2 Date.prototype.toString ( )bash

This function returns a String value. The contents of the String are implementation-dependent, but are intended to represent the Date in the current time zone in a convenient, human-readable form.函數

該函數返回基於當前時區的時間字符串。測試

ECMA規範連接ui

15.9.5.16 Date.prototype.getDay ( )this

Let t be this time value.

If t is NaN, return NaN.

Return WeekDay(LocalTime(t)).
複製代碼

該函數返回基於當前時區的星期數。

ECMA規範連接

15.9.5.26 Date.prototype.getTimezoneOffset ( )

Returns the difference between local time and UTC time in minutes.

Let t be this time value.

If t is NaN, return NaN.

Return (t − LocalTime(t)) / msPerMinute.
複製代碼

該函數返回的是當前時區與0時區(格林尼治時間)的差值。

測試結果

//Chrome
new Date(1542124800000).toString() // Wed Nov 14 2018 00:00:00 GMT+0800 (中國標準時間)
new Date(1542124800000).getDay() // 3
new Date(1542124800000).getTimezoneOffset() // -480

//QQ
new Date(1542124800000).toString() // Tue Nov 13 2018 16:00:00 GMT+0000 (UTC)
new Date(1542124800000).getDay()  // 2
new Date(1542124800000).getTimezoneOffset()  // 0
new Date("2018/11/14") //Wed Nov 14 2018 00:00:00 GMT+0000 (UTC)
複製代碼

相似的問題還有

//Chrome 
new Date("2018/11/14") // Wed Nov 14 2018 00:00:00 GMT+0800 (中國標準時間)
new Date("2018/11/14").getTime() // 1542124800000

//QQ
new Date("2018/11/14") //Wed Nov 14 2018 00:00:00 GMT+0000 (UTC)
new Date("2018/11/14").getTime() // 1542153600000
複製代碼

從以上結果來看,QQ瀏覽器的時區參考都是UTC時間(即0時區時間),是由於獲取不到當前系統時區,而默認用了UTC時區爲參考嗎?

基於以上猜測,咱們再測試下Date.prototype.toLocaleString(),咱們先來看下規範的定義

ECMA規範連接

15.9.5.5 Date.prototype.toLocaleString ( )

This function returns a String value. The contents of the String are implementation-dependent, but are intended to represent the Date in the current time zone in a convenient, human-readable form that corresponds to the conventions of the host environment’s current locale.

該函數返回基於當前時區的時間字符串,展現形式須要以當前主機的語言環境而定。

能夠注意到規範定義的 toString()、toLocaleString()都是以當前時區爲基準,僅展示語言有差異。

測試的結果

//Chrome
new Date(1542124800000).toString() // Wed Nov 14 2018 00:00:00 GMT+0800 (中國標準時間)
new Date(1542124800000).toLocaleString() // "2018/11/14 上午12:00:00"
new Date(1542124800000).toLocaleDateString() //"2018/11/14"


//QQ
new Date(1542124800000).toString() // Tue Nov 13 2018 16:00:00 GMT+0000 (UTC)
new Date(1542124800000).toLocaleString() // "2018/11/14 上午12:00:00"
new Date(1542124800000).toLocaleDateString() //"2018/11/14"
複製代碼

測試發現QQ是能夠正確展現toLocaleXXX()的結果的,看來是能獲取到時區的嘛。

那麼基於以上測試,咱們的結論是:QQ瀏覽器是能知道當前的系統時區的,但未正確實現 Date.prototype.toString()、Date.prototype.getDay()、Date.prototype.getTimezoneOffset()、new Date(Time String)等方法。

如何規避可能產生的Bug

以北京時區2018/11/14 上午12點(凌晨)的時間爲例, 先展現出bug的代碼:

日期的獲取使用toLocaleDateString():

new Date(1542124800000).toLocaleDateString() //"2018/11/14"

複製代碼

星期使用了getDay()

"星期" + "日一二三四五六".charAt(new Date(1542124800000).getDay()); 

// chrome中爲 星期三
// QQ中爲 星期二
複製代碼

在QQ瀏覽器中,會致使星期數比正確值少一的狀況。

解決的方法

知識點鋪墊

Date.now()獲取的是當前時間與格林尼治時間1970/1/1 0:0:0 的毫秒數之差, 全球全部時區的用戶,在同一物理時刻,調用該方法返回的數值是一致的。

假如Date.now()返回的是 1542124800000。這個毫秒數對應北京時間2018/11/14 00:00,對應格林尼治時間爲2018/11/13 16:00 在瀏覽器控制檯能夠進行驗證獲得:

new Date(1542124800000) // Wed Nov 14 2018 00:00:00 GMT+0800 (中國標準時間)
new Date(1542124800000).toUTCString() //Tue, 13 Nov 2018 16:00:00 GMT
複製代碼

同一個毫秒數,表示的是同一個物理時刻,體如今不一樣時區的時間上,會有時差。

解決思路

瞭解了這個知識點後,咱們的解決思路就是統一使用Date.getUTCxxx()方法,該函數返回的以0時區爲準的時間度量值。咱們只須要在測量的時間上,增長8個小時的時區偏移,便可獲取到基於當前時區的時間度量值。

例如獲取星期數:

"星期" + "日一二三四五六".charAt(new Date(time + 8 * 3600 * 1000).getUTCDay()); 
複製代碼

可能剛看到這樣的計算方式會比較繞,咱們能夠這麼想:北京時區比0時區快8個小時,那麼0時區8個小時候後的星期數,就是咱們如今的星期數。你們能夠停下來想想。

其餘注意事項

系統時區

業務場景: 倒計時

var endTime = new Date('2018/11/11 0:0:0')
console.log(endTime - Date.now())
複製代碼

這個方法可能存在的問題是物理世界的同一時刻,不一樣時區用戶獲取到的Date.now()是一致的,但 new Date('2018/11/11 0:0:0')的值會根據用戶機器設定的時區而變化,因此會致使同一物理時刻,不一樣時區的用戶看到的倒計時時間不一致。

解決方式是構造函數傳入時區值

var endTime = new Date('2018/11/11 0:0:0 GMT+0800')
console.log(endTime - Date.now())
複製代碼

或者 按照上述的UTC轉換法

var endTime = new Date(Date.UTC(2018,10,11,0,0,0)) - 8*3600*1000
console.log(endTime - Date.now())
複製代碼

這樣不一樣時區的用戶,看到的倒計時是一致的,能在同一時間參與秒殺。

結語

業務場景中與時間相關的處理,都須要額外打一個心眼,考慮時區等因素。本文分析了QQ瀏覽器上的實現Bug,並給出了基於UTC時間轉化的方式,保證了不一樣時區用戶的一致性,但願能給你們帶來參考價值。

相關文章
相關標籤/搜索