爲何到了時間你的活動還沒開始——探究Date對象

「活動已經上線了,按照預期,早上8點開始。然而,蘋果手機活動竟然還沒開始,而安卓手機已經開始了!」 🙀前端

活動怎麼還沒開始?!

假設有一個活動,原計劃定的是12月25日早上8點開始,結果蘋果用戶到了早上8點卻看見活動按鈕仍是灰色的,並且PC、安卓都是正常。這種狀況若是發生,首先往哪一個方向考慮呢?ios

第一個想到的應該就是new Date傳入UTC字符串的坑了:git

new Date('2019-12-25T08:00')
// pc chrome: Wed Dec 25 2019 08:00:00 GMT+0800 (中國標準時間)
// 蘋果手機: Wed Dec 25 2019 16:00:00 GMT+0800 (CST)
// mac safari: Wed Dec 25 2019 16:00:00 GMT+0800 (CST)
new Date('2019/12/25 08:00')
// pc chrome: Wed Dec 25 2019 08:00:00 GMT+0800 (中國標準時間)
// 蘋果手機: Wed Dec 25 2019 08:00:00 GMT+0800 (CST)
// mac safari: Wed Dec 25 2019 08:00:00 GMT+0800 (CST)

// 加一個T,safari下就能夠算是UTC字符串了
複製代碼

地理常識複習: 格林尼治時間(GMT)的正午是指當太陽橫穿本初子午線的時候(格林尼治此時爲當地中午12點),有了這個參考點,那麼其餘任意時刻任意時區的時間均可以推導出來。可是,衆所周知,地球不是完美的球體,地球天天的自轉也不是徹底按照同樣的規律的。如今的標準時間通常使用的是由原子鐘報時的協調世界時(UTC),UTC時間以原子時秒長爲基礎。不過GMT、UTC差異不影響生活。chrome

咱們也能夠看見new Date打印有GMT+0800 (中國標準時間)。由於中國處於東八區,與UTC時間相差8個小時,因此有GMT+0800標記。也就是說UTC時間00:00:00的時候,咱們的時間是08:00:00。咱們能夠把GMT+0800改爲GMT+0900,new Date後發現就少了一個小時了。另外,移動端打印的CST表示的就是北京時間了數據庫

好了,上面的問題怎麼解決。已經知道了傳UTC時間出問題了,那麼咱們就不傳UTC時間咯。後端

時間戳大法好,不過由於難以改變的歷史緣由,就是給你UTC字符串你怎麼辦?post

首先,中間加一個T就是分割日期和時間,而ios上這就算是UTC字符串了。若是要解決上面的問題,那麼咱們把它換成空格就行了。可是,又有另一個坑,IOS上執行new Date('2019-12-25 08:00')會獲得invilaid date。處理方法是把2019-12-25轉換成2019/12/25的格式:學習

'2019-12-25T08:00'.replace(/-/g, '/').replace('T', ' ')
new Date('2019/12/25 08:00')
複製代碼

若是最後一位加一個Z,則表示的必定是UTC時間,除了ios,pc上也是會加多8小時ui

new Date('2019-12-25T08:00Z')
// pc: Wed Dec 25 2019 16:00:00 GMT+0800 (中國標準時間)
複製代碼

另外,Date.prototype還有一個getTimezoneOffset,顧名思義應該和時差有關。該方法返回與UTC的時差,單位是分。咱們處於GMT+8,返回-480 (0 - 8) * 60 = -480spa

new Date().getTimezoneOffset()
複製代碼

因此,上面的問題咱們還能夠在UTC時間下,使用getTimezoneOffset做爲另外一個解決方案:

// 當判斷爲蘋果設備的時候,使用該方法
if (/iPhone|iPad|iPod|iOS/i.test(navigator.userAgent)) {
    const date = new Date(Date.parse(UTCString) + new Date().getTimezoneOffset() * 60 * 1000)
}
複製代碼

繼續研究

看了一下Date對象的prototype的方法,看起來不少,實際上就是get和set了UTC、GMT的年月日時分秒。基本的set、get方法,你們寫日期組件應該寫過很多了,市面上也有成熟的解決方案如moment。

對於時差問題,咱們平時產品若是沒有對外的話,通常沒什麼問題,若是是UTC時間記得轉回來就是了。若是涉及到海外,咱們儘可能仍是使用UTC好一些。對於先後端,也是應該傳UTC時間的,並且應該傳時間戳。UTC時間戳生成方法:

// 表示的是UTC時間2019/12/11 11:11:11:011的UTC時間戳
Date.UTC(2019, 11, 11, 11, 11, 11 ,11)
複製代碼

下面,咱們看看兩地時間如何轉換

本地時間 <=> UTC <=> 異地時間

// 本地異地以UTC爲溝通橋樑
// 本地/異地生成UTC
const UTCString = new Date().toISOString()
// 異地/本地解析UTC
const dateString = new Date(UTCString) 
dateString.toLocaleString() // 格式化爲當地時間,toLocaleString有不少配置項
複製代碼

UTC => 本地/異地時間

// 某個活動以UTC時間爲中心
const UTCTimestamp = Date.UTC(2019, 11, 11, 11, 11, 11 ,11)
// 解析爲本地時間
const localTime = new Date(new Date(UTCTimestamp).toISOString())
// 等價於
new Date('2019-12-11T11:11:11Z') // 注意 -
// 格式化時間
localTime.toLocaleString()
複製代碼

Date的prototype上有各類toxxstring,着重看一下toLocaleString、toLocaleTime,它們參數配置項不少,下面總結了一波文檔的介紹,快速熟悉這個方法的使用技巧。咱們先看幾個例子:

// 首先,咱們先定一個上帝時間UTC
const UTCTimestamp = Date.UTC(2019, 11, 11, 11, 11, 11 ,11)
// 無參數默認當前時區的時間格式化方案
// 🇨🇳"2019/12/11 下午7:11:11"
new Date(UTCTimestamp).toLocaleString()
new Date(UTCTimestamp).toLocaleString('zh-CN')

// 🇬🇧12/11/2019, 7:11:11 PM
new Date(UTCTimestamp).toLocaleString('en-US')

// 🇫🇷 11/12/2019 à 19:11:11
new Date(UTCTimestamp).toLocaleString('fr')

// 🇰🇷 2019. 12. 11. 오후 7:11:11
new Date(UTCTimestamp).toLocaleString('ko')
複製代碼

這種表示方法傳入的參數叫locales參數,指定了當地語言,告訴toLocaleString以哪一種語言、如何格式化日期。toLocaleTimeString也是同樣,只是它只返回時間部分。

toLocaleString還能夠傳第二個參數,是一個配置對象,該對象的屬性均可以隨意組合:

{
    ['weekday'(星期)|'era'(紀元)]:'narrow'(緊湊、最短)|'short'(短)|'long'(長),
    ['year'|'month'|'day'|'hour'|'minute'|'second']:'numeric'(展現完整)| '2-digit'(2位)
}
// 其中month還支持"narrow", "short", "long"
複製代碼

使用的時候,有什麼key以及對應的值,就以什麼狀態展現在最終返回的日期字符串中。若是使用的時候,key的值並非規定的那些,那麼js將會報錯

// 🌰
const date = new Date('2019-12-11T11:11:11Z')
date.toLocaleString("ch", {
  weekday: 'long', year: 'numeric', month: 'long', day: 'numeric',
  hour12: false, // 不開啓12小時模式
  hour: '2-digit', minute: '2-digit', era: 'narrow'
})
// 公元2019年12月11日星期三 19:11, 對於中文era用什麼都同樣

// 其餘條件不變,語言從ch換成en
// options.era='narrow': Wednesday, December 11, 2019 A, 19:11
// options.era='short': Wednesday, December 11, 2019 AD, 19:11
// options.era='long': Wednesday, December 11, 2019 Anno Domini, 19:11
// 同上,options.hour12=true: Wednesday, December 11, 2019 Anno Domini, 07:11 PM
複製代碼

好了,還有一個很重要的屬性——timeZone,它肯定了時區。因此,給你一個Date,你不規定時區的話,那麼它是多少就多少,不會轉時區,平時使用的new Date時候就是這樣。咱們前面所作的都是控制它的最終展現而已。它的值必須是timeZone數據庫裏面的,timeZone數據庫能夠點擊這裏下載。

下載了時區數據文件,看見一個叫asia的文件,果斷打開,而後找到了中國相關的時區:

date.toLocaleString("en", {
  weekday: 'long', year: 'numeric', month: 'long', day: 'numeric',
  hour12: true, hour: '2-digit', minute: '2-digit', era: 'long',
  timeZone: 'Asia/Urumqi'
})
// Wednesday, December 11, 2019 Anno Domini, 05:11 PM 烏魯木齊少兩個時區
// timeZone='Asia/Shanghai': Wednesday, December 11, 2019 Anno Domini, 07:11 PM 原本就是和上海時區同樣
// timeZone='Europe/Vienna': Wednesday, December 11, 2019 Anno Domini, 12:11 PM
複製代碼

其實就是各類屬性自由組合,隨意發揮了。因此,是否是以爲日期格式化白寫了?這並非的,若是不兼容呢,不仍是要寫?不過,檢測toLocaleString不兼容傳入各類配置也很簡單:

try {
    date.toLocaleString('are u ok?')
    // 不兼容,本身實現一波
} catch {
    // 兼容,愉快玩耍
}

複製代碼

Intl是另外一種方案,mdn上說: 當格式化大量日期時,最好建立一個 Intl.DateTimeFormat 對象,而後使用該對象 format 屬性提供的方法。使用起來其實也仍是差很少的

Date的隱式轉換

以前有另外一篇文章講了隱式轉換。Date對象在隱式轉換的時候,和其餘類型不同。Date對象先隱式調用toString,而其餘類型則會先嚐試調用valueOf,若是valueOf後返回的仍是原先那個類型的話,會執行toString。

new Date - 1 // 時間戳 - 1。先toString,發現有數字類型,再valueOf。而Date的valueOf返回的是時間戳
new Date + '1' // 一串文字1。先toString,字符串+字符串不須要再轉了

// 一個神奇的結果,猜測:JSON.stringify會尋找date的toJSON來使用
new Date().toJSON() // "yyyy-mm-ddThh:mm:ss.mmmZ"
JSON.stringify(new Date) // ""yyyy-mm-ddThh:mm:ss.mmmZ""
// 至關於JSON.stringify("yyyy-mm-ddThh:mm:ss.mmmZ")
複製代碼

咱們大膽地把date的toJSON幹掉:

const date = new Date
date.toJSON = null
JSON.stringify(date)
// "{"toJSON":null}"
複製代碼

還能夠改爲其餘值,最後的結果就是該是什麼就是什麼了

關注公衆號《不同的前端》,以不同的視角學習前端,快速成長,一塊兒把玩最新的技術、探索各類黑科技

相關文章
相關標籤/搜索