如何优雅地处理时区

The*_*Sky 135 .net c# asp.net-mvc timezone asp.net-mvc-3

我的网站托管在与使用该应用程序的用户不同的时区.除此之外,用户还可以拥有特定的时区.我想知道其他SO用户和应用程序如何处理这个问题?最明显的部分是在DB内部,日期/时间以UTC格式存储.在服务器上时,所有日期/时间都应以UTC格式处理.但是,我看到了我要克服的三个问题:

  1. 以UTC格式获取当前时间(轻松解决DateTime.UtcNow).

  2. 从数据库中提取日期/时间并将其显示给用户.可能有很多调用在不同视图上打印日期.我在考虑可以解决这个问题的视图和控制器之间的某个层.或者有自定义扩展方法DateTime(见下文).主要的缺点是在视图中使用日期时间的每个位置,必须调用扩展方法!

    这也会增加使用类似的东西的难度JsonResult.你不能再轻易打电话了Json(myEnumerable),它必须是Json(myEnumerable.Select(transformAllDates)).也许AutoMapper可以在这种情况下提供帮助?

  3. 从用户获取输入(本地到UTC).例如,使用日期发布表单需要将日期转换为UTC.首先想到的是创建一个自定义ModelBinder.

这是我想在视图中使用的扩展:

public static class DateTimeExtensions
{
    public static DateTime UtcToLocal(this DateTime source, 
        TimeZoneInfo localTimeZone)
    {
        return TimeZoneInfo.ConvertTimeFromUtc(source, localTimeZone);
    }

    public static DateTime LocalToUtc(this DateTime source, 
        TimeZoneInfo localTimeZone)
    {
        source = DateTime.SpecifyKind(source, DateTimeKind.Unspecified);
        return TimeZoneInfo.ConvertTimeToUtc(source, localTimeZone);
    }
}
Run Code Online (Sandbox Code Playgroud)

考虑到很多应用程序现在都是基于云的,服务器的本地时间可能与预期的时区差别很大,我认为处理时区会是如此常见.

以前优雅地解决了吗?有什么我想念的吗?非常感谢您的想法和想法.

编辑:为了清除一些混乱,我想添加更多细节.现在的问题不是如何在数据库中存储UTC时间,而是更多关于从UTC-> Local和Local-> UTC的过程.正如@Max Zerbini所指出的那样,将UTC-> Local代码放在视图中显然很聪明,但是DateTimeExtensions真的使用了答案吗?当从用户那里获得输入时,接受日期作为用户的本地时间(因为这是JS将使用的)然后使用a ModelBinder转换为UTC是否有意义?用户的时区存储在数据库中,可以轻松检索.

J. *_*mes 101

这不是一个推荐,它更多地分享范式,但我在网络应用程序(不是ASP.NET MVC独有)中处理时区信息的最激进的方式如下:

  • 服务器上的所有日期时间均为UTC.这意味着像你说的那样使用DateTime.UtcNow.

  • 尽量不要相信客户端尽可能少地将日期传递给服务器.例如,如果您需要"now",请不要在客户端上创建日期,然后将其传递给服务器.在GET中创建日期并将其传递给ViewModel或POST DateTime.UtcNow.

到目前为止,相当标准的票价,但这是事情变得"有趣"的地方.

  • 如果您必须接受来自客户端的日期,请使用javascript确保您发布到服务器的数据是UTC.客户端知道它所处的时区,因此可以合理准确地将时间转换为UTC.

  • 在渲染视图时,他们使用HTML5 <time>元素,他们永远不会直接在ViewModel中呈现日期时间.它被实现为HtmlHelper扩展,类似于Html.Time(Model.when).它会呈现<time datetime='[utctime]' data-date-format='[datetimeformat]'></time>.

    然后他们会使用javascript将UTC时间转换为客户端本地时间.该脚本将查找所有<time>元素并使用date-formatdata属性格式化日期并填充元素的内容.

通过这种方式,他们无需跟踪,存储或管理客户端时区.服务器不关心客户端的时区,也不关心任何时区翻译.它只是吐出UTC并让客户端将其转换为合理的东西.这很容易从浏览器中获取,因为它知道它所在的时区.如果客户端更改了他/她的时区,Web应用程序将自动更新.他们存储的唯一内容是用户区域设置的日期时间格式字符串.

我不是说这是最好的方法,但它是我以前从未见过的另一种方法.也许你会从中收集一些有趣的想法.

  • 我可以看到这种方法的一个缺点是,当你将时间用于其他事情,创建pdf,电子邮件等时,没有时间元素,所以你仍然需要手动转换它们.否则,非常整洁的解决方案 (3认同)
  • 我已经开展了另一个需要本地日期的项目,这就是我使用的方法.我正在使用[moment.js](http://momentjs.com/)来进行日期/时间格式化......这很好.谢谢! (2认同)

Nes*_*tor 13

经过多次反馈,这是我的最终解决方案,我认为这个解决方案简洁明了,涵盖了夏令时问题.

1 - 我们在模型级别处理转换.所以,在Model类中,我们写道:

    public class Quote
    {
        ...
        public DateTime DateCreated
        {
            get { return CRM.Global.ToLocalTime(_DateCreated); }
            set { _DateCreated = value.ToUniversalTime(); }
        }
        private DateTime _DateCreated { get; set; }
        ...
    }
Run Code Online (Sandbox Code Playgroud)

2 - 在全局帮助器中,我们创建自定义函数"ToLocalTime":

    public static DateTime ToLocalTime(DateTime utcDate)
    {
        var localTimeZoneId = "China Standard Time";
        var localTimeZone = TimeZoneInfo.FindSystemTimeZoneById(localTimeZoneId);
        var localTime = TimeZoneInfo.ConvertTimeFromUtc(utcDate, localTimeZone);
        return localTime;
    }
Run Code Online (Sandbox Code Playgroud)

3 - 我们可以通过在每个用户配置文件中保存时区ID来进一步改进这一点,这样我们就可以从用户类中检索而不是使用常量"中国标准时间":

public class Contact
{
    ...
    public string TimeZone { get; set; }
    ...
}
Run Code Online (Sandbox Code Playgroud)

4 - 在这里,我们可以获取要显示给用户的时区列表,以便从下拉框中进行选择:

public class ListHelper
{
    public IEnumerable<SelectListItem> GetTimeZoneList()
    {
        var list = from tz in TimeZoneInfo.GetSystemTimeZones()
                   select new SelectListItem { Value = tz.Id, Text = tz.DisplayName };

        return list;
    }
}
Run Code Online (Sandbox Code Playgroud)

所以,现在上午9:25在中国,网站在美国托管,日期保存在UTC数据库中,这是最终结果:

5/9/2013 6:25:58 PM (Server - in USA) 
5/10/2013 1:25:58 AM (Database - Converted UTC)
5/10/2013 9:25:58 AM (Local - in China)
Run Code Online (Sandbox Code Playgroud)

编辑

感谢Matt Johnson指出了原始解决方案的薄弱环节,并且对于删除原始帖子感到遗憾,但是得到了正确的代码显示格式的问题......结果编辑器在将"子弹"与"预编码"混合时遇到了问题,所以我删除了bulles,没关系.


cas*_*One 8

sf4answersevents部分中,用户输入事件的地址,以及开始日期和可选的结束日期.这些时间被转换为SQL服务器,用于计算与UTC的偏移量.datetimeoffset

这是你面临的同样问题(虽然你正在采用不同的方法,因为你正在使用DateTime.UtcNow); 你有一个位置,你需要将时间从一个时区转换为另一个时区.

我做了两件主要的事情对我有用.首先,始终使用DateTimeOffset结构.它占据了与UTC的偏差,如果您可以从客户那里获得这些信息,它会让您的生活更轻松.

其次,在执行翻译时,假设您知道客户端所在的位置/时区,您可以使用公共信息时区数据库将时间从UTC转换到另一个时区(或者如果您愿意,可以在两个时区之间进行三角测量)时区).关于tz数据库(有时也称为Olson数据库)的好处是,它记录了整个历史中时区的变化; 获得抵消是您想要获得抵消的日期的函数(只需看看2005年能源政策法案,法案改变了夏令时在美国生效的日期).

有了数据库,您可以使用ZoneInfo(tz数据库/ Olson数据库).NET API.请注意,没有二进制发行版,您必须下载最新版本并自行编译.

在撰写本文时,它目前正在解析最新数据发布中的所有文件(我实际上是在9月25日针对ftp://elsie.nci.nih.gov/pub/tzdata2011k.tar.gz文件运行它, 2011年; 2017年3月,您可以通过https://iana.org/time-zonesftp://fpt.iana.org/tz/releases/tzdata2017a.tar.gz获取.

所以在sf4answers上,获取地址后,它被地理编码为纬度/经度组合,然后发送到第三方Web服务以获得与tz数据库中的条目对应的时区.从那里,开始和结束时间被转换为DateTimeOffset具有适当UTC偏移的实例,然后存储在数据库中.

至于在SO和网站上处理它,它取决于观众和你想要展示的内容.如果您注意到,大多数社交网站(以及SO和sf4answers上的事件部分)以相对时间显示事件,或者,如果使用绝对值,则通常为UTC.

但是,如果您的受众需要本地时间,那么使用DateTimeOffset带有时区转换为的扩展方法就可以了.SQL数据类型datetimeoffset将转换为.NET DateTimeOffset,然后您可以获得使用该GetUniversalTime方法的通用时间.从那里,你只需使用ZoneInfo类上的方法将UTC转换为本地时间(你需要做一些工作才能把它变成一个DateTimeOffset,但这很简单).

在哪里进行转型?这就是你将要付出成本的地方,而且也没有"最好"的方式.我会选择视图,时区偏移作为视图模型的一部分呈现给视图.这样,如果视图的要求发生变化,则无需更改视图模型以适应更改.你JsonResult将只包含与模型偏移量.IEnumerable<T>

在输入端,使用模型绑定器?我绝对不敢说.您无法保证所有日期(现在或将来)都必须以这种方式进行转换,它应该是您的控制器执行此操作的显式功能.同样,如果需求发生变化,您不必调整一个或多个ModelBinder实例来调整业务逻辑; 它业务逻辑,这意味着它应该在控制器中.


Mas*_*ini 5

这只是我的观点,我认为MVC应用程序应该将井数据表示问题与数据模型管理分开.数据库可以在本地服务器时间存储数据,但表示层的任务是使用本地用户时区呈现日期时间.在我看来,这与I18N和不同国家的数字格式相同.在您的情况下,您的应用程序应检测Culture用户的时区并更改显示不同文本,数字和datime演示文稿的视图,但存储的数据可以具有相同的格式.