【问题标题】:DST implementation in JavaScript causing issues when sending to a MVC controllerJavaScript 中的 DST 实现在发送到 MVC 控制器时导致问题
【发布时间】:2013-11-13 00:52:17
【问题描述】:

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

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

  • 3 月的最后三周到 4 月初
  • 10 月的最后一周到 11 月的第一周。

由于我们的客户在美国/蒙特利尔时区,我立即想到了 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),这个偏移量始终存在,因为它是错误的基础数据(而不是数据表示)。

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

【问题讨论】:

  • 是否可以从等式中完全删除时区?仅使用 UTC 作为日期/时间?如果您必须跟踪未来的时间,请将它们转换为 UTC 以存储在数据库中以及何时需要处理时间计算,然后在正确的时区处理 UI 显示。
  • 好评,@StevenV。这是我的第一个想法,但即使是 UTC 日期也没有在 JavaScript 中正确报告这些日期。问题源于 JS Date 对象中的错误时区偏移,因此在应用于 UTC 表示时它仍然存在。此外,我们必须维护客户的时区,因为公司是全国性的,并且有业务规则要求我们将被保险人的时区与我的客户的时区进行比较。
  • “我们必须维护客户端的时区”,你是指哪个客户端?如果您谈论的是被保险人,您如何获得有关个人的数据?否则,您只是获得浏览器/系统的时区,可以轻松修改/更改。此外,在您的测试中很奇怪,Date() 构造函数中的month 参数是从零开始的。 9 月 8 日,10 月 9 日。
  • 是的,我指的是保险时区。事实上,它可以改变,但有权访问该平台的经纪人知道这一点。任何欺诈性提交都可能导致诉讼。这不是我要解决的问题。是的,感谢您指出错误。我更正了。

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


【解决方案1】:

您忽略了 .Net 的 DateTime 结构的一部分。具体来说,您没有考虑到任何DateTime.Kind 属性是三个可能的DateTimeKind 值之一。 More on MSDN.

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

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

另外,您正在做一些奇怪的事情,即在具有DateTimeKind.UnspecifiedDateTime 上使用zzz 格式化程序。在这种情况下,确实没有 任何 时区与该值相关联,但 .Net 会假定您希望它的行为 就像它是 DateTimeKind.Local,因为zzz 格式化程序没有其他意义。

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

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

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

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

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

不幸的是,目前 JavaScript 中没有原生类型用于没有时间的日期。 (JavaScript Date 类可能应该被命名为 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 并没有与之关联的时间或时区。大多数人在计算自己的年龄时不会跟踪他们出生的小时/分钟/秒(或出生地点的时区)。您可能会为占星术执行此操作,但对于日常业务使用,您只需使用您评估年龄的当地时区的一天开始。所以日期确实是唯一重要的数据。

【讨论】:

  • +1 因为它包含很多关于 DateTime 结构的有用信息,都在一个方便的地方。 - 事实上,我的例子只是 sn-ps 显示存在实际问题。对于该 Web 应用程序,我需要时区信息,因为它是业务逻辑所需的重要信息。实际代码管理 DateTimeKind,我在内部将所有 DateTime 转换为 UTC。无论如何, DateTime 的时区部分只是 UTC 时间的偏移量。在这种情况下,偏移量是错误的,所以无论我如何存储它,信息都是不正确的。我会更新我的问题。
  • 关于您更新的问题,是否在您的法律或业务要求中的某个地方定义了客户年龄在其所在地进行评估?时间真的那么敏感吗?我认为整个申请将被搁置,直到下一个工作日开始。 (只是猜测)。
  • 您还指出了事件的时间点,这当然应该附有正确的时区。在处理日期和时间时,上下文非常重要。例如,如果您决定使用 Noda Time,您会将出生日期存储为 LocalDate 值,而事件可能是 ZonedDateTime
  • 另外,它仍然失败的原因是您仍在 JavaScript 中转换为 UTC。在这种情况下,您可能 应该这样做(对于出生日期)。只需将日期字符串 "2006-10-31" 发送到服务器,而不是从 .getTime() 传递结果,因为这是 UTC 术语。
  • 2006...嗯..我认为一个 7 岁的孩子可能不应该获得汽车保险。 :P
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-28
相关资源
最近更新 更多