案例一:
new Date(Date.parse("Jul 8, 2005"));
输出:
2005年7月8日 星期五 00:00:00 GMT-0700 (PST)
Case Two:
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月8日 星期五 00:00:00 GMT-0700 (PST)
new Date(Date.parse("2005-07-08"));
2005年7月7日星期四 17:00:00 GMT-0700 (PST)
为什么第二个解析结果不正确?
在第五版规范发布之前,Date.parse
方法是完全依赖于实现的(new Date(string)
等同于 Date.parse(string)
,只不过后者返回一个数字而不是 Date
)。在第五版规范中添加了对简化版(稍有错误)ISO-8601标准的支持(也请参阅JavaScript 中有效的日期时间字符串是什么?)。但除此之外,Date.parse
/new Date(string)
应接受的内容没有其他要求(也没有说明这些内容是什么)。
截至 ECMAScript 2017(第8版),实现必须解析 Date#toString
和 Date#toUTCString
的输出,但未指定这些字符串的格式。
从 ECMAScript 2019(第9版)开始,Date#toString
和 Date#toUTCString
的格式已经被指定为(分别为):
为新实现提供了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
无法处理英国日期格式,而我又找不出原因。 - Benreturn new Date(parts[0], parts[1] - 1, parts[2], parts[3], parts[4], parts[5]);
完美地运行,谢谢! - Richard Rout最近编写JS解释器的经验中,我与ECMA/JS日期的内部工作进行了大量搏斗。因此,我想在这里分享我的2分钱。希望分享这些内容能够帮助其他人解决有关浏览器处理日期的差异的任何问题。
所有实现都将其日期值以64位数字的形式存储在内部,表示自1970年01月01日UTC(GMT与UTC相同)以来的毫秒数(ms)。这个日期是ECMAScript纪元,也被其他语言如Java和POSIX系统(如UNIX)所使用。纪元后发生的日期是正数,而纪元前则是负数。
以下代码在所有当前浏览器中被解释为相同的日期,但具有本地时区偏移:
Date.parse('1/1/1970'); // 1 January, 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
这个差异已经在2020年得到修复,但在解析ISO 8601格式字符串时,浏览器之间存在其他怪癖。
但情况变得更糟。ECMA-262的一个怪癖是,ISO 8601仅包含日期格式(YYYY-MM-DD)需要被解析为UTC,而ISO 8601要求它被解析为本地时间。以下是使用长和短ISO日期格式且没有时区指示符的FF输出。
Date.parse('1970-01-01T00:00:00'); // 18000000
Date.parse('1970-01-01'); // 0
因为第一个是ISO 8601日期和时间,没有时区,所以被解析为本地时间,而第二个是ISO 8601日期,所以被解析为UTC。
因此,直接回答原始问题,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());
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:00。此外,时区名称可能是缩写,并且在不同的实现中可能不同。
在打印之前将时间从UTC转换为本地时间
- toString
- toDateString
- toTimeString
- toLocaleString
- toLocaleDateString
- toLocaleTimeString
直接打印存储的UTC时间
- toUTCString
- toISOString
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"
我希望这个答案对你有所帮助。
这种方法有一定的规律。通常,如果浏览器可以将日期解释为ISO-8601格式,则会这样做。"2005-07-08"符合此条件,因此被解析为UTC时间。而"Jul 8, 2005"则不行,它会被解析为本地时间。
使用moment.js解析日期:
var caseOne = moment("Jul 8, 2005", "MMM D, YYYY", true).toDate();
var caseTwo = moment("2005-07-08", "YYYY-MM-DD", true).toDate();
另一个解决方案是构建一个具有日期格式的关联数组,然后重新格式化数据。
这种方法适用于以不寻常方式格式化的日期。
例如:
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'];
虽然CMS是正确的,传递字符串到解析方法通常是不安全的,但新的ECMA-262第5版(也称为ES5)规范在第15.9.4.2节中建议Date.parse()
实际上应该处理ISO格式的日期。旧规范没有这样的说法。当然,旧浏览器和一些当前浏览器仍然不提供此ES5功能。
你的第二个示例并没有错。它是UTC指定日期,如Date.prototype.toISOString()
所示,但以你的本地时区表示。
两种方式都是正确的,但它们被解释为带有两个不同时区的日期。所以你比较的是苹果和橙子:
// 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"
Date.parse()
的调用,因为它会自动用于字符串参数。我还使用ISO8601格式比较日期,这样您可以在本地日期和UTC日期之间直观地进行比较。这些时间相差7个小时,这是时区差异,也是您的测试显示两个不同日期的原因。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"
但我仍然强烈推荐Moment.js,它既简单又强大:Moment.js文档
// 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"
以下是一段简短灵活的代码,可以跨浏览器地将日期时间字符串转换成格式详细说明的方式,由@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()
这个轻量级的日期解析库(flexible-js-formatting)可以解决所有类似的问题。我喜欢这个库,因为它非常易于扩展。它也可以进行国际化(不是很直观,但也不难)。
解析示例:
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") );
NaN
日期,我发现大多数其他浏览器(和Node.js)将解析没有日期的日期,例如“2014年4月”作为2014年4月1日,但Firefox返回NaN。您必须传递正确的日期。 - Jazzy