15

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

一些投保人收到的保险文件中出生日期错误。在随后的调查中,我们发现除了数据录入错误极少外,错误日期还集中在以下两个时期:

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

由于我们的客户在美国/蒙特利尔时区,我立即想到了 DST 的问题。DST 规则在 2007 年在这个时区发生了变化

阅读了几篇文章和其他 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

。网 :

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),这个偏移量总是存在的,因为它是错误的基础数据(而不是数据表示)。

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

4

1 回答 1

4

您忽略了.NetDateTime结构的一部分。具体来说,您没有考虑.Kindany 的属性DateTime是三个可能DateTimeKind值之一。更多关于 MSDN

如果您使用 UTC 值,则需要设置DateTimeKind.Utc. 由于 JavaScript 的数值是基于 UTC 的,因此您应该使用它进行转换:

DateTime dt = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)
                      .AddMilliseconds(1162267200000);

此外,您正在做一些有点奇怪的事情,即zzzDateTime具有DateTimeKind.Unspecified. 在这种情况下,实际上并没有与该值相关联的任何时区,但 .Net 会假设您希望它表现得好像DateTimeKind.Local,因为zzz格式化程序在其他情况下没有意义。

您确实需要小心不要使用DateTimeKind.Local值(例如DateTime.Now)或任何可能将未指定类型误解为本地(例如zzzor .ToUniversalTime())的东西。在这种情况下,“本地”对于您的服务器来说是本地的,这在 Web 应用程序中很少相关。 在这里阅读更多

关于 JavaScript 的 DST 实现,我对 ECMAScript 规范做了类似的观察。 你可以在这里阅读更多内容

现在说了这么多,您遇到了一个非常具体的问题,即 JavaScript 或 .Net 都没有提供表示没有时间的日期的类型。两者都采取将时间设置为午夜的方法。如果您不小心,您可能会遇到当地日期没有午夜的问题,例如巴西的 2013 年 10 月 20 日。如果您仅限于美国,那么您可能没有那个特定的问题,但从设计的角度来看它仍然是相关的。

最好的方法是使用真正的“没有时间的日期”类型。在 .Net 中,您可以像LocalDateNoda Time库中一样找到它。作为 ISO8601 字符串,它看起来像"2013-10-31",没有任何时间或时区。

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

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

var s = dt.getFullYear() + "-" + 
  (dt.getMonth() < 10 ? "0" : "") + dt.getMonth() + "-" + 
  (dt.getDate() < 10 ? "0" : "") + dt.getDate();

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

var s = moment(dt).format("YYYY-MM-DD");

我只是就“最佳方法”提出我的意见。但是,如果您只是想知道如何防止当前正在执行的操作失败,那么您应该注意不要在 JavaScript 响应中转换为 UTC。你正在这样做.toISOString()。同样,您可以手动组合完整的日期和时间,或者您可以使用 moment 来格式化根据本地时间的响应。

如果您真的考虑一下,Birthdate 并没有与之关联的时间或时区。大多数人在计算自己的年龄时不会跟踪他们出生的小时/分钟/秒(或出生地点的时区)。您可能会为占星术执行此操作,但对于日常业务使用,您只需使用您正在评估年龄的当地时区的开始时间。所以日期确实是唯一重要的数据。

于 2013-11-02T02:22:52.183 回答