Javascript 时间时区大杂烩

Read not to contridict and confute; nor to believe and take for granted; nor to find talk and discourse; but to weigh and consider.

以前从来没有认真钻研过JS中Date的时间时区细节,因为工作中遇到了前端页面设置时间,实际保存早/晚了一天的情况,专门花时间学习了相关知识点,谨以此文作学习笔记。

GMT & UTC

  • GMT (格林威治时间) - 格林尼治标准时间的正午是指当太阳横穿格林威治子午线时(也就是在格林威治上空最高点时)的时间。
  • UTC (协调世界时) - 是最主要的世界時間標準,其以原子时秒长为基础,在时刻上尽量接近于格林尼治标准时间。

以上信息有兴趣可以去维基百科查看详情,看不懂也没关系,只需要记住:两个都是世界标准时间,由于GMT本身存在误差,现在的标准时间由UTC决定。

时间表示法 - ISO 8601

国际标准ISO 8601,是国际标准化组织的日期和时间的表示方法。 格式:YYYY-MM-DDThh24:mm:ssZ (eg. 2017-08-24T08:45Z)。日期时间合并表示时,要在时间前面加一大写字母T。如果时间是以 UTC 表示,则在时间后面直接加上一个“Z”(不加空格)。“Z”是 UTC 时间中0时区的标志。例如: “09:30 UTC” -> “09:30Z”/“0930Z”。 Date.toJSON 方法返回的就是这种格式的字符串。

Javascript的Date对象

new Date() 返回的是一个本地时间的date对象,

var date = new Date(); // Fri Aug 25 2017 13:11:03 GMT+0800 (CST)
var dateStr = date.toISOString(); // '2017-08-25T05:11:03.000Z'

CST(china standard time)是大中华标准时区,我们的时区是在东八区,GMT+0800表示标准时间基础上加8小时。
dateStr是date 对象的标准时间表示形式,”Z”表示这是UTC时间,从”05:11:03”也可以看出是在本地时间的基础上减去了8小时。

var dt1 = new Date('2017-08-25T05:11:03.000Z'); // Fri Aug 25 2017 13:11:03 GMT+0800 (CST)

var dt2 = new Date('2017-08-25T05:11:03.000'); // Fri Aug 25 2017 05:11:03 GMT+0800 (CST)

上面两行代码传入的初始时间都是ISO 8601标准,区别在于一个是UTC时间,一个是本地时间(没有”Z”)。如果传入的是UTC时间,那么new Date最后返回的是一个转换为本地时间的date对象,也就是在我们传入的初始日期上+8小时后的日期对象。如果没有”Z”标识,系统默认这是一个本地时间,返回的date对象不再转换。简而言之,new Date时传入UTC时间,得到+8小时后的时间,传入普通时间字符串,得到时间一致的date对象

以下两行代码的效果和上面对应代码具有相同效果:

var dt1 = new Date('Fri Aug 25 2017 05:11:03 UTC'); // 申明了时区(UTC), 返回+8小时后的date对像
var dt2 = new Date('Fri Aug 25 2017 13:11:03'); // 没有申明时区,返回与传入时间一致的date对象

前后端交互

经常遇到的一种交互场景: 用户在一个详情页面设置一个带日期时间保存到数据库,下次加载页面的时候显示已有的日期。
数据库日期相关的数据类型有多个,有的带时区,有的不带。如果在交互时,前后端对时区的处理不一致,就可能造成最终显示的时间与用户实际选择的时间不一致的情况。举例说明:

// 用户实际选择的时间是: 2017-08-25
// date object : Fri Aug 25 2017 07:00:00 GMT+0800
// save data
$http.put(apiUrl, {time: '2017-08-24T23:00:00.000Z'}).then(()=> 'success!');

// get page detail
$http.get('apiUrl').then( data => {
  console.log(data);   // {time: '2017-08-24T23:00:00.000'}
  this.date = new Date(data.time).toDateString(); //
});

//tpl.html
<p> date: <!-- %date% -->; </p> // date: Fri Aug 24 2017

用户选择一个日期, 我得到一个date对象Fri Aug 25 2017 07:00:00 GMT+0800,转换为JSON数据保存到数据库,实际传给服务端的值是’2017-08-24T23:00:00.000Z’。假设数据库存的是不带时区的时间, 而保存前服务端未对该时间做相应的转换。那页面重新加载时,数据库返回的是一个非UTC时间'2017-08-24T23:00:00.000'。页面显示的就会比用户实际选择的早一天(实际:2017.8.25, 显示:2017.8.24)。

参考

  • 维基百科