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

use*_*196 327 javascript date

案例一:

new Date(Date.parse("Jul 8, 2005"));
Run Code Online (Sandbox Code Playgroud)

输出:

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

案例二:

new Date(Date.parse("2005-07-08"));
Run Code Online (Sandbox Code Playgroud)

输出:

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


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

CMS*_*CMS 433

在第5版规范出来之前,该Date.parse方法完全依赖实现(new Date(string)等同于Date.parse(string)除了后者返回数字而不是a Date).在第5版规范中,添加了一个要求以支持简化(略微不正确)的 ISO-8601(另请参阅JavaScript中的有效日期时间字符串?).但除此之外,除了他们必须接受任何Date#toString输出(不说那是什么)之外,没有要求接受什么Date.parse/ new Date(string)应该接受.

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

从ECMAScript 2019(第9版)开始,Date#toStringDate#toUTCString的格式已被指定为(分别):

  1. ddd MMM DD YYYY HH:mm:ss ZZ [(时区名称)]
    例如Tue Jul 10 2018 18:39:58 GMT + 0530(IST)
  2. ddd,DD MMM YYYY HH:mm:ss Z
    例如 2018年7月10日星期二格林威治标准时间13:09:58

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

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

// 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
}
Run Code Online (Sandbox Code Playgroud)

  • @CMS _implementationdependent_ 是什么意思? (2认同)
  • @RoyiNamir,这意味着结果取决于运行代码的Web浏览器(或其他JavaScript实现). (2认同)

dra*_*112 188

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

输入端

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

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

Date.parse('1/1/1970');
Run Code Online (Sandbox Code Playgroud)

在我的时区(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
Run Code Online (Sandbox Code Playgroud)
  • "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
Run Code Online (Sandbox Code Playgroud)

因此,直接回答原始提问者的问题,"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());
Run Code Online (Sandbox Code Playgroud)

这样做:

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

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

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

console.log(new Date(Date.parse("2005-07-08T00:00:00Z")).toString());
Run Code Online (Sandbox Code Playgroud)

幸运的是,大多数当前的浏览器都会平等对待其他输入格式,包括最常用的'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"));
Run Code Online (Sandbox Code Playgroud)

输出端

在输出端,所有浏览器都以相同的方式转换时区,但它们以不同方式处理字符串格式.以下是toString功能及其输出内容.注意我的机器上的5:00 AM toUTCStringtoISOString函数输出.

打印前从UTC转换为本地时间

 - toString
 - toDateString
 - toTimeString
 - toLocaleString
 - toLocaleDateString
 - toLocaleTimeString
Run Code Online (Sandbox Code Playgroud)

直接打印存储的UTC时间

 - toUTCString
 - toISOString 
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)

我通常不使用ISO格式进行字符串输入.使用该格式对我有用的唯一时间是日期需要按字符串排序.ISO格式可按原样排序,而其他格式则不可排序.如果必须具有跨浏览器兼容性,请指定时区或使用兼容的字符串格式.

代码new Date('12/4/2013').toString()经历了以下内部伪转换:

  "12/4/2013" -> toUCT -> [storage] -> toLocal -> print "12/4/2013"
Run Code Online (Sandbox Code Playgroud)

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

  • 首先,这是一个很棒的写作.但是,我想指出依赖性.关于时区说明符,你说:"缺少说明符应该假设当地时间输入." 值得庆幸的是,ECMA-262标准消除了任何推测的需要.[它陈述](http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.15):"缺席时区偏移的值是"Z"." 因此,没有指定时区的日期/时间字符串被假定为UTC而不是本地时间.当然,与JavaScript这么多东西一样,实现之间似乎没有什么协议. (3认同)
  • *...包括最常用的'1/1/1970'和'1/1/1970 00:00:00 AM'格式.* - 最常用******当然,这不在我的国家. (2认同)
  • @ulidtko - 对不起,我在美国.哇...你就在基辅.我希望你和你的家人保持安全,事情很快就会稳定下来.照顾好自己,祝你好运. (2认同)

dan*_*nvk 70

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

查看JavaScript和日期,多么糟糕!更多.

  • “*作为一般规则,如果浏览器可以将日期解释为 ISO-8601,它就会。*”不受支持。“2020-03-20 13:30:30”被许多浏览器视为 ISO 8601 和本地,但 Safari 视为无效日期。大多数浏览器不支持许多 ISO 8601 格式,例如 2004-W53-7 和 2020-092。 (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'];
Run Code Online (Sandbox Code Playgroud)


Jua*_*nus 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/ 此格式:+通过使用ymd排序和4位数年份避免日期和月份顺序歧义+避免UTC与本地问题不对使用斜杠+ danvk符合ISO格式,dygraphs家伙说,这种格式在所有浏览器中都很好.

  • 那篇关于 dygraphs 的文章是错误的,页面上的第一个例子清楚地说明了为什么使用斜杠而不是连字符是非常糟糕的建议。在撰写本文时,使用“2012/03/13”导致浏览器将其解析为本地日期,而不是 UTC。ECMAScript 规范定义了对使用“YYYY-MM-DD”(ISO8601) 的明确支持,因此始终使用连字符。应该注意的是,在我撰写此评论时,Chrome 已被修补以将斜杠视为 UTC。 (2认同)

Luk*_*tor 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();
Run Code Online (Sandbox Code Playgroud)

第3个参数确定严格解析(从2.3.0开始提供).没有它,moment.js也可能会给出不正确的结果.