JavaScript中的DST实现在发送到MVC控制器时导致问题

时间:2013-11-01 18:41:38

标签: javascript ajax asp.net-mvc json datetime

我的客户,在保险领域,需要潜在被保险人的出生日期。它使用jQuery datepicker输入到Web表单中,使用Knockout连接到模型属性,并通过JSON中的ajax发送到MVC 4控制器。

一些保单持有人已收到错误出生日期的保险文件。在随后的调查过程中,我们发现除了数据输入错误之外,错误日期集中在以下两个时期:

  • 三月至四月初的最后三周
  • 十月的最后一周到十一月的第一周。

由于我们的客户在美国/蒙特利尔时区,我立即想到了DST的问题。 The DST rules changed in 2007 in this timezone

阅读了几篇文章和其他Stack Overflow问题,我了解到ECMAScript 5标准规定了实施必须考虑当前的DST规则,而不必尊重DST的变更历史。 ES5 15.9.1.8目前唯一不符合此规范的浏览器是IE10(稍后会详细介绍)。

根据此规范,浏览器将报告日期如下(在Chrome中测试):

(new Date(2006, 9, 31, 0, 0, 0)).toISOString()
// 2006-10-31T04:00:00.000Z

(new Date(2008, 9, 31, 0, 0, 0)).toISOString()
// 2008-10-31T04:00:00.000Z

.NET平台正确报告日期如下:

DateTime dt = new DateTime(2006, 10, 31, 0, 0, 0);
Console.WriteLine("{0:MM/dd/yy H:mm:ss zzz}", dt);
// 10-31-06 0:00:00 -05:00
dt = new DateTime(2008, 10, 31, 0, 0, 0);
Console.WriteLine("{0:MM/dd/yy H:mm:ss zzz}", dt);
// 10-31-08 0:00:00 -04:00

此问题的一个原因是,如果被保险人在2007年10月的最后一周出生,则UTC时间将错误地报告给我的MVC控制器,例如:

Javascript日期对象:

new DateTime(2006, 9, 31, 0, 0, 0);

.Net解析JSON数据并获取:

10-30-06 23:00:00 GMT

在测试期间,我发现JavaScript中的Date对象的内部表示与.Net DateTime的内部表示不兼容,并且我无法使用JavaScript表示来滚动自己的解析器:

Javascript:

(new Date(2006, 9, 31, 0, 0, 0)).getTime()
// 1162267200000

.Net:

DateTime dt = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(1162267200000);
Console.WriteLine("{0}", dt);
// 2006-10-31 04:00:00

(这是错误的,因为它代表的是当地时间2006年10月30日23:00。)

对于我客户的用例,因为它是我感兴趣的日期部分,我可能只是将Date对象的时间部分设置为正午,这将使我免受JavaScript对日期误解的所有情况的影响。不幸的是,这是一个丑陋的补丁,并没有解决其他潜在的情况,例如,如果我的客户希望我们实现一个功能,要求我们确定过去发生的事件的确切日期和时间。

另一种方法是编写一个算法来检测我的控制器中可能存在问题的DST周,并设置正确的时间,如果我在相关周内得到一个日期。不幸的是,鉴于ECMAScript 6标准规定必须遵守DST更改历史记录(因此所有未来的浏览器都应该正确处理这种情况),并且鉴于IE10已经正常工作,我害怕将来会出现问题。从架构上讲,这种方法似乎也是错误的。

要考虑的另一个方面是,如果我必须将模型从客户端传递到服务器然后再传回客户端,我将需要一种方法来重新创建“坏”JavaScript Date对象,以确保不会影响显示在应用程序的客户端。

没有解决方案似乎正确且可扩展。我无法相信以前没有人曾经处理过这个问题。

如何永久地解决这个问题,以及可以应用于所有其他JavaScript / MVC项目的方式?

编辑#1 - 与问题没有直接关系的补充信息:

正如@ matt-johnson指出的那样,很少会出现应该使用时区信息的情况。然而,在这种情况下,被保险人的出生日期与我的客户所在的时区有关。让我们以维多利亚不列颠哥伦比亚省的一名17岁生日为例,他的生日是12月2日。如果他在12月1日22:00提交保险报价,即使他仍然是17岁,保险报价也会被接受,因为他已经在我客户的时区18。同样的规则适用于保险定价。这是法律要求。

为了清楚起见,我正在寻找一种全面的解决方案,可以应用于任何其他项目。具体问题是:Javascript,在2007年之前的几个特定周内,以1小时的偏移量报告时间。无论我如何表示时间(本地时区或UTC),这个偏移始终存在,因为它是底层数据(而不是数据表示)是错误的。

我使用“在中午设置时间部分”解决方法来规避错误,但潜在的问题仍然存在。如果我的客户要求我们开发一个网络应用程序,要求我们获取过去特定事件的日期和时间,会发生什么?例如:“请说明事故发生的确切日期和时间:2005年10月31日19:32”。 MVC控制器将时间设置为18:32。

1 个答案:

答案 0 :(得分:4)

你忽略了.Net的DateTime结构的一部分。具体来说,您并不认为任何.Kind的{​​{1}}属性是三个可能的DateTime值之一。 More on MSDN

如果您使用UTC值,则需要设置DateTimeKind。由于JavaScript的数值是基于UTC的,因此您应该将其用于转换:

DateTimeKind.Utc

此外,您正在做一些有点奇怪的事情,即在DateTime dt = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc) .AddMilliseconds(1162267200000); zzz上使用DateTime格式化工具DateTimeKind.Unspecified。在这种情况下,确实没有与该值相关联的任何时区,但.Net将假定您希望它的行为就好像那样{{1}因为DateTimeKind.Local格式化器没有其他意义。

您确实需要小心不要使用zzz值(例如DateTimeKind.Local)或任何未指明类型可能被误解为本地值的内容(例如DateTime.Now或{{1} })。此上下文中的“本地”将是服务器的本地“,这在Web应用程序中很少相关。 Read more here

关于JavaScript的DST实现,我对ECMAScript规范做了类似的观察。 You can read more on that here

现在所有这些都说明了,你找到了一个非常具体的观点,即JavaScript或.Net都没有提供代表没有时间的日期的类型。两者都采用将时间设置为午夜的方法。如果您不小心,可能会遇到没有午夜的本地日期问题,例如October 20th, 2013 in Brazil。如果您被限制在美国,那么您可能没有这个特定问题,但从设计角度来看它仍然是相关的。

最好的方法是使用真正的“没有时间的日期”类型。在.Net中,您可以在Noda Time库中找到zzz。作为ISO8601字符串,它看起来像.ToUniversalTime(),没有任何时间或时区。

如果您不想使用Noda Time(虽然我强烈推荐它),您仍然可以在.Net中使用LocalDate类型,只是要非常小心,不要将时间部分用于任何事情。它应该有"2013-10-31"

不幸的是,JavaScript中目前没有一个没有时间的日期类型。 (JavaScript DateTime类可能应该被命名为DateTimeKind.Unspecified)。因此,您需要自己构建一个仅限日期的字符串。您可以手动从Date汇编它,例如:

DateTime

但更简单的方法是使用moment.js库:

Date

我只是就“最佳方法”提出自己的看法。但是,如果您只是想知道如何使用当前正在执行的操作来防止失败,那么您应该注意不要在JavaScript响应中转换为UTC。你是用var s = dt.getFullYear() + "-" + (dt.getMonth() < 10 ? "0" : "") + dt.getMonth() + "-" + (dt.getDate() < 10 ? "0" : "") + dt.getDate(); 做的。同样,您可以手动汇总完整的日期和时间,或者您可以使用片刻格式化以当地时间表示的响应。

如果你真的想到它,一个Birthdate实际上没有与之相关的时间或时区。大多数人在确定他们的年龄时,不会追踪他们出生的小时/分钟/秒(或他们出生地的时区)。您可以为占星术执行此操作,但对于日常业务使用,您只需使用当地时区的开始日期,即评估年龄。所以日期确实是唯一重要的数据。