为什么Date.parse会给出错误的结果?

时间:2010-04-06 18:35:53

标签: javascript date

<案例一:
new Date(Date.parse("Jul 8, 2005"));

输出:

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

案例二:

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

输出:

Thu Jul 07 2005 17:00:00 GMT-0700(太平洋标准时间)


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

11 个答案:

答案 0 :(得分:423)

在第5版规范出来之前,Date.parse方法完全依赖实现new Date(string)相当于Date.parse(string),除了后者返回数字而不是Date)。在第5版规范中,添加了一项要求以支持simplified (and slightly incorrect) ISO-8601(另请参阅What are valid Date Time Strings in JavaScript?)。但除此之外,{em>没有要求Date.parse / new Date(string)除了必须接受任何 Date#toString 输出之外应该接受的内容(不说那是什么)。

从ECMAScript 2017(第8版)开始,需要实现解析 Date#toString Date#toUTCString 的输出,但这些字符串的格式不是指定。

自ECMAScript 2019(第9版)起,Date#toStringDate#toUTCString的格式已分别指定为:

  1. ddd MMM DD YYYY HH:mm:ss ZZ [(时区名称)]
    例如2018年7月10日星期二18:39:58 GMT + 0530(IST)
  2. ddd,DD MMM YYYY HH:mm:ss Z
    例如2018年7月10日星期二13:09:58 GMT
  3. 提供另外两种格式, Date.parse 应该在新实现中可靠地解析(注意支持不是普遍存在,不兼容的实现将在一段时间内保持使用)。

    我建议手动解析日期字符串,并将Date constructor与年,月和日参数一起使用,以避免含糊不清:

    // parse a date in yyyy-mm-dd format
    function parseDate(input) {
      var 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
    }
    

答案 1 :(得分:186)

在最近编写JS解释器的经历中,我与ECMA / JS日期的内部工作进行了充分的斗争。所以,我想我会在这里投入2美分。希望分享这些内容可以帮助其他人解决浏览器在处理日期方面的差异。

输入端

所有实现在内部将它们的日期值存储为64位数字,表示自1970年1月1日UTC以来的毫秒数(GMT与UTC相同)。在1/1/1970 00:00:00之后发生的日期为正数且日期之前的日期为负数。

因此,以下代码会在所有浏览器上生成完全相同的结果。

Date.parse('1/1/1970');

在我的时区(EST)中,结果是18000000,因为这是5小时内的毫秒数(在夏令时期间只有4小时)。不同时区的价值会有所不同。所有主流浏览器都采用相同的方式。

虽然这很简单。虽然主要浏览器将作为日期解析的输入字符串格式存在一些差异,但就时区和夏令时而言,它们基本上将它们解释为相同。坚持的是ISO 8601格式。这是ECMA-262 v.5规范中列出的唯一格式。对于所有其他字符串格式,解释依赖于实现。具有讽刺意味的是,这是浏览器可以区分的格式。以下是我的机器上使用ISO 8601字符串格式的Chrome与Firefox在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时间,并且在存储之前不需要偏移。
  • “-0500”说明符表示输入是GMT-05:00所以两者都是 浏览器将输入解释为在我的本地时区。这意味着 值在存储之前转换为UTC。在我的情况下,这意味着将18000000ms添加到日期的内部值,因此需要-18000000ms(-05:00)班次才能让我回到当地时间。
  • 但是当没有说明符时,FF会将输入视为Chrome时的本地时间 将其视为UTC时间。对我来说,这会在存储值上产生5小时的差异,这是有问题的。在我的实现中,我最终支持FF,因为我喜欢toString的输出以匹配我的输入值,除非我指定一个备用时区,我从不这样做。说明符的缺席应该假定本地时间输入。

但是现在情况变得更糟,FF对待ISO 8601格式(“YYYY-MM-DD”)的缩写形式与对待长形式(“YYYY-MM-DDTHH:mm:ss:sssZ”不同“)没有任何合理的理由。以下是FF的输出,其中包含长短ISO日期格式,没有时区说明符。

Date.parse('1970-01-01T00:00:00');       // 18000000
Date.parse('1970-01-01');                // 0

因此,要直接回答原始提问者的问题,"YYYY-MM-DD"是ISO 8601格式"YYYY-MM-DDTHH:mm:ss:sssZ"的缩写形式。因此,它被解释为UTC时间,而另一个被解释为本地时间。这就是原因,

这不是jive:

console.log(new Date(Date.parse("Jul 8, 2005")).toString());
console.log(new Date(Date.parse("2005-07-08")).toString());

这样做:

console.log(new Date(Date.parse("Jul 8, 2005")).toString());
console.log(new Date(Date.parse("2005-07-08T00:00:00")).toString());

底线是解析日期字符串的底线。您可以安全地在浏览器中解析的唯一ISO 8601字符串是长格式。而且,总是使用“Z”说明符。如果你这样做,你可以安全地在本地和UTC时间之间来回。

这适用于各种浏览器(在IE9之后):

console.log(new Date(Date.parse("2005-07-08T00:00:00Z")).toString());

幸运的是,目前大多数浏览器都会平等对待其他输入格式,包括最常用的'1/1/1970'和'1/1/1970 00:00:00 AM'格式。以下所有格式(以及其他格式)在所有浏览器中都被视为本地时间输入,并在存储之前转换为UTC。因此,使它们跨浏览器兼容。此代码的输出在我的时区中的所有浏览器中都是相同的。

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"));

输出端

在输出端,所有浏览器都以相同的方式转换时区,但它们以不同方式处理字符串格式。以下是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"

我希望这个答案很有帮助。

答案 2 :(得分:70)

疯狂有一些方法。作为一般规则,如果浏览器可以将日期解释为ISO-8601,它将会。 “2005-07-08”属于这个阵营,所以它被解析为UTC。 “2005年7月8日”不能,所以它在当地时间解析。

有关详情,请参阅JavaScript and Dates, What a Mess!

答案 3 :(得分:7)

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

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

一个例子:

    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'];

答案 4 :(得分:5)

根据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格式  + {dannyk,dygraphs人说,这种格式适用于所有浏览器。

答案 5 :(得分:5)

使用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也可能会给出不正确的结果。

答案 6 :(得分:4)

虽然将字符串传递给parse方法的CMS is correct通常是不安全的,但15.9.4.2节中的新ECMA-262 5th Edition(又称ES5)规范表明Date.parse()实际应该处理ISO格式日期。旧规范没有提出这样的要求。当然,旧浏览器和一些当前浏览器仍然不提供此ES5功能。

你的第二个例子没错。它是UTC中指定的日期,由Date.prototype.toISOString()暗示,但在您当地的时区中显示。

答案 7 :(得分:2)

light weight date parsing library应解决所有类似的问题。我喜欢这个库,因为它很容易扩展。它也可能是它(不是很直接,但不是那么难)。

解析示例:

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") );

答案 8 :(得分:2)

这是一个简短,灵活的代码段,用于以跨浏览器安全的方式转换日期时间字符串,如@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()

答案 9 :(得分:2)

两者都是正确的,但它们被解释为具有两个不同时区的日期。所以你比较了苹果和橘子:

// 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 format比较了日期,因此您可以直观地比较当地日期和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 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"

答案 10 :(得分:0)

accepted answer from 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
}