为何到了时间你的活动还没开始——探究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}"
复制代码

还能够改为其余值,最后的结果就是该是什么就是什么了

关注公众号《不同的前端》,以不同的视角学习前端,快速成长,一块儿把玩最新的技术、探索各类黑科技

相关文章
相关标签/搜索