How to elegantly deal with timezones
我有一个网站托管在与使用该应用程序的用户不同的时区。除此之外,用户还可以有一个特定的时区。我想知道其他SO用户和应用程序如何处理这个问题?最明显的部分是,在数据库中,日期/时间存储在UTC中。在服务器上时,所有日期/时间都应以UTC处理。但是,我看到了三个我正在努力克服的问题:
获取当前时间(使用
从数据库中提取日期/时间并将其显示给用户。在不同的视图上打印日期可能有很多调用。我在考虑视图和控制器之间的一些层,可以解决这个问题。或者在
这也会给使用像
从用户(本地到UTC)获取输入。例如,发布带有日期的表单需要将日期转换为之前的UTC。首先想到的是创建一个自定义的
以下是我在视图中考虑使用的扩展:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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); } } |
我认为处理时区是一件很常见的事情,因为现在很多应用程序都是基于云的,服务器的本地时间可能与预期的时区大不相同。
这个问题以前解决得很好吗?我有什么东西不见了吗?思想和想法很受欢迎。
编辑:为了消除一些混乱,我想增加一些细节。现在的问题不是如何将UTC时间存储在数据库中,而是如何从UTC->Local和Local->UTC进行存储。正如@max zerbini指出的那样,将UTC->local代码放在视图中显然是明智的,但是使用
不是说这是一个建议,它更共享一个范例,而是我所看到的处理Web应用程序中时区信息(这不是ASP.NET MVC独有的)的最强烈方式是:
服务器上的所有日期时间都是UTC。这意味着,就像你说的,使用
DateTime.UtcNow 。尽量不要信任将日期传递给服务器的客户端。例如,如果您需要"现在",不要在客户机上创建日期,然后将其传递给服务器。要么在GeT中创建一个日期并将其传递给ViewModel,要么在Do-
DateTime.UtcNow 之后创建。
到目前为止,价格相当标准,但这正是事情变得"有趣"的地方。
如果您必须接受来自客户机的日期,那么使用javascript来确保您要发布到服务器的数据是UTC格式的。客户机知道它所处的时区,因此可以以合理的精度将时间转换为UTC。
在呈现视图时,他们使用html5
元素,永远不会直接在视图模型中呈现日期时间。它作为 HtmlHelper 扩展实现,类似于Html.Time(Model.when) 。它将使变为。 然后他们将使用javascript将UTC时间转换为客户机的本地时间。脚本将查找所有
元素,并使用 date-format 数据属性来格式化日期并填充元素的内容。
这样他们就不必跟踪、存储或管理客户的时区。服务器不关心客户机在哪个时区,也不需要进行任何时区转换。它只需吐出UTC,让客户将其转换为合理的内容。这在浏览器中很容易,因为它知道它在哪个时区。如果客户端更改了他/她的时区,Web应用程序将自动更新自己。它们存储的唯一内容是用户区域设置的日期时间格式字符串。
我不是说这是最好的方法,但这是我以前从未见过的另一种方法。也许你会从中收集一些有趣的想法。
在几次反馈之后,这里是我的最终解决方案,我认为这是干净和简单的,涵盖了日光节约问题。
1-我们在模型级别处理转换。因此,在模型类中,我们编写:
1 2 3 4 5 6 7 8 9 10 11 | public class Quote { ... public DateTime DateCreated { get { return CRM.Global.ToLocalTime(_DateCreated); } set { _DateCreated = value.ToUniversalTime(); } } private DateTime _DateCreated { get; set; } ... } |
2-在全局帮助程序中,我们将自定义函数"toLocalTime":
1 2 3 4 5 6 7 | public static DateTime ToLocalTime(DateTime utcDate) { var localTimeZoneId ="China Standard Time"; var localTimeZone = TimeZoneInfo.FindSystemTimeZoneById(localTimeZoneId); var localTime = TimeZoneInfo.ConvertTimeFromUtc(utcDate, localTimeZone); return localTime; } |
3-我们可以通过在每个用户配置文件中保存时区ID来进一步改进这一点,这样我们就可以从用户类中检索,而不是使用恒定的"中国标准时间":
1 2 3 4 5 6 | public class Contact { ... public string TimeZone { get; set; } ... } |
4-在这里,我们可以从下拉框中获取要向用户显示的时区列表:
1 2 3 4 5 6 7 8 9 10 | 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; } } |
因此,现在在中国的上午9:25,网站在美国托管,日期保存在数据库的UTC中,下面是最终结果:
1 2 3 | 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) |
编辑
感谢Matt Johnson指出了原始解决方案的薄弱环节,并为删除原始文章感到抱歉,但在获取正确的代码显示格式时遇到了问题…原来编辑把"子弹"和"预编码"混在一起有问题,所以我去掉了"牛头",没问题。
在对事件的sf4answers部分,用户输入地址的安全事件,以及一个安全的optional开始日期和结束日期。这些时代是一个
这是你在面对相同的问题(虽然你是以一个不同的方法对它,你是在使用
有两个主要的事情我做的,是为我工作。首先,使用
第二,当进行假设的翻译,你知道的,client位置/时间是在监狱中,你可以使用的公共数据库的信息时代翻译不能从大区到另一个时间GMT时间区(或triangulate之间,如果你会,两次zones)。关于the的东西tz数据库(有时被称为"大账户数据库Olson)是变化的,它的时间为zones的整个历史是一个功能的安全得到粒状的日期,你想得到粒状(只是看在2005年的能源政策法案的改变,当日光储蓄时间日期在不影响按成他在美国)。
与该数据库在手,你可以使用的zoneinfo(tz数据库/数据库API .net Olson)。注意,不是二进制分布有一个,你会有大的最新版本下载和编译时它自己。
在本文的写作时间,它parses目前所有的最新文件在数据分布(我跑它实际上是对的/ elsie.nci.nih.gov FTP:文件/酒吧/ tzdata2011k.tar.gz在九月25,2011;在2017年三月,你会得到它通过HTTPS iana.org:/ / /时间zones或从FTP fpt.iana.org tz:/ / / / / tzdata2017a.tar.gz)发布的。
在sf4answers相比后得到的地址,它是geocoded longitude组合成一个latitude /,然后发送到一个第三方网站的服务,得到对应于一个timezone tz进入安全的数据库。从那里,开始和结束的时间是与适当的情况下为该
作为为处理和与它在websites相比,这取决于你的观众和我们都试图显示。如果你会注意到,大多数的社会websites(和比较,和在显示部分sf4answers事件)事件在相对安全的时间,或者,如果是用absolute价值,这是通常GMT。
然而,如果你的expects当地观众的时代,然后使用安全的方法,
大的转型的原因在哪里?这是一个成本的付出,你是要有个地方,和有没有"最佳"的方式。我想opt的观点虽然为粒状,与作为部分的timezone观模型提出的观点。的方式,如果为观要求的改变,你不改变你的观点有很大的变化accommodate模型。你的
在一个模型的输入侧,采用binder?我想说,没有绝对的方法。你不能guarantee(现在所有的日期,或在未来将有大)就在这种方式,它应该是很明确的功能的控制器进行大的这一行动。再次,如果你不改变的要求,有一个或许多情况下tweak
这正是我的观点,我认为MVC应用程序应该将井数据表示问题与数据模型管理分开。数据库可以在本地服务器时间存储数据,但表示层有责任使用本地用户时区呈现日期时间。在我看来,这和不同国家的I18N和数字格式有着相同的问题。在您的情况下,应用程序应该检测用户的
对于输出,创建这样的显示/编辑器模板
1 2 | @inherits System.Web.Mvc.WebViewPage<System.DateTime> @Html.Label(Model.ToLocalTime().ToLongTimeString())) |
如果只希望某些模型使用这些模板,可以基于模型上的属性绑定它们。
有关创建自定义编辑器模板的详细信息,请参阅此处和此处。
或者,由于您希望它既适用于输入又适用于输出,我建议您扩展一个控件,甚至创建自己的控件。这样,您就可以截取输入和输出,并根据需要转换文本/值。
如果你想沿着这条路走下去,这个链接很有希望把你推向正确的方向。
不管怎样,如果你想要一个优雅的解决方案,它将是一个有点工作。好的一面是,一旦你完成了它,你就可以把它保存在代码库中以备将来使用!
这可能是破解一个螺母的重锤,但您可以在UI和业务层之间插入一个层,该层透明地将返回的对象图上的日期时间转换为本地时间,并在输入日期时间参数上转换为UTC。
我想这可以通过使用Postharp或一些反向控制容器来实现。
就个人而言,我只想在用户界面中显式地转换您的日期时间…