为什么Date.parse给出不正确的结果?

独自空忆成欢 提交于 2020-03-10 22:12:22

情况一:

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

输出:

2005年7月8日星期五00:00:00 GMT-0700(PST)

案例二:

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

输出:

Thu Jul 07 2005 17:00:00 GMT-0700(PST)


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


#1楼

根据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与本地问题的冲突dygraphs的家伙通过使用斜杠+ danvk来符合ISO格式,他说这种格式在所有浏览器中都很好。


#2楼

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

此方法对于以异常方式格式化日期很有用。

一个例子:

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

#3楼

这个轻量级的日期解析库应该解决所有类似的问题。 我喜欢该库,因为它很容易扩展。 也有可能(不是很简单,但是就不那么难了)。

解析示例:

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

#4楼

在最近写JS解释器的经验中,我为ECMA / JS日期的内部工作付出了很多努力。 因此,我认为我将在这里投入2美分。 希望共享这些内容可以帮助其他人解决有关浏览器在处理日期方面的差异的任何问题。

输入端

所有实现在内部将其日期值存储为64位数字,这些数字表示自1970年1月1日UTC以来的毫秒数(GMT与UTC是同一件事)。 1/1/1970 00:00:00之后的日期为正数,而之前的日期为负数。

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

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

在我的时区(EST),结果是1800万,因为这是5个小时内的毫秒数(在夏令时中只有4个小时)。 在不同的时区,该值将有所不同。 所有主要的浏览器都以相同的方式进行操作。

这是擦。 尽管主要浏览器会将输入字符串格式解析为日期存在一些差异,但就时区和夏令时而言,它们基本上将它们解释为相同的形式。 支持的一种是ISO 8601格式。 这是ECMA-262 v.5规范中概述的唯一格式。 对于所有其他字符串格式,解释取决于实现。 具有讽刺意味的是,这是浏览器可以不同的格式。 这是我的计算机上使用ISO 8601字符串格式的1970年1月1日Chrome和Firefox的比较输出。

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时间,而另一个被解释为本地时间。 这就是为什么,

这并不有趣:

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函数及其输出。 请注意,我的机器上凌晨5:00输出的toUTCStringtoISOString函数。

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

我希望这个答案会有所帮助。


#5楼

在第5版规范发布之前, Date.parse方法完全依赖实现new Date(string)等效于Date.parse(string)但后者返回一个数字而不是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 [(时区名称)]
    例如,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

提供了另外2种格式, 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
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!