为什么约会。解析给出不正确的结果?

案例一:

new Date(Date.parse("Jul 8, 2005"));

输出:

2005年7月8日星期五00:00:00 GMT-0700(太平洋标准时间)

案例二:

new Date(Date.parse("2005-07-08"));

输出:

美国太平洋标准时间2005年7月7日星期四17:00:00


为什么第二个解析不正确?

501242 次浏览

在第5版规范发布之前,Date.parse方法完全是实现依赖 (new Date(string)相当于Date.parse(string),只不过后者返回的是一个数字而不是Date)。在第5版规范中,添加了支持new Date(string)0(也参见new Date(string)1)的需求。但除此之外,对于Date.parse / new Date(string)应该接受的内容,还有new Date(string)2要求,而不是他们必须接受任何Date#toString输出(没有说明那是什么)。

从ECMAScript 2017 (edition 8)开始,实现需要解析Date#toStringDate#toUTCString的输出,但没有指定这些字符串的格式。

截至ECMAScript 2019(版本9),Date#toStringDate#toUTCString的格式已被指定为(分别):

  1. ddd MMM DD YYYY HH:mm:ss ZZ[(时区名)]
    例:2018年7月10日星期二18:39:58 GMT+0530(美国标准时间)
  2. ddd, DD MMM YYYY HH:mm:ss Z
    例:2018年7月10日星期二13:09:58 GMT

提供了另外两种格式,Date.parse应该在新的实现中可靠地解析(注意,支持并不普遍,不兼容的实现将继续使用一段时间)。

我建议手动解析日期字符串,并将日期的构造函数与年、月和日参数一起使用,以避免歧义:

// 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
}

虽然将字符串传递到解析方法的CMS是正确的通常是不安全的,但第15.9.4.2节中的新ECMA-262第五版(又名ES5)规范建议Date.parse()实际上应该处理iso格式的日期。旧的规范没有这样的要求。当然,旧的浏览器和一些当前的浏览器仍然不提供这种ES5功能。

你的第二个例子没有错。它是UTC的指定日期,正如Date.prototype.toISOString()所暗示的那样,但以您的本地时区表示。

这种疯狂是有原因的。作为一般规则,如果浏览器可以将日期解释为ISO-8601,它就会这样做。“2005-07-08”就属于这个阵营,因此它被解析为UTC。“july 8, 2005”不能,所以它是用当地时间解析的。

更多信息请参见JavaScript和日期,真是一团糟!

根据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 md顺序和4位年份来避免日和月顺序的模糊性 +通过使用斜杠避免了UTC与本地问题不符合ISO格式 + danvk, dygraphs的家伙,说这种格式在所有浏览器中都很好。< / p >

另一种解决方案是构建具有日期格式的关联数组,然后重新格式化数据。

此方法对于以不寻常的方式格式化的日期非常有用。

一个例子:

    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日期的内部工作原理进行了大量的斗争。所以,我想在这里发表我的意见。希望分享这些东西能帮助其他人解决浏览器在处理日期方面的差异问题。

输入端

所有实现都在内部将日期值存储为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
  • 在第一种情况下,“Z”说明符表示输入是UTC时间,因此不偏移于epoch,结果为0
  • 在第二种情况中,“-0500”说明符表示输入是GMT-05:00,两个浏览器都将输入解释为-05:00时区。这意味着UTC值从epoch偏移,这意味着将18000000ms添加到日期的内部时间值。
  • 第三种情况,在没有说明符的情况下,应该将被视为主机系统的本地变量。FF正确地将输入视为本地时间,而Chrome将其视为UTC,因此产生不同的时间值。对我来说,这在存储值中产生了5小时的差异,这是有问题的。其他具有不同偏移量的系统将得到不同的结果。

到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函数及其输出内容。注意我的机器上的toUTCStringtoISOString函数在5:00 AM输出。此外,时区名称可能是缩写,并且在不同的实现中可能不同。

在打印之前将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"

我希望这个回答对你有帮助。

下面是一个简短而灵活的代码片段,以跨浏览器安全的方式转换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.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"

我删除了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"

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
}