将 UTC 日期时间全局转换为用户指定的本地日期时间

Posted

技术标签:

【中文标题】将 UTC 日期时间全局转换为用户指定的本地日期时间【英文标题】:Globally convert UTC DateTimes to user specified local DateTimes 【发布时间】:2013-04-27 02:05:04 【问题描述】:

我将所有 DateTime 字段存储为 UTC 时间。当用户请求网页时,我想采用他首选的本地时区(而不是服务器机器的本地时区)并自动将所有 Web 表单中的所有 DateTime 字段显示为本地日期。

当然,我可以在每个表单中的每个 DateTime.ToString() 调用上应用转换,或者实现一些辅助实用程序,但这是一项耗时的任务,而且还有一些难以使用自定义配置的第 3 方组件日期时间显示模板。

基本上,我想让 DateTime 类的行为如下:

from this moment on for this web request, 
whenever some code calls DateTime.ToString(), convert it to the local time 
        using the timezone offset given at the very beginning of the web request, 
but if possible, please keep .NET core library DateTime.ToString() calls intact 
       (I don't want to mess up event logging timestamps etc.)

有什么办法吗?

顺便说一句,如果重要的话,我正在使用 ASP.NET MVC 4。

【问题讨论】:

【参考方案1】:

你不能直接按照你的要求做,但我会建议一些替代方案。正如 Nicholas 指出的那样,HTTP 中没有任何内容可以直接为您提供时区。

选项 1

首先,确定要使用的时区数据类型。有两种不同的类型可用,一种是您可以使用TimeZoneInfo 类访问的 Microsoft 时区,另一种是世界其他地区使用的 IANA/Olson 时区。 Read here for more info。我的建议是后者,使用NodaTime 提供的实现。

然后确定要转换到哪个时区。您应该允许您的用户在某处设置以选择他们的时区。

您可能会显示一个下拉列表来选择多个时区之一,或者您可能会做一些更有用的事情,例如显示世界地图,他们可以单击以选择他们的时区。有几个库可以在 javascript 中执行此操作,但我最喜欢的是 this one。

您可能想猜测要使用的默认时区,以便在他们从列表(或地图)中选择之前尽可能接近准确。有一个很棒的库,叫做jsTimeZoneDetect。它将询问浏览器的时钟并对它可能是哪个时区做出最佳猜测假设。这是相当不错的,但它仍然只是一个猜测。不要盲目地使用它——但一定要用它来确定起点。 更新您现在也可以在 moment.js 的 moment-timezone 组件中使用 moment.tz.guess() 执行此操作。

现在您知道用户的时区,您可以使用该值将您的 UTC DateTime 值转换为本地时区。不幸的是,您无法在线程上设置任何内容。当您更改系统时区时,它对所有进程和线程都是全局的。因此,您别无选择,只能将时区传递给您将其发回的每个地方。 (我相信这是你的主要问题。)See this almost duplicate here.

在将其转换为字符串之前,您还需要知道用户的区域设置(可以从 Request.UserLanguages 值中获取)。您可以将其分配给当前线程,也可以将其作为参数传递给DateTime.ToString() 方法。这不会进行任何时区转换 - 它只是确保数字位于正确的位置、使用正确的分隔符以及工作日或月份名称的适当语言。

选项 2

不要将其转换为服务器上的本地时间。

既然您说您正在使用 UTC 值,请确保它们的 .Kind 属性是 Utc。您可能应该在从数据库加载时执行此操作,但如果您必须手动执行此操作:

myDateTime = DateTime.SpecifyKind(myDateTime, DateTimeKind.Utc);

将其以纯 UTC 格式以 ISO8601 等不变格式发送回浏览器。换句话说:

myDateTime.ToString("o");  // example:  "2013-05-02T21:01:26.0828604Z"

在浏览器上使用一些 JavaScript 将其解析为 UTC。它将自动获取浏览器的本地时间设置。一种方法是在 JavaScript 中使用内置的 Date 对象,如下所示:

var dt = new Date('2013-05-02T21:01:26.0828604Z');

但是,这仅适用于支持 ISO-8601 格式的较新浏览器。相反,我建议使用 moment.js 库。它在浏览器之间是一致的,并且对 ISO 日期和本地化有更好的支持。此外,您还可以获得许多其他有用的解析和格式化功能。

// pass the value from your server
var m = moment('2013-05-02T21:01:26.0828604Z');

// use one of the formats supported by moment.js
// this is locale-specific "long date time" format.
var s = m.format('LLLL');

选项 1 的优点是您可以使用任何时区的时间。如果您可以从下拉列表中询问用户他们的时区,那么您不需要使用任何 Javascript。

选项 2 的优点是您可以让浏览器为您完成一些工作。如果您要发送原始数据,例如对 WebAPI 进行 AJAX 调用,这是最好的方法。但是,JavaScript 只知道 UTC 和浏览器的本地时区。因此,如果您需要转换到其他区域,则效果不佳。

您还应该注意,如果您选择选项 #2,您可能会受到 ECMAScript 5.1 设计中的缺陷的影响。如果您正在使用一组不同的夏令时规则所涵盖的日期,而不是当前有效的日期,这就会发挥作用。你可以阅读更多in this question和on my blog。

如果我们在 HTTP 标头中包含一些时区信息会容易得多,但不幸的是我们没有。有很多障碍需要跳过,但这是兼具灵活性和准确性的最佳方式。

【讨论】:

很好的答案!您说这也可以通过 JavaScript 中的内置 Date 对象来完成。您能否更新您的答案以说明如何做到这一点? @Scott - 已更新。谢谢。【参考方案2】:

简短的回答是你不能。 HTTP 不要求(甚至不提供标准方式)用户代理(浏览器)在 HTTP 请求中提供本地时间或时区信息。

你要么需要

询问用户他们的首选时区,或 让客户端 javascript 以某种方式向您报告(cookie?ajax?其他?)

请记住,客户端 JavaScript 解决方案也不是完美的。禁用 Javascript(或不存在,对于某些浏览器)。 Javascript 可能无法访问时区信息。等等。

【讨论】:

以上是关于将 UTC 日期时间全局转换为用户指定的本地日期时间的主要内容,如果未能解决你的问题,请参考以下文章

Sequelize将日期从UTC转换为本地

将UTC时间字符串转换为用户的本地时间[重复]

iOS将NSDate的UTC日期转换为本地时间以获取本地通知

将 UTC 纪元转换为本地日期

如何使用js将UTC日期转换为本地时间? [复制]

JSON如何反序列化日期时间并将UTC转换为指定时区?