案例一:
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)
为什么第二个解析不正确?
当前回答
两者都是正确的,但它们被解释为两个不同时区的日期。所以你比较了苹果和橘子:
// 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小时,这就是时区的差异,这就是为什么你的测试显示了两个不同的日期。
创建这些相同的本地/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"
但我仍然强烈推荐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"
其他回答
下面是一个简短而灵活的代码片段,以跨浏览器安全的方式转换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()
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
}
使用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也可能会给出不正确的结果。
在最近编写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"
我希望这个回答对你有帮助。
这个轻量级的数据解析库应该可以解决所有类似的问题。我喜欢图书馆,因为它很容易扩展。也有可能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") );