anitakym

使用JavaScript Date对象的糟糕经历 | PJSen Blog

anitakym · 2017-03-30翻译 · 520阅读 原文链接

JavaScript日期API的使用是有一些难点的,对此,我早有耳闻,但直到最近,我才亲身体验到了。接下来,我将阐述一个奇怪的情况,这个情况可能会导致客户端浏览器发送给服务器的日期值是错误的。当分析这些例子时,我们默认他们在机器上执行的为UTC+01:00 时区,除非我明确说了这个例子指的是另一个不同的时区。

让我们试着分析一个JavaScript Date对象:

var c = new Date("2015-03-01");
c.toString();
> Sun Mar 01 2015 01:00:00 GMT+0100

我们来看一下时间的值, 01:00 看起来虽然不好理解,但是当我们将其和时区信息(这些时区信息是和JavaScript对象一起存储的)联系起来的时候,就好理解了。时区信息是Date对象的固有部分,它来自于浏览器,而浏览器又是从操作系统的本地设置中获取信息的。这样一来,在AJAX调用中,结果中后面两个信息是必不可少的了,因为.toJSON() 方法会被接着调用。我的这个猜测基于ngResource 库的行为,但是别的框架或库也很有可能是一样的情况,因为它们也必须将JavaScript日期对象转成通用的文本格式,使其能够通过HTTP协议传递。顺便说一句,.toJSON().toISOString()返回的结果是一样的。

c.toJSON();
> 2015-03-01T00:00:00.000Z

我们在这里得到的是UTC标准化的日期和时间值。当我们将日期信息发送给服务器时,我们可以用时区信息来标准化时间,因为我们用时区信息来表示相对于实际时间的偏移值。有一点很重要,就是存储在Date对象中的值是以本地时区(即浏览器的时区)表示的。这意味着如在UTC-xx:xx 这种情况下的话,会有一些奇怪的结果。我们把时区设为UTC-01:00 来看一下。

var c = new Date("2015-03-01");
c.toString();
> Sat Feb 28 2015 23:00:00 GMT-0100

这里的问题是,我们最终得到了与原始文本表示不同的解析值,现在得到的是2月28日,而原来是3月1日。 我们看下面的例子,从中仍可以表明,我们的日期处理逻辑依赖于规范化的值:

c.toJSON();
> 2015-03-01T00:00:00.000Z

但是,当我们尝试获取单个日期组件时,就会出现问题。 这里我们试着获取日期月份中的天数。

c.getDate()
> 28

通常情况下,如果我们依赖于规范化的值,调用合适的方法,日期对象任然可以很好的完成它的任务。 但问题在于,并不是所有的Date constructor 的行为都一样,我们来试试下面这个:

var b = new Date('03/01/2015');
b.toString();
> Sun Mar 01 2015 00:00:00 GMT+0100

现在,虽然解析的日期仍然包含从操作系统获得的时区信息,但并不具有由相应偏移修正的时间值。在第一个例子中,我们得到的是GMT + 01:00 及据其产生的1am,这里我们得到的是00:00 am,当然,我们仍然得到了GMT + 01:00时区信息。 没有正确转变时间值的时区信息实际上是灾难性的。 让我们看一下调用.toJSON()时会发生什么:

b.toJSON()
> 2015-02-28T23:00:00.000Z

结果是,我们发送给服务器的日期是错误的。而且,将日期值从服务器传输到客户端时也会发生类似的情况。现在,让我们假设服务器发送以下日期,而我们对它进行解析。 请记住,实际的解析过程可能会在一些框架的代码中隐式发生,例如当指定Kendo网格的数据源时, 所以为我们解析它的可能是一个框架的代码。

var d = new Date("2015-03-01T00:00:00.000Z");
d.toString();
> Sun Mar 01 2015 01:00:00 GMT+0100

正如我们所见,这个构造函数会产生像 Date(“2015-03-01”) 这样的转换后的时间值,但当要显示这些重新取到的值时,我们都无可避免的要回答这个问题:我们是想要显示本地时间还是服务器时间呢? 我们必须记住,如果客户端的浏览器是在GMT-xx:xx时区,而我们又试图显示解析的值(如c.getDate()示例),而不是规范化的值,这可能会导致错误的日期显示在用户面前。 我说“可能”,因为这取决于用户的需求。 例如,在Angular 我们可以通过向$ filter('date')提供可选的时区参数来强制显示规范化的值。

var c = new Date("2015-03-01");
$filter('date')(c, "yyyy-MM-dd", "+0000");
> 2015-03-01

这里我们不用担心原型为Date的对象c的内部实际组件值。 它内部可能存储的是2月28日,但这没有关系, $ filter会输出UTC时间值。 还值得一提的是,Date constructor 还假定它规定的参数为UTC值。 所以我们可以使用UTC值填充Date对象,并返回UTC值,而无需考虑过程中的局部的内部表示。 这种方法可以让输出日期和时间等于我们的预期。

其实作为结论,我应该写一些建议,像是如何使用Date对象和应该避免哪些问题等。 但我觉得自己研究不深,不足以写出指导性的方针。因而在此,我只做一些一般性的陈述。 首先,多注意你的库组件的情况,如,一个日期选择器控件,那么它的输出是Date对象还是用字符串表示的日期? 用这个输出可以做什么? 输入是Date对象还是字符串表示?解析是它自己做的吗?仔细检测核实,不要盲目相信框架。我是一个知其然还要知其所以然的人,所以我会深究细节,试着找到本质的东西。我总是相信深入的研究(尽管会花费不少时间)并理解底层的技术是值得的,Scott Hanselman (他也遵循这个原则)也常常使我回想起这一点。

译者anitakym尚未开通打赏功能

相关文章