案例一:
new Date(Date.parse("Jul 8, 2005"));
输出:
2005年7月08日上午00:00 GMT-0700 (PST)
案例二:
new Date(Date.parse("2005-07-08"));
输出:
2005 年 7 月 7 日星期四 17:00:00 GMT-0700 (PST)
为什么第二个解析不正确?
案例一:
new Date(Date.parse("Jul 8, 2005"));
输出:
2005年7月08日上午00:00 GMT-0700 (PST)
案例二:
new Date(Date.parse("2005-07-08"));
输出:
2005 年 7 月 7 日星期四 17:00:00 GMT-0700 (PST)
为什么第二个解析不正确?
当前回答
这种疯狂是有原因的。作为一般规则,如果浏览器可以将日期解释为ISO-8601,它就会这样做。“2005-07-08”就属于这个阵营,因此它被解析为UTC。“july 8, 2005”不能,所以它是用当地时间解析的。
参见JavaScript和日期,真是一团糟!更多信息。
其他回答
Until the 5th edition spec came out, the Date.parse method was completely implementation dependent (new Date(string) is equivalent to Date.parse(string) except the latter returns a number rather than a Date). In the 5th edition spec the requirement was added to support a simplified (and slightly incorrect) ISO-8601 (also see What are valid Date Time Strings in JavaScript?). But other than that, there was no requirement for what Date.parse / new Date(string) should accept other than that they had to accept whatever Date#toString output (without saying what that was).
从ECMAScript 2017 (edition 8)开始,实现需要解析dat# toString和dat# toUTCString的输出,但没有指定这些字符串的格式。
截至ECMAScript 2019(版本9),日期#toString和日期#toUTCString的格式已被指定为(分别):
ddd MMM DD YYYY HH:mm:ss ZZ[(时区名)]2018年7月10日星期二18:39:58 GMT+0530(美国标准时间) ddd, DD MMM YYYY HH:mm:ss Ze.g。2018年7月10日星期二13:09:58 GMT
提供2个以上的格式,日期。Parse应该在新的实现中可靠地解析(注意,支持不是普遍存在的,不兼容的实现将继续使用一段时间)。
我建议手动解析日期字符串,并将date构造函数与年、月和日参数一起使用以避免歧义:
// parse a date in yyyy-mm-dd format
function parseDate(input) {
let parts = input.split('-');
// new Date(year, month [, day [, hours[, minutes[, seconds[, ms]]]]])
return new Date(parts[0], parts[1]-1, parts[2]); // Note: months are 0-based
}
在最近编写JS解释器的经历中,我与ECMA/JS日期的内部工作原理进行了大量的斗争。所以,我想在这里发表我的意见。希望分享这些东西能帮助其他人解决浏览器在处理日期方面的差异问题。
输入端
所有实现都在内部将日期值存储为64位数字,表示自1970-01-01 UTC (GMT与UTC相同)以来的毫秒数(ms)。这个日期是ECMAScript纪元,其他语言(如Java)和POSIX系统(如UNIX)也使用它。纪元之后的日期为正数,纪元之前的日期为负数。
下面的代码在当前所有浏览器中被解释为相同的日期,但有本地时区偏移:
Date.parse('1/1/1970'); // 1 January, 1970
在我所在的时区(EST,是-05:00),结果是18000000,因为这是5小时内的毫秒数(夏令时月份只有4小时)。该值在不同时区会有所不同。此行为在ECMA-262中指定,因此所有浏览器都以相同的方式执行。
虽然主要浏览器将输入字符串格式解析为日期时存在一些差异,但就时区和夏令时而言,它们基本上是相同的,尽管解析很大程度上依赖于实现。
但是,ISO 8601格式是不同的。它是ECMAScript 2015 (ed 6)中明确列出的所有实现必须以相同方式解析的两种格式之一(另一种是Date.prototype.toString指定的格式)。
但是,即使对于ISO 8601格式字符串,一些实现也会出错。下面是Chrome和Firefox的比较输出,当这个答案最初是为1/1/1970(纪元)写在我的机器上使用ISO 8601格式字符串,应该在所有实现中解析为完全相同的值:
Date.parse('1970-01-01T00:00:00Z'); // Chrome: 0 FF: 0
Date.parse('1970-01-01T00:00:00-0500'); // Chrome: 18000000 FF: 18000000
Date.parse('1970-01-01T00:00:00'); // Chrome: 0 FF: 18000000
In the first case, the "Z" specifier indicates that the input is in UTC time so is not offset from the epoch and the result is 0 In the second case, the "-0500" specifier indicates that the input is in GMT-05:00 and both browsers interpret the input as being in the -05:00 timezone. That means that the UTC value is offset from the epoch, which means adding 18000000ms to the date's internal time value. The third case, where there is no specifier, should be treated as local for the host system. FF correctly treats the input as local time while Chrome treats it as UTC, so producing different time values. For me this creates a 5 hour difference in the stored value, which is problematic. Other systems with different offsets will get different results.
到2020年,这一差异已被修复,但在解析ISO 8601格式字符串时,浏览器之间存在其他怪癖。
但还有更糟的。ECMA-262的一个奇怪之处在于,ISO 8601仅限日期格式(YYYY-MM-DD)需要被解析为UTC,而ISO 8601要求它被解析为本地。下面是FF输出的长、短ISO日期格式,没有时区说明符。
Date.parse('1970-01-01T00:00:00'); // 18000000
Date.parse('1970-01-01'); // 0
因此,第一个被解析为本地,因为它是ISO 8601日期和时间,没有时区,第二个被解析为UTC,因为它只有ISO 8601日期。
因此,要直接回答最初的问题,ECMA-262要求“YYYY-MM-DD”解释为UTC,而另一个解释为本地。这就是为什么:
这不会产生等价的结果:
console.log(new Date(Date.parse("Jul 8, 2005")).toString()); // Local
console.log(new Date(Date.parse("2005-07-08")).toString()); // UTC
这样做:
console.log(new Date(Date.parse("Jul 8, 2005")).toString());
console.log(new Date(Date.parse("2005-07-08T00:00:00")).toString());
这是用于解析日期字符串的底线。唯一可以跨浏览器安全地解析的ISO 8601字符串是带偏移量的长格式(±HH:mm或“Z”)。如果这样做,您就可以安全地在本地时间和UTC时间之间来回切换。
这适用于所有浏览器(IE9之后):
console.log(new Date(Date.parse("2005-07-08T00:00:00Z")).toString());
目前大多数浏览器对其他输入格式一视同仁,包括经常使用的'1/1/1970' (M/D/YYYY)和'1/1/1970 00:00:00 AM' (M/D/YYYY hh:mm:ss ap)格式。以下所有格式(除了最后一种格式)在所有浏览器中都被视为本地时间输入。在我所在时区的所有浏览器中,此代码的输出都是相同的。不管主机时区如何,最后一个都被视为-05:00,因为偏移量是在时间戳中设置的:
console.log(Date.parse("1/1/1970"));
console.log(Date.parse("1/1/1970 12:00:00 AM"));
console.log(Date.parse("Thu Jan 01 1970"));
console.log(Date.parse("Thu Jan 01 1970 00:00:00"));
console.log(Date.parse("Thu Jan 01 1970 00:00:00 GMT-0500"));
然而,由于即使是ECMA-262中指定的格式的解析也不一致,因此建议永远不要依赖内置解析器,始终手动解析字符串,例如使用库并将格式提供给解析器。
例如,在moment.js中,你可以这样写:
let m = moment('1/1/1970', 'M/D/YYYY');
输出端
在输出端,所有浏览器都以相同的方式转换时区,但它们处理字符串格式的方式不同。下面是toString函数及其输出内容。注意我的机器上的toUTCString和toISOString函数在早上5点输出。此外,时区名称可能是缩写,并且在不同的实现中可能不同。
在打印之前将UTC时间转换为本地时间
- toString
- toDateString
- toTimeString
- toLocaleString
- toLocaleDateString
- toLocaleTimeString
直接打印存储的UTC时间
- toUTCString
- toISOString
In Chrome
toString Thu Jan 01 1970 00:00:00 GMT-05:00 (Eastern Standard Time)
toDateString Thu Jan 01 1970
toTimeString 00:00:00 GMT-05:00 (Eastern Standard Time)
toLocaleString 1/1/1970 12:00:00 AM
toLocaleDateString 1/1/1970
toLocaleTimeString 00:00:00 AM
toUTCString Thu, 01 Jan 1970 05:00:00 GMT
toISOString 1970-01-01T05:00:00.000Z
In Firefox
toString Thu Jan 01 1970 00:00:00 GMT-05:00 (Eastern Standard Time)
toDateString Thu Jan 01 1970
toTimeString 00:00:00 GMT-0500 (Eastern Standard Time)
toLocaleString Thursday, January 01, 1970 12:00:00 AM
toLocaleDateString Thursday, January 01, 1970
toLocaleTimeString 12:00:00 AM
toUTCString Thu, 01 Jan 1970 05:00:00 GMT
toISOString 1970-01-01T05:00:00.000Z
我通常不使用ISO格式的字符串输入。使用这种格式对我有益的唯一时间是需要将日期排序为字符串的时候。ISO格式可以按原样排序,而其他格式则不能。如果必须具有跨浏览器兼容性,请指定时区或使用兼容的字符串格式。
代码new Date('12/4/2013').toString()经过以下内部伪转换:
"12/4/2013" -> toUCT -> [storage] -> toLocal -> print "12/4/2013"
我希望这个回答对你有帮助。
虽然CMS是正确的,将字符串传递到解析方法通常是不安全的,但新的ECMA-262第5版(又名ES5)规范在15.9.4.2节中建议Date.parse()实际上应该处理iso格式的日期。旧的规范没有这样的要求。当然,旧的浏览器和一些当前的浏览器仍然不提供这种ES5功能。
你的第二个例子没有错。正如date .prototype. toisostring()所暗示的那样,它是以UTC为单位指定的日期,但以本地时区表示。
这种疯狂是有原因的。作为一般规则,如果浏览器可以将日期解释为ISO-8601,它就会这样做。“2005-07-08”就属于这个阵营,因此它被解析为UTC。“july 8, 2005”不能,所以它是用当地时间解析的。
参见JavaScript和日期,真是一团糟!更多信息。
下面是一个简短而灵活的代码片段,以跨浏览器安全的方式转换datetime-string,正如@ drinkin2112所详细描述的那样。
var inputTimestamp = "2014-04-29 13:00:15"; //example
var partsTimestamp = inputTimestamp.split(/[ \/:-]/g);
if(partsTimestamp.length < 6) {
partsTimestamp = partsTimestamp.concat(['00', '00', '00'].slice(0, 6 - partsTimestamp.length));
}
//if your string-format is something like '7/02/2014'...
//use: var tstring = partsTimestamp.slice(0, 3).reverse().join('-');
var tstring = partsTimestamp.slice(0, 3).join('-');
tstring += 'T' + partsTimestamp.slice(3).join(':') + 'Z'; //configure as needed
var timestamp = Date.parse(tstring);
您的浏览器应该提供与Date相同的时间戳结果。解析:
(new Date(tstring)).getTime()