ISO格式的UTC时间与本地时间的互相转化
2018-01-02阅读量
最近遇到了这样一个需求,将后端传过来的ISO格式的UTC时间转换成ISO格式的本地时间。
UTC 和 ISO
首先,我们先了解一下什么是UTC时间,什么是ISO格式。
协调世界时(英语:Coordinated Universal Time,法语:Temps Universel Coordonné,简称UTC)是最主要的世界时间标准,其以原子时秒长为基础,在时刻上尽量接近于格林尼治标准时间。 —— 维基百科
整个地球分为二十四时区,每个时区都有自己的本地时间,同一时刻每个时区的时间都不一样,所以就用UTC来统一标准的。
国际标准ISO 8601,是国际标准化组织的日期和时间的表示方法,全称为《数据存储和交换形式·信息交换·日期和时间的表示方法》。目前是第三版“ISO8601:2004”以替代第一版“ISO8601:1988”与第二版“ISO8601:2000”。 —— 维基百科
当前的UTC时间是2018-01-02T06:29Z(ISO 8601 表示法。)其中Z是4位数字格式的时间偏移量,不写的时候默认不偏移。
UTC时间转本地时间
第一种方法
我们可以在控制台中看一下:
new Date()
Tue Jan 02 2018 16:42:02 GMT+0800 (CST)
new Date('2018-01-02T00:00')
Tue Jan 02 2018 00:00:00 GMT+0800 (CST)
new Date('2018-01-02T00:00+0800')
Tue Jan 02 2018 00:00:00 GMT+0800 (CST)
new Date('2018-01-02T00:00-0800')
Tue Jan 02 2018 16:00:00 GMT+0800 (CST)
new Date('2018-01-02T00:00+0000')
Tue Jan 02 2018 08:00:00 GMT+0800 (CST)
可以看到, new Date()
返回的是当前本地时间。new Date('2018-01-02T00:00')
传入的是ISO格式的字符串,但是没有加偏移量,可以看到结果本地时间跟传入的时间一致,但是上面说其中Z是4位数字格式的时间偏移量,不写的时候默认不偏移, 转化成本地时间不应该是Tue Jan 02 2018 08:00:00 GMT+0800 (CST)
吗?
原来new Date()
对传入的字符串调用Date.parse()
来解析, 如果没有指定时区,默认使用本地时区。
parse 方法接受一个表示时间的字符串,返回相应的时间值。该方法可以接受符合 RFC2822 / IETF 日期语法 (RFC2822 Section 3.3) 的字符串,如 "Mon, 25 Dec 1995 13:30:00 GMT"。该方法能够理解美国大陆时区的缩写,但是为了更通用,应该使用时区偏移,如 "Mon, 25 Dec 1995 13:30:00 +0430" (格林威治的子午线向东偏移4小时30分钟)。如果没有指定时区,默认使用本地时区。—— MDN
后面例子中+0800
,-0800
,+0000
就更好理解了。
所以, UTC 时间与 本地时间的关系就是:
UTC + 时区差 = 本地时间
于是,我们只需要知道时区差就可以转换时间了, 可以利用Date.prototype.getTimezoneOffset()
:
时区偏差(time-zone offset)表示协调世界时(UTC)与本地时区之间的差值,单位为分钟。需要注意的是如果本地时区晚于协调世界时,则该差值为正值,如果早于协调世界时则为负值。 --MDN
var x = new Date();
var currentTimeZoneOffsetInHours = x.getTimezoneOffset() / 60;
然后进行简单的加减即可,不过要注意晚于UTC是正,早是负。
第二种方法
然而这样就结束了吗? No,还有更简单的方法。
从上面的例子中我们还可以看到, new Date()
返回的都是本地时间,所以我们看一下Date.prototype.toString()
:
Return an implementation-dependent String value that represents tv as a date and time in the current time zone using a convenient, human-readable form. --Standard ECMA-262
可以看到toString返回的时间就是基于系统当前所在时区的,即本地时间,所以直接调用toString即可转化为本地时间:
let value = '2018-01-02T00:00+0000'
new Date(value).toString()
然后再将得到的时间字符串解析成ISO格式:
/**
* 把 JS datetime 值转为指定日期时间格式
* @param {string} value - Js datetime string
* @return {string} - ISO Dates YYYY-MM-DDTHH:mm:ss
*/
export function getISOdatetime (value) {
var tmpStr = new Date(value),
year = tmpStr.getFullYear(),
month = pad(tmpStr.getMonth() + 1), // 月份取值0-11
date = pad(tmpStr.getDate()),
hour = pad(tmpStr.getHours()),
minute = pad(tmpStr.getMinutes()),
second = pad(tmpStr.getSeconds())
return `${year}-${month}-${date}T${hour}:${minute}:${second}`
}
/**
* 把 UTC时间 转换成 本地时间
* @param {string} value - UTC datetime string
* @return {string} - ISO Dates YYYY-MM-DDTHH:mm:ss
*/
export function getLocalISODatetime (value) {
return getISOdatetime(new Date(value).toString())
}
本地时间转UTC时间
那本地时间转换成UTC时间也很简单:
new Date() //Wed Jan 24 2018 11:06:29 GMT+0800 (CST)
new Date().toISOString() // "2018-01-24T03:06:22.861Z"
toISOString() 方法返回一个 ISO(ISO 8601 Extended Format)格式的字符串: YYYY-MM-DDTHH:mm:ss.sssZ。时区总是UTC(协调世界时),加一个后缀“Z”标识。 ------ Date.prototype.toISOString() | MDN
哈哈,其实最终的实现很简单,但是要搞清楚原理需要深入了解一下标准是怎么去定义实现的。
参考资料
v1.4.14