如何在SQL Server中正确处理TimeZone?


34

我的本地开发服务器在中东,但我的生产服务器在英国。

我需要向用户显示其所在时区的日期。例如,如果用户在沙特阿拉伯,那么我需要根据沙特阿拉伯格式显示时间。

是否应该创建一个名为TimeZone的新数据库表并将时间保存在UTC中?



这是Web应用程序还是桌面应用程序,还是...?客户端交付机制的实现方式差异很大,因为您需要的取决于客户端。
乔恩·塞格尔

该网站以及本机手机。是的,这会对不同的客户产生不同的影响。
user960567 2013年

1
似乎您的问题不仅涉及数据库本身,还涉及应用程序开发。这个堆栈溢出问题是一个不可不看的,你那么:夏令时和时区的最佳实践

Answers:


48

不幸的是,没有快速解决方案。应用程序的国际化应作为最初设计讨论的一部分,因为它实际上进入了许多不同领域的核心,包括日期/时间比较和输出格式。

无论如何,要走正确的道路,必须将时区信息与时间存储在一起。换句话说,意识到日期/时间20130407 14:50没有意义的,而没有(a)包括时区当时的UTC偏移量(注释1),或者(b)确保所有插入这些值的逻辑先转换为某个固定偏移量(最有可能是0)。没有这些东西,两个给定的时间值是不可比的,并且数据已损坏。(顺便说一句,后一种方法正在玩火(注2);请勿这样做。)

在SQL Server 2008+中,您可以使用datetimeoffset数据类型直接将偏移量与时间一起存储。(为完整起见,在2005年及之前,我将添加第二列以存储当时的当前UTC偏移值(以分钟为单位)。)

这使桌面型应用程序变得容易,因为这些平台通常具有将日期/时间+时区自动转换为本地时间,然后根据用户的区域设置进行格式化以输出的机制。

对于本质上是断开连接的架构的Web,即使正确设置了后端数据,它也更加复杂,因为您需要有关客户端的信息才能进行转换和/或格式化。这通常是通过用户首选项设置(应用程序在输出之前转换/格式化事物)来完成的,或者只是对所有人显示具有相同固定格式和时区偏移的事物(这是Stack Exchange平台当前所做的事情)。

您会看到如果后端数据设置不正确,如何很快使它变得复杂和棘手。我不建议您选择任何一条路径,因为您最终会遇到更多问题。

注1:

时区的UTC偏移量不是固定的:考虑夏令时,其中时区的UTC偏移量会相差一个小时或一个小时。另外,区域的夏时制日期也会定期变化。因此,使用datetimeoffset(或复合的local timeUTC offset at that time)的结果在最大信息恢复。

笔记2:

这是关于控制数据输入的。虽然没有万无一失的方法来验证传入的值,但是最好执行一个不涉及计算的简单标准。如果公共API期望包含偏移量的数据类型,则调用者将清楚该要求。

如果不是这种情况,则调用者必须依靠文档(如果他们阅读了文档),或者计算不正确,等等。需要偏移量时,失败/错误模式更少,尤其是对于分布式系统(甚至是单独服务器上的网络/数据库(如此处所示)。

无论如何,存储偏移量可以用一块石头杀死两只鸟。即使现在不需要,也可以在以后根据需要使用。的确,它占用了更多的存储空间,但是我认为这是值得进行权衡的,因为如果一开始就从不记录数据,那么数据将会丢失。


3
据我了解,偏移量与时区不同...存储在datetimeoffset中的时间会丢失诸如夏令时意识等信息...更多信息:stackoverflow.com/tags/timezone/info
Peter McEvoy

1
@PeterMcEvoy DST在这里没有相关性。datetimeoffset意思是“这是发生事件时用户/计算机的本地时间”-我们不需要知道DST是否有效,因为给定的UTC偏移始终存在(嵌入在datetimeoffset值中)。
2013年

12

我为SQL Server中的时区转换开发了一个全面的解决方案。请参阅GitHub上的SQL Server时区支持

它可以正确处理时区之间以及往返于UTC的转换,包括夏令时。

它在概念上与adss的答案中所述的“ T-SQL工具箱”解决方案相似,但是它使用的是更常见的IANA / Olson / TZDB时区,而不是Microsoft的时区,并且它包括一些实用程序,可将数据保持为最新版本。 TZDB问世。

用法示例(回答OP):

SELECT Tzdb.UtcToLocal(sysutcdatetime(), 'Asia/Riyadh') as CurrentTimeInSaudiArabia

有关其他API和详细说明,请参见GitHub上的自述文件

另外,请注意,由于这是基于UDF的解决方案,因此其设计并非以性能为主要目标。如果将此功能内置到SQL Server中,效果会更好,类似于CONVERT_TZOracle和MySQL中该功能的存在方式。


6

SQL Server从2005年开始进行了修改,将内部时区保存在UTC中。这主要是由于地理复制和HA投影机涉及到原木运输,并且原木运输时间保存在不同时区中,使得旧方法无法恢复原木。

因此,在UTC时间内部保存所有内容使SQL Server可以在全局范围内正常工作。这就是为什么夏令时在Windows中很难解决的原因之一,因为其他MS产品(例如Outlook)也在内部将日期/时间保存为UTC,并创建了需要修补的偏移量。

我在一家公司工作,那里有成千上万的服务器(虽然不是MS SQL Server,而是各种各样的服务器)分布在世界各地,并且如果我们不专门强迫所有事情通过UTC进行处理,我们都会发疯很快


5

我已经在Codeplex上开发并发布了“ T-SQL工具箱”项目以帮助在SQL Server中处理日期时间和时区问题的任何人。它是开源的,完全免费使用。

它提供了使用普通T-SQL的简便的日期时间转换UDF,并带有开箱即用的其他配置表。

在您的示例中,您可以使用以下示例:

SELECT [DateTimeUtil].[UDF_ConvertLocalToLocalByTimezoneIdentifier] (
    'GMT Standard Time', -- the original timezone in which your datetime is stored
    'Middle East Standard Time', -- the target timezone for your user
    '2014-03-30 01:55:00' -- the original datetime you want to convert
)

这将为您的用户返回转换后的日期时间值。

在T-SQL工具箱数据库中也提供的表“ DateTimeUtil.Timezone”中可以找到所有受支持的时区的列表。


这样的工具包似乎对开发人员有很大帮助。但是,标量函数是查询优化程序的黑匣子。这意味着当我将函数应用于列时,您提到的配置表将被击中的次数与结果集中有行的次数相同(假设我仅在SELECT子句中使用该函数)。就性能而言,这似乎不是一个很好的观点。不过,我还没有真正测试过您的工具箱,因此我不能肯定地说,这只是基于我所知的普遍问题。
Andriy M 2014年

从性能角度来看,安德里(Andriy)当然是正确的。UDF查询表不是为海量数据操作而设计的,而是易于使用的。但是,它们在我的机器上最多可以完成约100.000条记录。
2014年

2
如果一个人必须在用例中处理更多记录,并且性能很重要,那么您最好考虑保留预先计算的数据。但是即使那样,这些UDF仍可能有助于将所需的时区中的数据时间值保持不变。
广告

从我的角度来看,这个问题与性能无关,而仅与获得基本功能有关。能够从查询中返回准确的信息是第一件事。制定一些临时表来缓存内容,然后在查询中引用该表,或者您可以通过处理临时表来提高性能,这是次要的。
行动丹

1

我可能会遗漏一些明显的东西,但是如果您要做的就是使用用户时区和语言格式显示日期,最简单的方法是始终将日期存储在数据库中的UTC中,并让客户端应用程序从UTC转换为本地时区并使用用户区域设置相应地设置日期格式。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.