提问人:user121196 提问时间:4/7/2010 最后编辑:RobGuser121196 更新时间:4/8/2023 访问量:510488
为什么 Date.parse 给出不正确的结果?
Why does Date.parse give incorrect results?
问:
案例一:
new Date(Date.parse("Jul 8, 2005"));
输出:
2005 年 7 月 8 日星期五 00:00:00 GMT-0700 (PST)
案例二:
new Date(Date.parse("2005-07-08"));
输出:
2005 年 7 月 7 日星期四 17:00:00 GMT-0700 (PST)
为什么第二次解析不正确?
答:
在第 5 版规范发布之前,Date.parse 方法完全依赖于实现(相当于 Date.parse(string),
但后者返回一个数字而不是 )。在第 5 版规范中,添加了支持简化(且略有不正确)的 ISO-8601 的要求(另请参阅什么是 JavaScript 中的有效日期时间字符串?但除此之外,除了他们必须接受任何输出(没有说那是什么)之外,没有要求/应该接受什么。
new Date(string)
Date
Date.parse
new Date(string)
Date#toString
从 ECMAScript 2017(第 8 版)开始,需要实现来解析 和 的输出,但未指定这些字符串的格式。Date#toString
Date#toUTCString
从 ECMAScript 2019(第 9 版)开始,Date#toString
和 Date#toUTCString
的格式已分别指定为:
- ddd MMM DD YYYY HH:mm:ss ZZ [(时区名称)]
,例如 Tue Jul 10, 2018 18:39:58 GMT+0530 (IST) - DDD,DD MMM YYYY HH:mm:ss Z,
例如Tue 10 Jul 2018 13:09:58 GMT
提供另外 2 种格式,这些格式应该在新的实现中可靠地解析(请注意,支持并非无处不在,不合规的实现将在一段时间内继续使用)。Date.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
}
评论
Date.parse
return new Date(parts[0], parts[1] - 1, parts[2], parts[3], parts[4], parts[5]);
虽然 CMS 正确地将字符串传递到 parse 方法中通常是不安全的,但第 15.9.4.2 节中的新 ECMA-262 第 5 版(又名 ES5)规范表明,实际上应该处理 ISO 格式的日期。旧规范没有提出这样的要求。当然,旧浏览器和一些当前的浏览器仍然不提供这个 ES5 功能。Date.parse()
你的第二个例子没有错。它是 UTC 中的指定日期,如 所暗示的那样,但以本地时区表示。Date.prototype.toISOString()
评论
疯狂是有方法的。作为一般规则,如果浏览器可以将日期解释为 ISO-8601,则它会。“2005-07-08”属于这个阵营,因此它被解析为UTC。“Jul 8, 2005”不能,因此它以当地时间进行解析。
有关更多信息,请参见 JavaScript 和 Dates, What a Mess!。
评论
根据 http://blog.dygraphs.com/2012/03/javascript-and-dates-what-mess.html 格式“yyyy/mm/dd”解决了常见问题。 他说:“尽可能坚持使用”YYYY/MM/DD“作为日期字符串。它得到了普遍支持和明确。使用这种格式,所有时间都是本地时间。 我已经设置了测试: http://jsfiddle.net/jlanus/ND2Qg/432/ 这种格式: + 通过使用 y m d 排序和 4 位数字年份来避免日和月顺序歧义 + 通过使用斜杠避免 UTC 与本地问题不符合 ISO 格式 + Dygraphs 的家伙 Danvk 说这种格式在所有浏览器中都很好。
评论
另一种解决方案是构建一个具有日期格式的关联数组,然后重新格式化数据。
此方法对于以非连续方式格式化的日期很有用。
举个例子:
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'];
这个轻量级的日期解析库应该可以解决所有类似的问题。我喜欢这个库,因为它很容易扩展。也可以 i18n 它(不是很直接,但不是那么难)。
解析示例:
var caseOne = Date.parseDate("Jul 8, 2005", "M d, Y");
var caseTwo = Date.parseDate("2005-07-08", "Y-m-d");
并格式化回字符串(您会注意到两种情况给出完全相同的结果):
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") );
评论
在最近编写 JS 解释器的经历中,我与 ECMA/JS 日期的内部工作原理进行了大量斗争。所以,我想我会在这里投入我的 2 美分。希望分享这些东西能帮助其他人解决有关浏览器之间处理日期差异的任何问题。
输入端
所有实现在内部将其日期值存储为 64 位数字,表示自 1970-01-01 UTC(GMT 与 UTC 相同)以来的毫秒 (ms) 数。此日期是 ECMAScript 纪元,其他语言(如 Java)和 POSIX 系统(如 UNIX)也使用该纪元。纪元之后的日期为正数,前一天的日期为负数。
以下代码在所有当前浏览器中被解释为相同的日期,但具有本地时区偏移量:
Date.parse('1/1/1970'); // 1 January, 1970
在我的时区(美国东部标准时间,即 -05:00),结果是 18000000,因为这是 5 小时内的毫秒数(夏令时月份只有 4 小时)。该值在不同的时区会有所不同。此行为在 ECMA-262 中指定,因此所有浏览器都以相同的方式执行此操作。
虽然主要浏览器将解析为日期的输入字符串格式存在一些差异,但就时区和夏令时而言,它们基本上对它们的解释相同,即使解析在很大程度上取决于实现。
但是,ISO 8601 格式是不同的。它是 ECMAScript 2015 (ed 6) 中概述的仅有的两种格式之一,所有实现都必须以相同的方式解析(另一种是为 Date.prototype.toString 指定的格式)。
但是,即使对于 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
- 在第一种情况下,“Z”说明符指示输入采用 UTC 时间,因此不偏离纪元,结果为 0
- 在第二种情况下,“-0500”说明符指示输入在 GMT-05:00 中,并且两个浏览器都将输入解释为 -05:00 时区。这意味着 UTC 值与纪元相偏,这意味着在日期的内部时间值上增加 18000000 毫秒。
- 第三种情况(没有说明符)应被视为主机系统的本地情况。FF 正确地将输入视为本地时间,而 Chrome 将其视为 UTC,因此生成不同的时间值。对我来说,这会在存储值中产生 5 小时的差异,这是有问题的。具有不同偏移量的其他系统将获得不同的结果。
自 2020 年起,这种差异已得到修复,但在解析 ISO 8601 格式字符串时,浏览器之间还存在其他怪癖。
但情况变得更糟。ECMA-262 的一个怪癖是 ISO 8601 仅日期格式 (YYYY-MM-DD) 需要解析为 UTC,而 ISO 8601 要求将其解析为本地格式。这是 FF 的输出,其中包含没有时区说明符的长 ISO 日期格式和短 ISO 日期格式。
Date.parse('1970-01-01T00:00:00'); // 18000000
Date.parse('1970-01-01'); // 0
因此,第一个被解析为本地,因为它是 ISO 8601 日期和时间,没有时区,第二个被解析为 UTC,因为它只是 ISO 8601 日期。
因此,要直接回答原始问题,ECMA-262 要求将其解释为 UTC,而另一个则被解释为本地问题。原因如下:"YYYY-MM-DD"
这不会产生等效的结果:
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');
输出端
在输出端,所有浏览器都以相同的方式转换时区,但它们处理字符串格式的方式不同。以下是函数及其输出内容。请注意,在我的机器上,凌晨 5:00 输出 和函数。此外,时区名称可能是缩写,并且在不同的实现中可能不同。toString
toUTCString
toISOString
打印前从 UTC 转换为本地时间
- toString
- toDateString
- toTimeString
- toLocaleString
- toLocaleDateString
- toLocaleTimeString
直接打印存储的 UTC 时间
- toUTCString
- toISOString
在 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
在 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" -> toUTC -> [storage] -> toLocal -> print "12/4/2013"
我希望这个答案对您有所帮助。
评论
这是一个简短而灵活的代码片段,用于以跨浏览器安全的方式转换日期时间字符串,如 @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);
您的浏览器应提供与以下内容相同的时间戳结果:Date.parse
(new Date(tstring)).getTime()
评论
使用 moment.js 解析日期:
var caseOne = moment("Jul 8, 2005", "MMM D, YYYY", true).toDate();
var caseTwo = moment("2005-07-08", "YYYY-MM-DD", true).toDate();
第三个参数确定严格解析(从 2.3.0 开始可用)。如果没有它,moment.js 也可能会给出不正确的结果。
两者都是正确的,但它们被解释为具有两个不同时区的日期。所以你比较了苹果和橙子:
// 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"
我删除了该调用,因为它会自动用于字符串参数。我还使用ISO8601格式比较了日期,以便您可以直观地比较本地日期和 UTC 日期之间的日期。时间相隔 7 小时,这是时区差异,也是您的测试显示两个不同日期的原因。Date.parse()
创建这些相同的本地/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"
// 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"
CMS接受的答案是正确的,我刚刚添加了一些功能:
- 修剪和清理输入空间
- 解析斜杠、破折号、冒号和空格
- 具有默认日期和时间
// parse a date time that can contains spaces, dashes, slashes, colons
function parseDate(input) {
// trimes and remove multiple spaces and split by expected characters
var parts = input.trim().replace(/ +(?= )/g,'').split(/[\s-\/:]/)
// new Date(year, month [, day [, hours[, minutes[, seconds[, ms]]]]])
return new Date(parts[0], parts[1]-1, parts[2] || 1, parts[3] || 0, parts[4] || 0, parts[5] || 0); // Note: months are 0-based
}
评论
NaN