指定日期时间的时区而不更改值
本文关键字:时区 日期 时间 | 更新日期: 2023-09-27 18:31:29
我想知道如何在不实际更改值的情况下更改 DateTime 对象的时区。这是背景...
我有一个托管在 AppHarbor 上的 ASP.NET MVC 站点,并且服务器的时间设置为 UTC。当我从我的网站提交包含日期时间值的表单时,例如 2013 年 9 月 17 日凌晨 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。我希望在表单提交并将值传递给控制器操作后,它将日期时间的时区设置为浏览器的 (EST) 而不是服务器主机的 (UTC),但这并没有发生。
无论如何,现在我被一个包含正确日期/时间值但时区错误的 DateTime 对象所困扰,因此我无法将其正确转换为 UTC。我存储每个用户的时区,所以如果有一种方法可以在不实际更改值的情况下为 DateTime 对象设置时区(我不能做 TimeZoneInfo.ConvertTime,因为它会更改值),那么我可以有正确的日期/时间值和正确的时区,然后我可以安全地做日期。到世界时间。话虽如此,我只看到了如何更改日期时间的种类,而不是时区,而不更改其实际值。
有什么想法吗?
实际上,它正在做你要求它做的事情。 从服务器的角度来看,您向它传递了一个"未指定"DateTime
。 换句话说,只是年、月日等,没有任何上下文。 如果你看一下.Kind
属性,你会发现它确实是DateTimeKind.Unspecified
.
在未指定类型的DateTime
上调用.ToUniversalTime()
时,.NET 将假定运行代码的计算机的本地时区的上下文。 您可以在此处的文档中阅读有关此内容的更多信息。 由于您的服务器设置为 UTC,因此无论输入类型如何,所有三种类型都将产生相同的结果。 基本上,这是一个无操作。
现在,您说您希望时间反映用户的时区。 不幸的是,这是服务器没有的信息。 没有带有时区详细信息的神奇 HTTP 标头。
不过,有一些方法可以实现这种效果,并且您有一些选择。
JavaScript 方法
这可能是最简单的选择,但它确实需要JavaScript。
- 从用户那里获取本地日期和时间输入,并将其解析为 JavaScript
Date
类。- 为了便于解析,您可以考虑改用
moment
,从现在开始.js库。
- 为了便于解析,您可以考虑改用
- 从
Date
或moment
获取UTC日期和时间,并将其传递给您的服务器。- 这在 JavaScript 中相对容易,所以我就不赘述了。
- 您可以传递多种格式,但首选方法是作为ISO8601时间戳。 示例:
2013-09-17T08:00:00.000Z
- 不要忘记最后的
Z
。 这表示时间采用 UTC 格式。 - 由于它是作为 UTC 传递给您的,因此您无需进行任何转换即可存储它。
- 检索它时,您再次将 UTC 传递给浏览器,使用 JavaScript 将其加载到
Date
或moment
中,然后发出本地日期和时间。 - 如果您采用这种方法.js我强烈建议您尝试一下。 它可以在没有它的情况下完成,但这可能更加复杂且容易出错。
使用 Windows 时区的 .NET 方法
如果你不打算调用JavaScript,那么你将需要询问用户他们的时区。 这在较大的应用程序中可以很好地工作,例如在用户的配置文件页面上。
- 使用
TimeZoneInfo.GetSystemTimeZones
构建下拉列表。- 对于列表中的每个
TimeZoneInfo
项,对值使用.Id
,对文本使用.DisplayName
。
- 对于列表中的每个
然后,当您要转换时间时,可以使用此值。
TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById(yourUsersTimeZone); DateTime utc = TimeZoneInfo.ConvertTimeToUtc(theInputDatetime, tz);
这将适用于Windows时区,这是一个Microsoft创建,并且有一些缺点。 您可以在时区标签维基中阅读他们的一些缺陷。
使用 IANA 时区的 .NET 方法
如果您想使用更标准的 IANA 时区,例如 America/New_York
或 Europe/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
时,你可以用野田时间方法直接看到这一点。 但它也发生在其他的身上——它只是隐含的。 在 JavaScript 中,规则可能因浏览器而异,因此不要期望一致的结果。
根据您收集的数据类型,您可能会决定做出这种假设是完全可以接受的。 但在许多情况下,假设是不合适的。 在这种情况下,您可能需要提醒用户输入时间无效,或者询问他们指的是两个不明确的时间中的哪一个。
在 .Net 中,可以使用 TimeZoneInfo.IsInvalidTime
和 TimeZoneInfo.IsAmbiguousTime
进行检查。
有关夏令时工作原理的示例,请参阅此处。 在"弹簧-前向"过渡中,过渡期间的时间无效。 在"回退"过渡中,过渡期间的时间是模糊的 - 也就是说,它可能发生在过渡之前或之后。