指定datetime的时区而不更改值

时间:2013-09-17 01:52:01

标签: c# asp.net-mvc datetime timezone appharbor

我想知道如何在不实际更改值的情况下更改DateTime对象的时区。这是背景......

我在AppHarbor上托管了一个ASP.NET MVC站点,服务器的时间设置为UTC。当我从我的网站提交包含日期时间值的表单时,比如9/17/2013凌晨4:00,它会转到具有该值的服务器。然而,当我这样做时:

public ActionResult Save(Entity entity)
{
    entity.Date = entity.Date.ToUniversalTime();
    EntityService.Save(entity);
}

...它错误地将其保持为同一时间(凌晨4点),因为服务器已经处于UTC时间。因此,在转换为UTC并保存到数据库后,数据库值为2013-09-17 04:00:00.000,实际应该是2013-09-17 08:00:00.000,因为浏览器在EST上。我希望在提交表单并将值传递给控制器​​操作之后,它会将DateTime的时区设置为浏览器(EST)而不是服务器主机(UTC),但这并没有发生。

在任何情况下,现在我都遇到了包含正确日期/时间值但时区错误的DateTime对象,因此无法将其正确转换为UTC。我存储每个用户的时区,所以如果有办法设置DateTime对象的时区而不实际更改值(我不能做TimeZoneInfo.ConvertTime因为它会改变值)然后我可以有正确的日期/时间值和正确的时区,然后我可以安全地做date.ToUniversalTime。话虽这么说,我只看到如何在不改变其实际值的情况下更改DateTime的KIND而不是TimeZone。

有什么想法吗?

1 个答案:

答案 0 :(得分:13)

实际上,它完全按照你的要求去做。从服务器的角度来看,你传递了一个"未指定的" DateTime。换句话说,只有年,月,日等,没有任何上下文。如果查看.Kind属性,您会发现它确实是DateTimeKind.Unspecified

当您在未指定类型的.ToUniversalTime()上调用DateTime时,.NET将假定运行代码的计算机的本地时区的上下文。您可以阅读有关此in the documentation here的更多信息。由于您的服务器设置为UTC,因此无论输入类型如何,这三种类型都将产生相同的结果。基本上,它是一个无操作。

现在,您说您希望有时间反映用户的时区。不幸的是,这是服务器没有的信息。没有神奇的HTTP标头带有时区细节。

虽然有很多方法可以达到这个效果,但你有一些选择。

JavaScript方法

这可能是最简单的选择,但确实需要JavaScript。

  • 从用户那里获取本地日期和时间输入,并将其解析为JavaScript Date类。
    • 为了便于解析,您可以考虑使用moment来自moment.js库。
  • Datemoment获取UTC日期和时间,并将其传递给您的服务器。
    • 这在JavaScript中比较容易,所以我会为您提供详细信息。
    • 您可以传递多种格式,但首选方式是ISO8601时间戳。示例:2013-09-17T08:00:00.000Z
  • 不要忘记最后的Z。这表示时间是UTC。
  • 由于它是以UTC身​​份传递给您的,因此您只需将其存储而无需任何转换。
  • 检索时,再次将UTC传递给浏览器,使用JavaScript将其加载到Datemoment,然后发出本地日期和时间。
  • 如果您采用这种方法,我强烈建议您尝试moment.js。它可以在没有它的情况下完成,但这可能会更加复杂并且容易出错。

使用Windows时区的.NET方法

如果您不打算调用JavaScript,那么您需要询问用户他们的时区。这可以在更大的应用程序中很好地工作,例如在用户的个人资料页面上。

  • 使用TimeZoneInfo.GetSystemTimeZones构建下拉列表。
    • 对于列表中的每个TimeZoneInfo项,请使用.Id作为值,并使用.DisplayName作为文本。
  • 然后,当您想要转换时间时,可以使用此值。

    TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById(yourUsersTimeZone);
    DateTime utc = TimeZoneInfo.ConvertTimeToUtc(theInputDatetime, tz);
    
  • 这适用于Windows时区,这是Microsoft创建的,并且有一些缺点。您可以在the timezone tag wiki中了解其中的一些不足之处。

使用IANA时区的.NET方法

如果您想使用更标准的IANA时区,例如America/New_YorkEurope/London,则可以使用Noda Time之类的库。与内置框架相比,它提供了更好的API来处理日期和时间。这有一点学习曲线,但如果你做任何复杂的事情,这是值得的。举个例子:

DateTimeZone tz = DateTimeZoneProviders.Tzdb["America/New_York"];
var pattern = LocalDateTimePattern.CreateWithInvariantCulture("yyyy-MM-dd HH:mm:ss");
LocalDateTime dt = pattern.Parse("2013-09-17 04:00:00").Value;
ZonedDateTime zdt = tz.AtLeniently(dt);
Instant utc = zdt.ToInstant();


关于夏令时

无论您采用这三种方法中的哪一种,都必须处理夏令时产生的问题。这些样本中的每一个都显示出一个宽松的"方法,如果您指定的本地时间不明确或无效,则遵循某些规则,以便您仍然获得一些有效的时刻。当我调用AtLeniently时,您可以使用Noda Time方法直接看到这一点。但它也与其他的一起发生 - 它只是隐含的。在JavaScript中,规则因浏览器而异,因此不要期望一致的结果。

根据您收集的数据类型,您可能会认为做出此类假设是完全可以接受的。但在许多情况下,这是不恰当的假设。在这种情况下,您可能需要提醒您的用户输入时间无效,或者询问他们意味着两个不明确的时间。

在.Net中,您可以使用TimeZoneInfo.IsInvalidTimeTimeZoneInfo.IsAmbiguousTime检查此内容。

有关夏令时如何运作的示例see here。在" spring-forward"转换,转换期间的时间无效。在"后退"过渡期,过渡期间的时间不明确 - 也就是说,它可能在过渡之前或之后发生。