夏令时和时区最佳实践

2191
我希望将这个问题及其答案作为处理夏令时的权威指南,特别是处理实际更改的过程。许多系统依赖于精确的时间记录,问题在于由于夏令时的更改 - 将时钟向前或向后移动而导致的时间更改。例如,在订单系统中,有基于订单时间的业务规则 - 如果时钟更改,则规则可能不太清晰。订单时间应如何持久化?当然,有无数的情况 - 这只是一个说明性的例子。您是如何处理夏令时问题的?您的解决方案包括哪些假设?(在这里寻找上下文)同样重要的是:您尝试了哪些没有奏效的方法?为什么它没有奏效?我会对编程、操作系统、数据持久性和其他相关方面的问题感兴趣。一般性的回答很好,但如果只在一个平台上可用,我也想看到细节。

2
@abatishchev - 这样在 SQL 中使用 GETDATE() 将会是 UTC 时间(DateTime.Now 也是)。而且服务器不会受到任何自动 DST 更改的影响。 - Oded
4
@Oded:如果加上“在服务器上”,我同意。但这仍然可能会影响其他需要本地时间的应用程序。因此,出于这个原因和其他原因,我认为更好的方法是显式请求协调世界时(UTC)时间。 - abatishchev
7
UTC比GMT更受欢迎,因为它的定义更加精确,而且在一些操作系统上GMT的定义混乱。人们通常将“GMT”和“UTC”视为可以互换使用,但它们并非完全相同。几乎对于任何软件/系统目的,请使用UTC。请参见https://dev59.com/8nE95IYBdhLWcg3wft1w。 - Chris Johnson
7
@JoshStodola -- Jon Skeet的'答案'。 - Kenny Evitt
2
@Oded 你不能假设服务器将使用UTC时区,我曾经见过生产服务器使用“UTC”时区,但应用了夏令时,因此在一年的多半时间里实际上是UTC+1。同意@abatishchev的建议,要明确地使用 'DateTime.UtcNow' 和 'GETUTCDATE()',这也说明了你已经考虑到这个问题并且专业。 - ono2012
显示剩余4条评论
30个回答

1084

答案和其他数据的总结:(请添加您的答案)

要做:

  • 无论何时涉及到一个确切的时间点,请按照统一标准持久化时间,不受夏令时影响。(GMT和UTC在这方面是等价的,但最好使用UTC。请注意,UTC也被称为ZuluZ时间。)
  • 如果您选择使用本地时间值持久化(过去的)时间,请包括此特定时间与UTC的本地时间偏移量(此偏移量可能会在一年中发生变化),以便稍后可以明确地解释时间戳。
  • 在某些情况下,您可能需要存储UTC时间和等效本地时间。通常情况下,这是通过两个单独的字段完成的,但有些平台支持可以在单个字段中存储两者的datetimeoffset类型。
  • 将时间戳存储为数字值时,请使用Unix时间 - 即从1970-01-01T00:00:00Z(不包括闰秒)开始的整秒数。如果需要更高的精度,请改用毫秒。此值应始终基于UTC,不进行任何时区调整。
  • 如果您可能稍后需要修改时间戳,请包括原始时区ID,以便确定偏移量是否可能已从记录的原始值更改。
  • 在安排未来事件时,通常应使用本地时间而不是UTC,因为偏移量可能会更改。请参阅答案blog post
  • 存储整个日期(例如生日和纪念日)时,请勿转换为UTC或任何其他时区。
    • 如果可能,请存储在仅包括日期而不包括时间的日期数据类型中。
    • 如果没有这样的类型,请确保在解释该值时始终忽略时间。如果不能保证将忽略时间,则选择中午12:00而不是半夜00:00作为该天上的较安全的代表时间。
  • 请记住,时区偏移量不始终是小时数的整数倍(例如,印度标准时间为UTC+05:30,尼泊尔使用UTC+05:45)。
  • 如果使用Java,请在Java 8及更高版本中使用java.time
  • 这些java.time功能的大部分都已回溯到Java 6和7中的ThreeTen-Backport库。
  • 早期Android(<26)还进一步适用于ThreeTenABP库。
  • 这些项目正式取代了备受推崇的Joda-Time,现在处于维护模式。 Joda-Time,ThreeTen-Backport,ThreeTen-Extrajava.time类和JSR 310由同一人Stephen Colebourne领导。
  • 如果使用.NET,请考虑使用Noda Time
  • 如果没有使用Noda Time的.NET,请注意,DateTimeOffset通常比DateTime更好。
  • 如果使用Perl,请使用DateTime
  • 如果使用Python 3.9或更高版本,请使用内置的zoneinfo处理时区。否则,请使用dateutilarrow。一般应避免使用旧的pytz库。
  • 如果使用JavaScript,请避免使用不再得到积极维护的旧版moment.jsmoment-timezone库。有关更多详情,请参阅Moment.js项目状态。而是请考虑使用Luxondate-fnsday.jsjs-joda
  • 如果使用PHP> 5.2,请使用由DateTimeDateTimeZone类提供的本机时区转换。在使用DateTimeZone :: listAbbreviations()时要小心 - 请参阅答案。要使PHP保持最新的Olson数据,请定期安装the timezonedb PECL软件包;请参阅答案
  • 如果使用C ++,请确保使用正确实现IANA时区数据库的库。这些包括cctzICUHoward Hinnant的“tz”库。在C ++20中,后者被采用为标准<chrono>库。
    • 不要使用Boost进行时区转换。虽然其API声称支持标准的IANA(又名“zoneinfo”)标识符,但它粗略地映射到POSIX风格的数据,而不考虑每个区域可能有的丰富变化历史。(此外,该文件已经停止维护。)
  • 如果使用Rust,请使用chrono
  • 大多数业务规则使用民用时间,而不是UTC或GMT。因此,在应用程序逻辑之前,计划将UTC时间戳转换为本地时区。
  • 请记住,时区和偏移量并非固定不变,可能会更改。例如,历史上美国和英国使用相同的日期“向前跳”和“回撤”。但是,在2007年,美国改变了更改时钟的日期。现在,一年中有48周的伦敦时间和纽约时间之间的差异为5小时,而在4周(春季3周,秋季1周)中为4小时。在涉及多个区域的任何计算中,请注意此类项目。
  • 根据需要考虑存储哪种类型的时间(实际事件时间、广播时间、相对时间、历史时间、循环时间),以及需要存储哪些元素(时间戳、时区偏移量和时区名称)以进行正确检索 - 请参阅this answer中的“时间类型”。
  • 保持您的操作系统、数据库和应用程序tzdata文件在彼此之间和与世界其他地方同步。
  • 在服务器上,将硬件时钟和操作系统时钟设置为UTC而不是本地时区。
  • 无论上一个要点如何,服务器端代码,包括网站,都不应该期望服务器的本地时区是任何特定值。请参阅答案
  • 在应用程序代码中优先按情况逐个处理时区,而不是全局通过配置文件设置或默认设置处理它们。
  • 在所有服务器上使用NTP服务。
  • 如果使用FAT32,请记住时间戳存储在本地时间而不是UTC中。
  • 在处理重复事件时(例如每周电视节目),请记住时间会随着DST的变化而改变,并且在不同的时区中将不同。
  • 始终查询日期时间值作为下限包括,上限排除> =<)。

不要:

  • 不要混淆“时区”(例如America/New_York)和“时区偏移量”(例如-05:00)。它们是两个不同的概念。请参见时区标签 wiki
  • 不要在旧版 Web 浏览器中使用 JavaScript 的 Date 对象进行日期和时间计算,因为 ECMAScript 5.1 及更低版本存在设计缺陷可能会错误地使用夏令时。(这在 ECMAScript 6 / 2015 中已修复)。
  • 永远不要相信客户端的时钟。它很可能是不正确的。
  • 不要告诉人们“无论何时都要在全球使用协调世界时(UTC)”。这种广泛的建议短视,本文档中描述了几种有效的情况。相反,应该针对正在处理的数据使用适当的时间参考。 (时间戳可以使用 UTC,但未来的时间调度和仅日期值不应该使用。)

测试:

  • 测试时,请确保测试西半球、东半球、北半球和南半球的国家(实际上是全球的四个区域),同时测试正在进行夏令时和没有夏令时的情况(共计8个),以及一个不使用夏令时的国家(另外4个,覆盖所有地区,总共12个)。
  • 测试夏令时转换,即当您当前处于夏令时时,选择从冬季选择一个时间值。
  • 测试边界情况,例如UTC+12时区,夏令时,使本地时间为夏季UTC+13,甚至是冬季UTC+13的地方
  • 测试所有第三方库和应用程序,并确保它们正确处理时区数据。
  • 至少测试半小时时区。

参考:

其他:

  • 游说你的代表结束夏令时这种可悲的事情。我们总是可以抱有希望...
  • 游说地球标准时间

32
使用GMT并不能真正解决问题,你仍然需要找出应该应用何种正确的时间差,而这就是所有复杂性所在。在被不同地区用户使用(具有不同夏令时切换时间表)或显示历史/未来数据的系统中,确定时间差可能会很复杂。 - ckarras
13
如果所有应用程序需要做的只是显示当前本地时间,那么这是正确的。但是,如果我们例如在澳大利亚有一个服务器,需要显示加拿大2006年4月2日(这是加拿大夏令时切换规则改变之前的最后一年)交易的日期/时间 - 你是否有一个可以执行DateTime(“2006/04/02 14:35Z”)。ToLocalTime()。WithDaylightSavingRules(“Canada”,2006)的操作系统调用? - ckarras
23
是的,在Windows中有这样一个操作系统调用——SystemTimeToTzSpecificLocation。http://msdn.microsoft.com/en-us/library/ms724949(VS.85).aspx - Michael Madsen
7
也许如果你坚持下去,就能够做到。 - Peter Ajtai
22
记住,将未来的日期(即使只是明天)转换为UTC时,总会有些信息会丢失。例如,您可能使用了一些已知的偏移量进行转换,但由于日期在未来,设置这些规则的团体始终有可能更改这些规则。此外,将某些未来日期转换为UTC几乎是不可能的,因为夏令时的具体时间要等到那一年才能确定(一些国家每年都设置夏令时,而不是提前10年或根据任何固定规则)。 - eselk
显示剩余20条评论

346

我不确定在上面的答案中能添加什么,但是这里有几点需要注意:

时间类型

需要考虑四种不同的时间:

  1. 事件时间:例如,国际体育比赛发生的时间,加冕/逝世等。 这取决于事件的时区而不是观众的时区。
  2. 电视时间:例如,某个电视节目在全球各地的当地时间下午9点播出。 在考虑发布结果(例如美国偶像的结果)时要考虑此时间。
  3. 相对时间:例如:本问题开放悬赏,还有21小时结束。 这很容易展示。
  4. 重复时间:例如:每个星期一晚上9点都会有一档电视节目,即使在夏令时改变时也是如此。

还有历史/备用时间。 这些很烦人,因为它们可能无法映射回标准时间。 例如:朱利安日,土星上的阴历日期,克林贡历法。

在UTC中存储开始/结束时间戳非常有效。 对于1,您需要将事件时区名称+偏移量与事件一起存储。 对于2,您需要为每个区域存储本地时间标识符和每个观众存储本地时区名称+偏移量(如果您处于危机之中,则可以从IP中推导出这一点)。 对于3,以UTC秒为单位存储即可,无需时区。 4是1或2的特殊情况,具体取决于它是全球事件还是当地事件,但您还需要存储一个“创建于”时间戳,以便您可以判断时区定义是在此事件创建之前还是之后更改的。 如果需要显示历史数据,则必要。

存储时间

  • 始终将时间存储为UTC
  • 在显示时转换为本地时间(本地由查看数据的用户定义)
  • 在存储时区信息时,需要包括名称、时间戳和偏移量。这是因为政府有时会更改其时区的含义(例如:美国政府更改了夏令时日期),而您的应用程序需要优雅地处理这些变化...例如:《迷失》电视剧所播放的具体时间戳,在夏令时规则更改前后都要考虑。
  • 偏移量和名称

    以上内容的示例为:

    2010年世界杯决赛在南非(UTC+2--SAST)于2010年7月11日19:00 UTC举行。

    通过这些信息,我们可以历史性地确定2010年WCS决赛确切的时间,即使南非时区定义发生更改,并且能够在当用户查询数据库时将其显示为他们当地的时区。

    系统时间

    您还需要确保操作系统、数据库和应用程序的tzdata文件保持同步,彼此之间以及与世界其他地方的时间保持一致,并在升级时进行广泛测试。不是没有先例,你依赖的第三方应用程序没有正确处理TZ更改。

    确保硬件时钟设置为UTC,如果您在全球范围内运行服务器,请确保它们的操作系统也配置为使用UTC。当您需要从多个时区的服务器复制每小时轮换的Apache日志文件时,这变得明显起来。仅按文件名排序仅在所有文件都以相同的时区命名时才起作用。这还意味着在从一个框到另一个框进行ssh并需要比较时间戳时,您不必在脑海中进行日期数学运算。

    此外,在所有盒子上运行ntpd。

    客户端

    不要相信从客户机器获取的时间戳是有效的。例如,HTTP头中的日期:HTTP头文件或javascript Date.getTime()调用。当用作不透明标识符或在同一客户端的单个会话期间进行日期计算时,这些内容是可以使用的,但是不要尝试将这些值与服务器上的某些内容交叉引用。您的客户端没有运行NTP,并且其BIOS时钟可能没有工作电池。

    小知识

    最后,政府有时会做非常奇怪的事情:

    荷兰的标准时间从1909-05-01到1937-06-30被法律明确规定为UTC正好快19分32.13秒。无法使用HH:MM格式准确表示此时区。

    好了,我完成了。


7
这个回答有很好的观点,我特别想指出其中的一部分:"重复时间:例如,一档电视节目每周一晚上9点播出,即使夏令时改变"。将时间以UTC格式存储在数据库中,然后转换以进行显示,有助于处理许多处理时区的棘手问题。但是,处理跨越夏令时界限的“重复事件”会变得更加困难。 - Jared Hales
48
+1 鼓励闲聊知识。保持内容有趣会让任务更加愉快,从而可能提高生产力 :) - Chris
5
这个回答有很多好的内容,但也有一些问题。1)许多时区缩写是有歧义的。在这里看。2)并不总是要存储UTC时间。大多数情况下是需要的,但具体情况取决于上下文。在你的术语中,“电视时间”不会被存储为UTC。此外,有时不存储本地时间是违法或违反政策的,这就是 DateTimeOffset(或等价物)派上用场的地方。除此之外,写得很好。 - Matt Johnson-Pint
1
因此,每个人都同意Joda库是迄今为止可用于此工作的最佳工具。但是,根据这篇文章(http://www.joda.org/joda-time/tz_update.html),我们需要不断更新(重建)JODA jar文件以保持最新的时区信息。是否有标准方法从IANA网站(http://www.iana.org/time-zones)下载最新的时区数据并重建joda库?换句话说,是否可能自动化这个过程而不是手动执行? - saravana_pc
2
荷兰的小知识看起来很真实。http://www.ietf.org/rfc/rfc3339.txt第5.8节。我有点觉得有人在开玩笑。 - ruffin
显示剩余7条评论

93

这是一个重要且令人惊讶的难题。事实上,并没有完全令人满意的持久化时间标准。例如,SQL标准和ISO格式(ISO 8601)显然是不够的。

从概念上讲,人们通常处理两种类型的时间日期数据,并且方便地区分它们(上述标准没有):“物理时间”和“民用时间”。

“物理”时间瞬间是物理学所涉及的连续通用时间线上的点(当然忽略相对论)。例如,这个概念可以在UTC中被充分编码-持久化(如果您可以忽略闰秒)。

“民用”时间是遵循民用规范的日期时间规范:此处的某个时间点由一组日期时间字段(Y、M、D、H、MM、S、FS)加上TZ(时区规范)(实际上也是“日历”;但假设我们将讨论限制在格里高利日历上)来完全指定。 时区和日历联合允许(原则上)从一种表示到另一种表示的映射。 但是,民用和物理时间瞬间是根本不同类型的量,应在概念上分开并以不同方式处理(类比:字节数组和字符字符串)。

这个问题很令人困惑,因为我们可以互换地使用这些类型的事件,并且民用时间受政治变化的影响。 对于未来的事件,问题(和需要区分这些概念)变得更加明显。例如(摘自我的讨论here)。

John在他的日历中记录了一个提醒事件的日期时间:2019年7月27日,10:30:00,TZ=Chile/Santiago,(其偏移量为GMT-4,因此对应于UTC2019年7月27日14:30:00)。 但是,在未来的某一天,该国决定将TZ偏移更改为GMT-5。

现在,当这一天到来时...... 提醒应该触发吗?

A) 2019年7月27日10:30:00智利/圣地亚哥时区 = UTC时间2019年7月27日15:30:00 ?

还是

B) 2019年7月27日9:30:00智利/圣地亚哥时区 = UTC时间2019年7月27日14:30:00 ?

除非知道约翰在他告诉日历“请在2019年7月27日10:30:00 TZ=Chile/Santiago提醒我”时在概念上意味着什么,否则没有正确答案。

他是指“民用日期时间”(“当我所在城市的时钟显示为10:30时”)吗?如果是这样,那么A)就是正确答案。

还是他指的是“物理时间点”,我们宇宙时间的连续线上的一点,例如,“下一次日食发生时”。在这种情况下,答案B)是正确的。

一些日期/时间API正确地处理了这个区别:其中包括Jodatime,它是下一个(第三个!)Java DateTime API(JSR 310)的基础。


1
另一个需要考虑的问题是,即使给出了时间和时区,在API中也没有看到至少两个合理的含义,例如“13:00:00EDT”。它可能指已知发生在当地时间下午1点的某事,当时被认为是东部夏令时,或者指已知发生在东部时间达到13:00的时刻的某事,当时被认为是在遵守东部夏令时的地方发生。如果有许多时间戳,其中时区信息可能错误(例如数码相机文件)... - supercat
1
了解它们是否反映了准确的本地时间或全球时间可能很重要,以确定如何进行更正。 - supercat
6
约翰明显想要A)。因为无论当前是夏令时还是其他时区,人们通常在早上8:30去工作。如果进入夏令时,他们将在早上8:30上班;当退出夏令时时,他们仍将在早上8:30上班。如果国家决定改变时区,他们也将每天早上8:30上班。 - Gianluca Ghettini

64

清晰地划分关注点架构 - 确定哪个层与用户交互,并且必须将日期时间更改为/从规范表示(UTC)。非UTC日期时间是演示文稿(遵循用户本地时区),UTC时间是模型(对于后端和中间层保持唯一)。

此外,决定您的实际受众、您不必服务的内容以及拉出线条的位置。除非您确实在那里有重要客户,否则不要涉及奇异的日历,然后考虑为该地区单独提供面向用户的服务器。

如果您可以获取并维护用户的位置,请使用位置进行系统化的日期时间转换(例如.NET culture或SQL表),但为最终用户提供选择覆盖的方式,如果日期时间对您的用户至关重要。

如果有历史审计义务(例如告知何时Jo在2年前的9月支付了账单),则同时保留UTC和本地时间以备记录(您的转换表将随着时间而变化)。

定义数据的时间参考时区,如文件、Web服务等批量进货 - 例如东海岸公司在加利福尼亚州有数据中心 - 您需要询问并知道他们使用的标准,而不是假设其中之一。

不要相信嵌入日期时间文本表示中的时区偏移量,也不要接受解析和遵循它们。 相反,始终请求明确定义时区和/或参考区域。您可以轻松获得具有PST偏移量的时间,但实际上该时间是EST,因为这是客户端的参考时间,而记录只是在位于PST的服务器上导出的。


41
你需要了解奥尔森时区数据库,它可以从ftp://elsie.nci.nih.gov/pub http://iana.org/time-zones/获取。它每年更新多次,以应对世界各地不同国家在冬季和夏季(标准时间和夏令时)之间切换的时间以及是否切换等经常性的最后一分钟更改。在2009年,最后一个发布版本是2009s;在2010年,是2010n;在2011年,是2011n;在2012年5月底,发布版本是2012c。请注意,这里有一组代码来管理数据和实际的时区数据,分别存储在两个不同的档案中(tzcode20xxy.tar.gz 和 tzdata20xxy.tar.gz)。代码和数据都属于公共领域。
这是时区名称(如America/Los_Angeles和US/Pacific的同义词)的来源。
如果您需要追踪不同的时区,则需要使用Olson数据库。正如其他人建议的那样,您还需要以固定格式存储数据 - UTC通常是选择的格式 - 以及生成数据的时区记录。您可能想区分时间与UTC之间的偏移量和时区名称;这可能会在后面产生差异。此外,知道当前时间是2010-03-28T23:47:00-07:00(美国/太平洋)可能有助于您解释值2010-11-15T12:30 - 这个值很可能是指定为PST(太平洋标准时间)而不是PDT(太平洋夏令时)。

C标准库接口对这种情况并不是非常有用。


由于A D Olson即将退休,以及维护人员因版权侵权而被起诉(现已被驳回),Olson数据已经移动。时区数据库现在由IANA(互联网数字分配机构)管理,并且在首页上有一个指向'时区数据库'的链接。讨论邮件列表现在是tz@iana.org;公告列表为tz-announce@iana.org


12
Olson数据库包含历史数据,因此在处理夏令时方面特别有用。例如,它知道对应于2000年3月31日的UTC值在美国不处于夏令时,但对应于2008年3月31日的UTC值则处于夏令时。 - Josh Kelley
3
Unicode联盟维护着奥尔森时区ID和Windows时区ID之间的映射关系:http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/windows_tzid.html。 - Josh Kelley
Unicode Consortium的页面更新链接:http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/zone_tzid.html - Span
在哪里可以找到有关解析Olson文件的信息?我想将其编译成一个数据库表,但是不知道应该如何读取这些文件。 - shealtiel
@gidireich:主要信息包含在数据中,不易理解。主要文档在zic.8手册页(或zic.8.txt)中找到。您需要获取tzcode2011i.tar.gz文件,而不是或者同时也需要获取tzdata2011i.tar.gz文件才能获得这些信息。 - Jonathan Leffler

29

通常,在存储时间戳时包括本地时间偏移量(包括DST偏移量):如果您稍后想要在原始时区(和夏令时设置)中显示时间戳,则仅使用UTC是不够的。

请注意,时区偏移量并不总是整数小时(例如,印度标准时间为UTC+05:30)。

例如,合适的格式可以是一个元组(Unix时间,分钟偏移量)或ISO 8601


6
在显示方面,偏移量是完美的。如果您想进行更改,则仍然不足。您还必须知道时区,因为新值可能需要不同的偏移量。 - Matt Johnson-Pint

24
穿越“计算机时间”和“人类时间”的边界是一场噩梦。主要问题在于,没有任何关于时区和夏令时规则的标准。各国可以随时更改自己的时区和夏令时规则。
有些国家(如以色列、巴西)每年决定何时开始实行夏令时,所以无法提前知道夏令时是否会生效。其他国家有固定的或近似固定的夏令时规则,还有一些国家根本不使用夏令时。
时区与GMT的差异不一定是整数小时。尼泊尔是+5.45,甚至有+13的时区。
SUN 23:00 in Howland Island (-12)
MON 11:00 GMT 
TUE 00:00 in Tonga (+13)

三个时间点都是一样的时间,但是属于3个不同的日期!

时区缩写也没有一个明确的标准,以及在夏令时时如何调整,因此你可能会遇到像这样的情况:

AST Arab Standard Time     UTC+03
AST Arabian Standard Time  UTC+04
AST Arabic Standard Time   UTC+03

最好的建议是尽可能远离本地时间,而是坚持使用UTC。只在最后一刻转换为本地时间。

在测试时,请确保测试西半球和东半球的国家,无论是否使用夏令时,以及不使用夏令时的国家(共6个)。


关于以色列 - 这是半真的。虽然夏令时更改的时间不是儒略历中的特定日期 - 但基于希伯来日历有一个规则(2014年进行了更改)。无论如何,总体想法是正确的 - 这些事情可能会偶尔发生变化。 - Yaron U.
1
此外,AST代表大西洋标准时间。至于“最好的建议”,它可以作为一个起点,但你需要阅读本页面的其余部分,并祷告一下,这样你可能会有一段时间做得对。 - DavidWalley

23

针对PHP:

PHP > 5.2 版本中的 DateTimeZone 类已经基于 Olson 数据库,这一点也被其他人提到过,因此如果您正在 PHP 中进行时区转换而不是在数据库中进行,那么您可以免去使用难以理解的 Olson 文件。

但是,PHP 的更新频率不如 Olson 数据库高,因此仅使用 PHP 的时区转换可能会让您的 DST(夏令时)信息过时,并影响数据的正确性。虽然这种情况不会经常发生,但如果您有全球大量用户,则可能发生,并且一定会发生。

为了解决上述问题,请使用 timezonedb pecl 包。它的功能是更新 PHP 的时区数据。尽可能经常地安装该包以保持最新。 (我不确定此包的更新是否完全遵循 Olson 的更新,但它似乎更新频率至少非常接近 Olson 更新的频率。)


20
如果你的设计可以容纳,尽量避免本地时间转换! 我知道对于一些人来说这听起来很疯狂,但考虑用户体验:用户在一瞥之间处理近似、相对日期(今天、昨天、下周一),比绝对日期(2010.09.17、星期五9月17日)更快。而且当你更深入地思考时,时区(和夏令时)的准确性越接近now()日期就越重要,因此如果你可以用相对格式表示日期/时间,对于+/- 1或2周的日期,其余日期可以是UTC,对95%的用户来说并不会太重要。
这样,你可以将所有日期存储为UTC,并在UTC中进行相对比较,只需在超出相对日期阈值时向用户显示UTC日期。
这也适用于用户输入(但通常受到更严格的限制)。从下拉列表中选择{昨天、今天、明天、下周一、下周四}比使用日期选择器更简单、更容易。日期选择器是最令人痛苦的表单填写组件之一。当然,这种方法并不适用于所有情况,但你可以看到,只需要一点巧妙的设计就可以使它非常强大。

16

虽然我没有尝试过,但我认为一个有吸引力的时区调整方法应该是:

  1. 将所有内容存储在UTC中。

  2. 创建一个名为TZOffsets的表,其中包含三列:RegionClassId、StartDateTime和OffsetMinutes(int,以分钟为单位)。

在表中,存储一份日期和时间列表,列表中包括本地时间何时以及更改了多少。表中的区域数量和日期数量取决于您需要支持的日期范围和世界各地的区域。尽管这些日期应该包括未来某个实际限制的日期,但可以将其视为“历史”日期。

当您需要计算任何UTC时间的本地时间时,只需执行以下操作:

SELECT DATEADD('m', SUM(OffsetMinutes), @inputdatetime) AS LocalDateTime
FROM   TZOffsets
WHERE  StartDateTime <= @inputdatetime
       AND RegionClassId = @RegionClassId;

你可能想在应用程序中缓存此表,并使用LINQ或类似的方法进行查询,而不是直接访问数据库。
这些数据可以从公共领域tz数据库中提取。
这种方法的优点和注释:
  1. 代码中没有预设规则,你可以轻松调整新区域或日期范围的偏移量。
  2. 您无需支持每个日期范围或区域,可根据需要添加它们。
  3. 区域不必直接对应地缘政治边界,并且为了避免重复行(例如,美国大多数州以相同方式处理夏令时),您可以有广泛的RegionClass条目,在另一个表中链接更传统的州,国家等列表。
  4. 对于像美国这样在过去几年中夏令时的开始和结束日期已更改的情况,这很容易处理。
  5. 由于StartDateTime字段还可以存储时间,因此标准的2:00 AM切换时间可以轻松处理。
  6. 并非世界上所有地方都使用一小时的夏令时。这些情况很容易处理。
  7. 数据表是跨平台的,可以是一个单独的开源项目,可供使用几乎任何数据库平台或编程语言的开发人员使用。
  8. 这可以用于与时区无关的偏移量。例如,不时进行的1秒调整以调整地球的旋转,历史上对格里高利日历的调整和调整等。
  9. 由于这是在数据库表中,标准报告查询等可以利用数据而无需通过业务逻辑代码。
  10. 如果您希望,这也可以处理时区偏移,并且甚至可以考虑到特殊的历史情况,其中一个区域被分配给另一个时区。您只需要一个将时区偏移量分配给具有最小开始日期的每个区域的初始日期。这将需要为每个时区创建至少一个区域,但将允许您提出有趣的问题,例如:“亚利桑那州尤马和华盛顿州西雅图于1989年2月2日上午5:00之间的当地时间差是多少?”(只需从一个SUM()中减去另一个)。
现在,这种方法或任何其他方法的唯一缺点是,从本地时间转换为GMT的转换不完美,因为具有负偏移量的任何DST更改都会重复给定的本地时间。恐怕没有简单的方法来处理这个问题,这也是首先存储本地时间的坏消息之一。

8
你提出的数据库想法已经作为Olson数据库实现。虽然夏令时会导致时间重叠,但指定夏令时时区可以解决这个问题。例如,2:15 EST和2:15 EDT是不同的时间。正如其他帖子中所指出的,指定偏移量也可以解决歧义。 - BillThor
5
自行管理数据库的一个大问题是难以保持它的更新。Olson和Windows的维护是由他们来负责的。因为表格内容过时,所以我曾多次遇到夏令时转换出现问题的情况。完全删除它们,依赖框架或操作系统级别的数据库更加可靠。 - Matt Johnson-Pint
5
不要创建自己的数据库:始终依赖于系统的API! - Alex

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接