在最近写JS解释器的经验中,我为ECMA / JS日期的内部工作付出了很多努力。因此,我认为我将在这里投入2美分。希望共享这些内容可以帮助其他人解决有关浏览器在处理日期方面的差异的任何问题。
输入端
所有实现在内部将其日期值存储为64位数字,这些数字表示自1970-01-01 UTC(GMT与UTC相同)以来的毫秒数(毫秒)。该日期是ECMAScript时代,其他语言(例如Java)和POSIX系统(例如UNIX)也使用ECMAScript时代。纪元之后出现的日期为正数,而纪元之前的日期为负数。
以下代码在所有当前浏览器中均解释为同一日期,但具有本地时区偏移:
Date.parse('1/1/1970'); // 1 January, 1970
在我的时区(美国东部标准时间-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值从纪元偏移,这意味着将日期的内部时间值加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日期。
因此,要直接回答原始问题,"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
函数及其输出。请注意我的机器上凌晨5:00 的toUTCString
和toISOString
函数输出。另外,时区名称可能是缩写,在不同的实现中可能会有所不同。
在打印之前从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" -> toUCT -> [storage] -> toLocal -> print "12/4/2013"
我希望这个答案会有所帮助。