Javascript 为什么 Date.parse 给出不正确的结果?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2587345/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why does Date.parse give incorrect results?
提问by user121196
Case One:
案例一:
new Date(Date.parse("Jul 8, 2005"));
Output:
输出:
Fri Jul 08 2005 00:00:00 GMT-0700 (PST)
2005 年 7 月 8 日星期五 00:00:00 GMT-0700 (PST)
Case Two:
案例二:
new Date(Date.parse("2005-07-08"));
Output:
输出:
Thu Jul 07 2005 17:00:00 GMT-0700 (PST)
2005 年 7 月 7 日星期四 17:00:00 GMT-0700 (PST)
Why is the second parse incorrect?
为什么第二次解析不正确?
回答by CMS
Until the 5th edition spec came out, the Date.parsemethod 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 norequirement for what Date.parse/ new Date(string)should accept other than that they had to accept whatever Date#toStringoutput (without saying what that was).
在第 5 版规范出来之前,该Date.parse方法完全依赖于实现(new Date(string)相当于Date.parse(string)除了后者返回一个数字而不是 a Date)。在第 5 版规范中,添加了要求以支持简化的(并且稍微不正确)ISO-8601(另请参阅JavaScript 中的有效日期时间字符串是什么?)。但除此之外,除了他们必须接受任何输出(不说那是什么)之外,没有要求Date.parse/new Date(string)应该接受Date#toString什么。
As of ECMAScript 2017 (edition 8), implementations were required to parse their output for Date#toStringand Date#toUTCString, but the format of those strings was not specified.
从 ECMAScript 2017(第 8 版)开始,实现需要为Date#toString和解析其输出Date#toUTCString,但未指定这些字符串的格式。
As of ECMAScript 2019 (edition 9) the format for Date#toStringand Date#toUTCString, have been specified as (respectively):
自 ECMAScript 2019(第 9 版)起,Date#toString和的格式Date#toUTCString已指定为(分别):
- ddd MMM DD YYYY HH:mm:ss ZZ [(timezone name)]
e.g. Tue Jul 10 2018 18:39:58 GMT+0530 (IST) - ddd, DD MMM YYYY HH:mm:ss Z
e.g. Tue 10 Jul 2018 13:09:58 GMT
- ddd MMM DD YYYY HH:mm:ss ZZ [(时区名称)]
例如,2018 年 7 月 10 日星期二 18:39:58 GMT+0530 (IST) - ddd, DD MMM YYYY HH:mm:ss Z
例如 2018 年 7 月 10 日星期二 13:09:58 GMT
providing 2 more formats that Date.parseshould parse reliably in new implementations (noting that support is not ubiquitous and non–compliant implementations will remain in use for some time).
提供另外 2 种格式,这些格式Date.parse应该在新的实现中可靠地解析(注意支持并不普遍,不兼容的实现将继续使用一段时间)。
I would recommend that date strings are parsed manually and the Date constructorused with year, month and day arguments to avoid ambiguity:
我建议手动解析日期字符串,并将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
}
回答by drankin2112
During recent experience writing a JS interpreter I wrestled plenty with the inner workings of ECMA/JS dates. So, I figure I'll throw in my 2 cents here. Hopefully sharing this stuff will help others with any questions about the differences among browsers in how they handle dates.
在最近编写 JS 解释器的经历中,我对 ECMA/JS 日期的内部工作进行了大量研究。所以,我想我会在这里投入我的 2 美分。希望分享这些内容可以帮助其他人解决有关浏览器在处理日期方面的差异的任何问题。
The Input Side
输入端
All implementations store their date values internally as 64-bit numbers that represent the number of milliseconds (ms) since 1970-01-01 UTC (GMT is the same thing as UTC). This date is the ECMAScript epoch that is also used by other languages such as Java and POSIX systems such as UNIX. Dates occurring after the epoch are positive numbers and dates prior are negative.
所有实现都在内部将其日期值存储为 64 位数字,这些数字表示自 1970-01-01 UTC 以来的毫秒数 (ms)(GMT 与 UTC 相同)。这个日期是 ECMAScript 纪元,也被其他语言(如 Java 和 POSIX 系统(如 UNIX))使用。纪元之后的日期为正数,之前的日期为负数。
The following code is interpreted as the same date in all current browsers, but with the local timezone offset:
以下代码在所有当前浏览器中被解释为相同的日期,但具有本地时区偏移:
Date.parse('1/1/1970'); // 1 January, 1970
In my timezone (EST, which is -05:00), the result is 18000000 because that's how many ms are in 5 hours (it's only 4 hours during daylight savings months). The value will be different in different time zones. This behaviour is specified in ECMA-262 so all browsers do it the same way.
在我的时区(美国东部标准时间,即 -05:00)中,结果是 18000000,因为这是 5 小时内的毫秒数(夏令时月份只有 4 小时)。不同时区的值会有所不同。此行为在 ECMA-262 中指定,因此所有浏览器都以相同的方式执行此操作。
While there is some variance in the input string formats that the major browsers will parse as dates, they essentially interpret them the same as far as time zones and daylight saving is concerned even though parsing is largely implementation dependent.
尽管主要浏览器将解析为日期的输入字符串格式存在一些差异,但就时区和夏令时而言,它们基本上将它们解释为相同的,即使解析在很大程度上取决于实现。
However, the ISO 8601 format is different. It's one of only two formats outlined in ECMAScript 2015 (ed 6) specifically that must be parsed the same way by all implementations (the other is the format specified for Date.prototype.toString).
但是,ISO 8601 格式不同。它是 ECMAScript 2015(第 6 版)中概述的仅有的两种格式之一,所有实现都必须以相同的方式解析(另一种是为Date.prototype.toString指定的格式)。
But, even for ISO 8601 format strings, some implementations get it wrong. Here is a comparison output of Chrome and Firefox when this answer was originally written for 1/1/1970 (the epoch) on my machine using ISO 8601 format strings that shouldbe parsed to exactly the same value in all implementations:
但是,即使对于 ISO 8601 格式字符串,某些实现也会出错。这是 Chrome 和 Firefox 的比较输出,当这个答案最初是在我的机器上使用 ISO 8601 格式字符串为 1/1/1970(纪元)编写的,在所有实现中这些字符串应该被解析为完全相同的值:
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, shouldbe 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.
- 在第一种情况下,“Z”说明符表示输入采用 UTC 时间,因此与纪元没有偏移,结果为 0
- 在第二种情况下,“-0500”说明符表示输入在 GMT-05:00 并且两个浏览器都将输入解释为在 -05:00 时区。这意味着 UTC 值与纪元有偏移,这意味着将 18000000 毫秒添加到日期的内部时间值。
- 第三种情况,没有说明符,应该被视为主机系统的本地。FF 正确地将输入视为本地时间,而 Chrome 将其视为 UTC,因此产生不同的时间值。对我来说,这会在存储值中产生 5 小时的差异,这是有问题的。其他具有不同偏移量的系统会得到不同的结果。
This difference has been fixed as of 2020, but other quirks exist between browsers when parsing ISO 8601 format strings.
自 2020 年起,此差异已得到修复,但在解析 ISO 8601 格式字符串时,浏览器之间还存在其他问题。
But it gets worse. A quirk of ECMA-262 is that the ISO 8601 date–only format (YYYY-MM-DD) is required to be parsed as UTC, whereas ISO 8601 requires it to be parsed as local. Here is the output from FF with the long and short ISO date formats with no time zone specifier.
但情况会变得更糟。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
So the first is parsed as local because it's ISO 8601 date and time with no timezone, and the second is parsed as UTC because it's ISO 8601 date only.
所以第一个被解析为本地,因为它是没有时区的 ISO 8601 日期和时间,第二个被解析为 UTC,因为它只是 ISO 8601 日期。
So, to answer the original question directly, "YYYY-MM-DD"is required by ECMA-262 to be interpreted as UTC, while the other is interpreted as local. That's why:
因此,要直接回答原始问题,"YYYY-MM-DD"ECMA-262 要求将其解释为 UTC,而另一个则解释为本地。这就是为什么:
This doesn't produce equivalent results:
这不会产生等效的结果:
console.log(new Date(Date.parse("Jul 8, 2005")).toString()); // Local
console.log(new Date(Date.parse("2005-07-08")).toString()); // UTC
This does:
这样做:
console.log(new Date(Date.parse("Jul 8, 2005")).toString());
console.log(new Date(Date.parse("2005-07-08T00:00:00")).toString());
The bottom line is this for parsing date strings. The ONLY ISO 8601 string that you can safely parse across browsers is the long form with an offset(either ±HH:mm or "Z"). If you do that you can safely go back and forth between local and UTC time.
底线是用于解析日期字符串。唯一可以跨浏览器安全解析的 ISO 8601 字符串是带有偏移量(±HH:mm 或“Z”)的长格式。如果这样做,您可以安全地在本地时间和 UTC 时间之间来回切换。
This works across browsers (after IE9):
这适用于跨浏览器(IE9 之后):
console.log(new Date(Date.parse("2005-07-08T00:00:00Z")).toString());
Most current browsers do treat the other input formats equally, including the frequently used '1/1/1970' (M/D/YYYY) and '1/1/1970 00:00:00 AM' (M/D/YYYY hh:mm:ss ap) formats. All of the following formats (except the last) are treated as local time input in all browsers. The output of this code is the same in all browsers in my timezone. The last one is treated as -05:00 regardless of the host timezone because the offset is set in the timestamp:
大多数当前浏览器确实平等对待其他输入格式,包括常用的“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"));
However, since parsing of even the formats specified in ECMA-262 is not consistent, it is recommended to never rely on the built–in parser and to always manually parse strings, say using a library and provide the format to the parser.
但是,即使解析 ECMA-262 中指定的格式也不一致,因此建议永远不要依赖内置解析器并始终手动解析字符串,例如使用库并将格式提供给解析器。
E.g. in moment.js you might write:
例如,在 moment.js 中,您可能会这样写:
let m = moment('1/1/1970', 'M/D/YYYY');
The Output Side
输出端
On the output side, all browsers translate time zones the same way but they handle the string formats differently. Here are the toStringfunctions and what they output. Notice the toUTCStringand toISOStringfunctions output 5:00 AM on my machine. Also, the timezone name may be an abbreviation and may be different in different implementations.
在输出端,所有浏览器都以相同的方式转换时区,但它们处理字符串格式的方式不同。这是toString函数及其输出的内容。请注意我机器上 5:00 AM的toUTCString和toISOString函数输出。此外,时区名称可能是缩写,在不同的实现中可能会有所不同。
Converts from UTC to Local time before printing
在打印前从 UTC 转换为本地时间
- toString
- toDateString
- toTimeString
- toLocaleString
- toLocaleDateString
- toLocaleTimeString
Prints the stored UTC time directly
直接打印存储的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
I normally don't use the ISO format for string input. The only time that using that format is beneficial to me is when dates need to be sorted as strings. The ISO format is sortable as-is while the others are not. If you have to have cross-browser compatibility, either specify the timezone or use a compatible string format.
我通常不使用 ISO 格式进行字符串输入。使用该格式对我有益的唯一时间是需要将日期排序为字符串。ISO 格式可以按原样排序,而其他格式则不能。如果您必须具有跨浏览器兼容性,请指定时区或使用兼容的字符串格式。
The code new Date('12/4/2013').toString()goes through the following internal pseudo-transformation:
代码new Date('12/4/2013').toString()经过以下内部伪转换:
"12/4/2013" -> toUCT -> [storage] -> toLocal -> print "12/4/2013"
I hope this answer was helpful.
我希望这个答案有帮助。
回答by danvk
There is some method to the madness. As a general rule, if a browser can interpret a date as an ISO-8601, it will. "2005-07-08" falls into this camp, and so it is parsed as UTC. "Jul 8, 2005" cannot, and so it is parsed in the local time.
有一些疯狂的方法。作为一般规则,如果浏览器可以将日期解释为 ISO-8601,它就会。“2005-07-08”属于这个阵营,所以解析为UTC。“Jul 8, 2005”不能,所以它是在本地时间解析的。
See JavaScript and Dates, What a Mess!for more.
回答by guido rizzi
Another solution is to build an associative array with date format and then reformat data.
另一种解决方案是使用日期格式构建关联数组,然后重新格式化数据。
This method is useful for date formatted in an unussual way.
此方法对于以不寻常方式格式化的日期很有用。
An example:
一个例子:
mydate='01.02.12 10:20:43':
myformat='dd/mm/yy HH:MM:ss';
dtsplit=mydate.split(/[\/ .:]/);
dfsplit=myformat.split(/[\/ .:]/);
// creates assoc array for date
df = new Array();
for(dc=0;dc<6;dc++) {
df[dfsplit[dc]]=dtsplit[dc];
}
// uses assc array for standard mysql format
dstring[r] = '20'+df['yy']+'-'+df['mm']+'-'+df['dd'];
dstring[r] += ' '+df['HH']+':'+df['MM']+':'+df['ss'];
回答by Lukasz Wiktor
Use moment.jsto parse dates:
使用moment.js解析日期:
var caseOne = moment("Jul 8, 2005", "MMM D, YYYY", true).toDate();
var caseTwo = moment("2005-07-08", "YYYY-MM-DD", true).toDate();
The 3rd argument determines strict parsing (available as of 2.3.0). Without it moment.js may also give incorrect results.
第三个参数确定严格解析(从 2.3.0 开始可用)。没有它 moment.js 也可能给出不正确的结果。
回答by Juan Lanus
According to http://blog.dygraphs.com/2012/03/javascript-and-dates-what-mess.htmlthe format "yyyy/mm/dd" solves the usual problems. He says: "Stick to "YYYY/MM/DD" for your date strings whenever possible. It's universally supported and unambiguous. With this format, all times are local." I've set tests: http://jsfiddle.net/jlanus/ND2Qg/432/This format: + avoids the day and month order ambiguity by using y m d ordering and a 4-digit year + avoids the UTC vs. local issue not complying with ISO format by using slashes + danvk, the dygraphsguy, says that this format is good in all browsers.
根据http://blog.dygraphs.com/2012/03/javascript-and-dates-what-mess.html格式“yyyy/mm/dd”解决了通常的问题。他说:“尽可能坚持使用“YYYY/MM/DD”作为日期字符串。它得到普遍支持且明确无误。使用这种格式,所有时间都是本地的。” 我已经设置了测试:http: //jsfiddle.net/jlanus/ND2Qg/432/这种格式:+ 通过使用 ymd 排序和 4 位年份避免了日和月顺序的歧义 + 避免了 UTC 与本地问题不通过使用斜杠 + danvk 来遵守 ISO 格式,dygraphs人员说这种格式在所有浏览器中都很好。
回答by peller
While CMS is correctthat passing strings into the parse method is generally unsafe, the new ECMA-262 5th Edition(aka ES5) specification in section 15.9.4.2 suggests that Date.parse()actually should handle ISO-formatted dates. The old specification made no such claim. Of course, old browsers and some current browsers still do not provide this ES5 functionality.
虽然CMS 正确地认为将字符串传递到 parse 方法通常是不安全的,但新的ECMA-262 第 5 版(又名 ES5)规范第 15.9.4.2 节建议Date.parse()实际上应该处理 ISO 格式的日期。旧的规范没有提出这样的要求。当然,旧的浏览器和一些当前的浏览器仍然没有提供这个 ES5 功能。
Your second example isn't wrong. It is the specified date in UTC, as implied by Date.prototype.toISOString(), but is represented in your local timezone.
你的第二个例子没有错。它是 UTC 中的指定日期,如 暗示的那样Date.prototype.toISOString(),但以您当地的时区表示。
回答by Nux
This light weight date parsing libraryshould solve all similar problems. I like the library because it is quite easy to extend. It's also possible to i18n it (not very straight forward, but not that hard).
这个轻量级的日期解析库应该可以解决所有类似的问题。我喜欢这个库,因为它很容易扩展。也可以 i18n 它(不是很直接,但不是那么难)。
Parsing example:
解析示例:
var caseOne = Date.parseDate("Jul 8, 2005", "M d, Y");
var caseTwo = Date.parseDate("2005-07-08", "Y-m-d");
And formatting back to string (you will notice both cases give exactly the same result):
并格式化回字符串(您会注意到两种情况给出完全相同的结果):
console.log( caseOne.dateFormat("M d, Y") );
console.log( caseTwo.dateFormat("M d, Y") );
console.log( caseOne.dateFormat("Y-m-d") );
console.log( caseTwo.dateFormat("Y-m-d") );
回答by Lorenz Lo Sauer
Here is a short, flexible snippet to convert a datetime-string in a cross-browser-safe fashion as nicel detailed by @drankin2112.
这是一个简短而灵活的片段,用于以跨浏览器安全的方式转换日期时间字符串,如@drankin2112 所详述。
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);
Your browser should provide the same timestamp result as Date.parsewith:
您的浏览器应提供与以下内容相同的时间戳结果Date.parse:
(new Date(tstring)).getTime()
回答by DJDaveMark
Both are correct, but they are being interpreted as dates with two different timezones. So you compared apples and oranges:
两者都是正确的,但它们被解释为具有两个不同时区的日期。所以你比较了苹果和橙子:
// local dates
new Date("Jul 8, 2005").toISOString() // "2005-07-08T07:00:00.000Z"
new Date("2005-07-08T00:00-07:00").toISOString() // "2005-07-08T07:00:00.000Z"
// UTC dates
new Date("Jul 8, 2005 UTC").toISOString() // "2005-07-08T00:00:00.000Z"
new Date("2005-07-08").toISOString() // "2005-07-08T00:00:00.000Z"
I removed the Date.parse()call since it's used automatically on a string argument. I also compared the dates using ISO8601 formatso you could visually compare the dates between your local dates and the UTC dates. The times are 7 hours apart, which is the timezone difference and why your tests showed two different dates.
我删除了Date.parse()调用,因为它自动用于字符串参数。我还使用ISO8601 格式比较了日期,以便您可以直观地比较本地日期和 UTC 日期之间的日期。时间相差 7 小时,这是时区差异以及您的测试显示两个不同日期的原因。
The other way of creating these same local/UTC dates would be:
创建这些相同的本地/UTC 日期的另一种方法是:
new Date(2005, 7-1, 8) // "2005-07-08T07:00:00.000Z"
new Date(Date.UTC(2005, 7-1, 8)) // "2005-07-08T00:00:00.000Z"
But I still strongly recommend Moment.jswhich is as simple yet powerful:
// parse string
moment("2005-07-08").format() // "2005-07-08T00:00:00+02:00"
moment.utc("2005-07-08").format() // "2005-07-08T00:00:00Z"
// year, month, day, etc.
moment([2005, 7-1, 8]).format() // "2005-07-08T00:00:00+02:00"
moment.utc([2005, 7-1, 8]).format() // "2005-07-08T00:00:00Z"

