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

2024-11-02 21:00:00
admin
原创
49
摘要:问题描述:案例一:new Date(Date.parse("Jul 8, 2005")); 输出:2005 年 7 月 8 日星期五 00:00:00 GMT-0700(太平洋标准时间)案例二:new Date(Date.parse("2005-07-08...

问题描述:

案例一:

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 GMT-0700(太平洋标准时间)


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


解决方案 1:

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

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

Date#toString从 ECMAScript 2019(第 9 版)开始,和的格式Date#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(请注意,支持并不普遍,且不兼容的实现仍将在一段时间内继续使用)。

我建议手动解析日期字符串,并使用年、月、日参数来构造日期,以避免产生歧义:

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

解决方案 2:

在最近编写 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(第 6 版)中特别列出的两种格式之一,所有实现都必须以相同的方式解析(另一种是为Date.prototype.toString指定的格式)。

但是,即使对于 ISO 8601 格式的字符串,某些实现也会出错。以下是 Chrome 和 Firefox 的比较输出,当此答案最初在我的计算机上为 1970 年 1 月 1 日(纪元)编写时,使用 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 时间,因此与纪元没有偏移,结果为 0

  • 在第二种情况下,“-0500”说明符表示输入处于 GMT-05:00,并且两种浏览器都将输入解释为处于 -05:00 时区。这意味着 UTC 值与纪元有偏移,这意味着在日期的内部时间值上添加 18000000 毫秒。

  • 第三种情况,没有说明符,应该被视为主机系统的本地时间。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 日期。

因此,直接回答原始问题,"YYYY-MM-DD"ECMA-262 要求将 解释为 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 

在 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

在 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" -> toUTC -> [storage] -> toLocal -> print "12/4/2013"

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

解决方案 3:

这种疯狂的做法是有一定道理的。一般来说,如果浏览器可以将日期解释为 ISO-8601,它就会这样做。“2005-07-08”属于这一类,因此它被解析为 UTC。“2005 年 7 月 8 日”则不能,因此它被解析为本地时间。

请参阅JavaScript 和日期,真是一团糟!以了解更多。

解决方案 4:

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

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

举个例子:

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

解决方案 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:

根据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 与本地时间不符合 ISO 格式的问题 + dygraphs专家 danvk 说此格式适用于所有浏览器。

解决方案 7:

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

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

解决方案 8:

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

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

解析示例:

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

解决方案 10:

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

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

我删除了该调用,因为它会自动用于字符串参数。我还使用ISO8601 格式Date.parse()比较了日期,以便您可以直观地比较当地日期和 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"

解决方案 11:

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
}
相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用