夏令时和时区最佳实践


2046

我希望将这个问题及其答案作为处理夏时制(尤其是处理实际变更)的权威指南。

如果您要添加任何内容,请执行

许多系统都依赖于保持准确的时间,问题在于夏时制导致的时间变化-向前或向后移动时钟。

例如,一个在接订单系统中有取决于订单时间的业务规则-如果时钟改变,则规则可能不太清楚。订购时间应如何保留?当然,有无数种情况-这只是一种说明。

  • 您如何处理夏令时问题?
  • 解决方案中包含哪些假设?(在此处查找上下文)

同样重要的是,如果不是更重要的话:

  • 您尝试了什么却不起作用?
  • 为什么不起作用?

我会对编程,操作系统,数据持久性以及该问题的其他相关方面感兴趣。

通用答案很好,但是我也想查看详细信息,特别是如果它们仅在一个平台上可用。


2
@abatishchev-这种GETDATE()在SQL上的方式将是UTC(以及DateTime.Now)。而且,服务器不会受到任何自动DST更改的影响。
奥德

4
@Oded:我可以同意是否添加“在服务器上”。但这仍然会影响需要本地时间的其他应用程序。由于这个原因和其他原因,我认为最好明确请求Utc时间。
abatishchev 2012年

7
UTC比GMT更受青睐,这是因为它的定义更加精确,也因为GMT定义在某些操作系统上很混乱。人们通常将术语“ GMT”和“ UTC”视为可互换,但它们并不完全互换。对于几乎所有软件/系统,请使用UTC。参见stackoverflow.com/questions/2292334/…–
克里斯·约翰逊

7
@JoshStodola-乔恩·斯基特(Jon Skeet)的' 答案 '。
肯尼·埃维特

1
@Oded,您不能假定服务器将位于UTC上,我已经看到生产服务器的时区为“ UTC”,但已应用DST,因此实际上已使用UTC + 1超过半年了。与@abatishchev同意是明确和使用DateTime.UtcNowGETUTCDATE(),这也说明其他开发者,你实际上已经想过这个问题
ono2012

Answers:


940

答案和其他数据摘要:(请添加您的答案)

做:

  • 每当您指的是确切的时间时,请按照不受夏令时影响的统一标准坚持时间。(GMT和UTC在这方面是等效的,但最好使用术语UTC。请注意,UTC也称为ZuluZ time。)
  • 如果相反,您选择使用本地时间值来保留时间,则包括该特定时间与UTC的本地时间偏移量(此偏移量可能会在全年中发生变化),以便以后可以明确地解释时间戳。
  • 在某些情况下,你可能需要存储两个 UTC时间和等效本地时间。通常,这是通过两个单独的字段完成的,但是某些平台支持datetimeoffset可以将两个字段都存储在一个字段中的类型。
  • 将时间戳记存储为数值时,请使用Unix时间 -这是自此以来的总秒数1970-01-01T00:00:00Z(不包括leap秒)。如果需要更高的精度,请改用毫秒。此值应始终基于UTC,而无需进行任何时区调整。
  • 如果以后可能需要修改时间戳,请包括原始时区ID,以便可以确定偏移量是否可能与记录的原始值有所变化。
  • 在安排未来事件时,通常首选本地时间而不是UTC,因为偏移通常会发生变化。 请参阅答案博客文章
  • 当存储整个日期(例如生日和周年纪念日)时,请勿转换为UTC或任何其他时区。
    • 如果可能,以不包括一天中的时间的仅日期数据类型存储。
    • 如果没有这样的类型,则在解释该值时请确保始终忽略时间。如果您不能确定一天中的时间会被忽略,请选择中午12:00,而不是午夜00:00,作为该天更安全的代表时间。
  • 请记住,时区偏移量并不总是整数小时(例如,印度标准时间为UTC + 05:30,而尼泊尔使用UTC + 05:45)。
  • 如果使用Java,则将Java.time用于Java 8及更高版本。
  • 如果使用.NET,请考虑使用Noda Time
  • 如果使用不带Noda Time的.NET,DateTimeOffset则通常认为它是比更好的选择DateTime
  • 如果使用Perl,请使用DateTime
  • 如果使用Python,请使用pytzdateutil
  • 如果使用JavaScript,请使用带有moment-timezone扩展名的moment.js
  • 如果使用PHP> 5.2,请使用DateTimeDateTimeZone类提供的本地时区转换。使用时请小心DateTimeZone::listAbbreviations()- 请参阅答案。为了使PHP具有最新的Olson数据,请定期安装timezonedb PECL软件包;看答案
  • 如果使用C ++,请确保使用使用正确实现IANA时区数据库的库。这些包括cctzICUHoward Hinnant的“ tz”库
    • 不要将Boost用于时区转换。尽管其API声称支持标准IANA(又名“ zoneinfo”)标识符,但它粗略地将它们映射到POSIX样式的数据,而无需考虑每个区域可能具有的丰富变更历史。(此外,该文件已退出维护。)
  • 如果使用Rust,请使用chrono
  • 大多数业务规则使用民事时间,而不是UTC或GMT。因此,在应用逻辑之前,计划将UTC时间戳转换为本地时区。
  • 请记住,时区和偏移量不是固定的,可能会发生变化。例如,从历史上看,美国和英国使用相同的日期来“向前跳”和“向后退”。但是,在2007年,美国更改了更改时钟的日期。现在,这意味着一年中的48周,伦敦时间和纽约时间之间的时差为5小时,而4周(春季为3个,秋季为1个)为4小时。在涉及多个区域的任何计算中,请注意此类项目。
  • 考虑时间类型(实际事件时间,广播时间,相对时间,历史时间,重复发生的时间),您需要存储哪些元素(时间戳,时区偏移量和时区名称)以进行正确检索-请参阅“时间类型” 这个答案
  • 在您自己的OS,数据库和应用程序tzdata文件之间以及与世界其他地方之间保持同步。
  • 在服务器上,将硬件时钟和操作系统时钟设置为UTC而不是本地时区。
  • 不管前面的项目点,服务器端代码,包括网站,应该从来没有想到的服务器的本地时区是什么特别。 看答案
  • 最好在您的应用程序代码中视情况使用时区,而不是通过配置文件设置或默认值进行全局时区处理。
  • 在所有服务器上使用NTP服务。
  • 如果使用FAT32,请记住时间戳记存储在本地时间,而不是UTC。
  • 在处理重复事件(例如每周电视节目)时,请记住,时间随着夏令时而改变,并且在各个时区会有所不同。
  • 总是查询日期时间值作为下限以下,上界独占>=<)。

别:

  • 请勿将“时区” America/New_York与“时区偏移”(如)混淆-05:00。他们是两个不同的东西。请参阅时区标签Wiki
  • 请勿使用JavaScript的Date对象在较旧的Web浏览器中执行日期和时间计算,因为ECMAScript 5.1及更低版本具有设计缺陷,可能会错误地使用夏时制。(这已在ECMAScript 6/2015中修复)。
  • 永远不要相信客户的时钟。这很可能是不正确的。
  • 不要告诉人们“总是在任何地方使用UTC”。这种广泛的建议是本文档前面描述的几种有效方案的短视。而是对要使用的数据使用适当的时间参考。(时间戳记可以使用UTC,但将来的时间安排和仅日期值不应使用。)

测试:

  • 测试时,请确保您测试在西部,东部,北部和国家南方(在地球的每个季度其实,这样4个区域)半球,而期间双方DST和不(给8),并且做了国家不要使用DST(另外4个覆盖所有区域,总共12个)。
  • 测试DST的转换,即当您当前处于夏季时间时,请从冬季中选择一个时间值。
  • 使用DST测试边界情况,例如时区为UTC + 12,将夏令时设为本地时间UTC + 13,冬季将夏令时设为UTC + 13
  • 测试所有第三方库和应用程序,并确保它们正确处理时区数据。
  • 至少测试半小时时区。

参考:

其他:

  • 游说您的代表以终止DST的可憎之处。我们总是希望...
  • 地球标准时间大厅

31
使用GMT并没有真正解决问题,你仍然需要弄清楚什么是应用正确的增量,而这正是所有的复杂性所在。在不同地区的用户使用的系统(具有不同的夏令时时间表)中或在显示历史/未来数据的系统中,确定增量可能很复杂。
ckarras 2010年

10
如果应用程序需要做的只是显示当前本地时间,那么这是正确的。但是,例如,如果我们在澳大利亚有一台服务器需要显示2006年4月2日在加拿大进行交易的日期/时间(这是加拿大更改夏令时规则的最后一年),您是否有OS呼叫可以做DateTime(“ 2006/04/02 14:35Z”)。ToLocalTime()。WithDaylightSavingRules(“ Canada”,2006)吗?
ckarras 2010年

22
是的,Windows中有这样的OS调用-SystemTimeToTzSpecificLocation。msdn.microsoft.com/zh-CN/library/ms724949(VS.85).aspx
Michael Madsen 2010年

7
@tchrist如果您坚持不懈,也许可以。
彼得·阿杰泰

16
请记住,将将来的日期(甚至只是明天)转换为UTC,您总是会丢失一些东西。例如,您使用一些已知的偏移量进行了转换,但是由于日期是将来的日期,因此设置这些规则的组始终有机会更改这些规则。而且,几乎不可能将某些将来的日期转换为UTC,因为夏令时直到该年才知道(某些国家/地区每年设置日期,而不是提前10年以上,或者基于任何设置的规则)。
eselk

319

我不确定我可以在上面的答案中添加什么,但是我有几点建议:

时间类型

您应该考虑四个不同的时间:

  1. 活动时间:例如,国际体育赛事发生的时间或加冕/死亡/等时间。这取决于事件的时区,而不取决于查看器。
  2. 电视时间:例如,某个特定的电视节目在世界各地的当地时间晚上9点播出。考虑在您的网站上发布结果(例如“美国偶像”)时很重要
  3. 相对时间:例如:此问题在21小时内有悬空赏金。这很容易显示
  4. 循环播放时间:例如:即使DST更改,电视节目也每周星期一晚上9点播放。

也有历史/备用时间。这些很烦人,因为它们可能无法映射回标准时间。例如:朱利安(Julian)日期,根据土星(Klingon)日历上的阴历确定日期。

在UTC中存储开始/结束时间戳记效果很好。对于1,您需要一个事件时区名称+与事件一起存储的偏移量。对于2,您需要在每个区域存储一个本地时间标识符,并为每个查看者存储一个本地时区名称+偏移量(如果您处于紧缩状态,可以从IP导出该时间)。对于3,以UTC秒存储,不需要时区。4是1还是2的特殊情况,具体取决于它是全局事件还是本地事件,但是您还需要存储一个时间戳记创建的事件,以便您可以知道在创建此事件之前或之后是否更改了时区定义。如果需要显示历史数据,这是必需的。

储存时间

  • 始终将时间存储在UTC中
  • 转换为显示的本地时间(本地由用户查看数据定义)
  • 存储时区时,您需要名称,时间戳和偏移量。这是必需的,因为政府有时会更改其时区的含义(例如:美国政府更改了夏令时的日期),并且您的应用程序需要妥善处理……例如:当DOST规则前后都显示LOST情节的确切时间戳改变了。

偏移量和名称

以上是一个示例:

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

有了这些信息,即使南非时区定义发生了变化,我们也可以从历史上确定2010年WCS决赛的确切时间,能够在观众查询数据库时向本地时区的观众显示该时间。

系统时间

您还需要使您的OS,数据库和应用程序tzdata文件彼此之间以及与世界其他地方保持同步,并在升级时进行广泛的测试。您依赖的第三方应用未正确处理TZ更改并非闻所未闻。

确保硬件时钟设置为UTC,并且如果您在世界各地运行服务器,请确保其操作系统也配置为使用UTC。当您需要从多个时区的服务器复制每小时轮换的apache日志文件时,这变得显而易见。仅当所有文件都使用相同的时区命名时,才按文件名对它们进行排序。这也意味着,当您从一个框切换到另一个框并需要比较时间戳时,您不必费心地进行日期数学运算。

另外,在所有机器上运行ntpd。

客户群

永远不要相信从客户端计算机获得的时间戳是有效的。例如,日期:HTTP标头或javascript Date.getTime()调用。当用作不透明标识符时,或者在同一客户端上的单个会话中进行日期数学运算时,这些方法都很好,但是请勿尝试将这些值与服务器上的内容交叉引用。您的客户端不运行NTP,并且不一定为他们的BIOS时钟配备可用的电池。

琐事

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

从1909-05-01到1937-06-30,根据法律,荷兰的标准时间恰好比UTC提前19分32.13秒。无法使用HH:MM格式准确表示该时区。

好吧,我想我完成了。


6
这个答案很有意思,我特别想指出这一部分“重复时间:例如:即使DST更改,每个星期一的晚上9点都是电视节目”将时间存储在数据库中的UTC中,然后转换为显示方式有助于处理处理时区的许多棘手方面。但是,处理跨越DST障碍的“重复发生的事件”变得更加困难。
贾里德·海尔斯

45
琐事+1。保持内容有趣会使这项任务变得更加有趣,这可能会提高生产率:)
Chris

4
这个答案有很多好东西,但有几个问题。1)许多时区缩写是模棱两可的。请参阅此处 2)并非总是要存储在UTC中。大多数时候-是的,但是上下文很重要。用您的术语来说,电视时间不会存储在UTC中。另外,有时它不合法或违反政策以不存储本地时间-这是诸如此类DateTimeOffset(或等价物)派上用场的地方。否则,请写好。
马特·约翰逊

1
因此,每个人都同意Joda库是迄今为止最适合该工作的工具。但是,我们需要根据本文(joda.org/joda-time/tz_update.html)不断更新(重建)JODA jar文件,以便了解最新的时区信息。有什么标准的方法可以从IANA网站(iana.org/time-zones)下载最新的时区数据并重建joda库?换句话说,可以自动执行此过程,而不是手动执行吗?
saravana_pc 2013年

2
荷兰琐事看起来很合法。ietf.org/rfc/rfc3339.txt第5.8节。中途发现有人在拉我们的腿。
鲁芬2014年

89

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

从概念的角度来看,一种通常处理两种类型的时间日期数据,并且将它们区分开是很方便的(上述标准没有):“ 物理时间 ”和“ 民事时间 ”。

时间的“物理”时刻是物理处理的连续通用时间线中的一个点(当然,忽略相对论)。例如,此概念可以在UTC中进行适当的编码持久化(如果您可以忽略leap秒)。

“民事”时间是遵循民事规范的日期时间规范:此处的时间点由一组日期时间字段(Y,M,D,H,MM,S,FS)加上TZ(时区规范)完全指定(实际上,这也是一个“日历”;但假设我们将讨论范围限制为公历)。时区和日历共同(原则上)允许从一种表示映射到另一种表示。但是民用和物理时刻在根本上是不同的量值类型,应该在概念上将它们分开并进行不同的处理(比喻:字节和字符串数组)。

这个问题令人困惑,因为我们可以互换地谈论这些类型的事件,并且因为平民时代可能会发生政治变化。对于将来的事件,问题(以及区分这些概念的需求)变得更加明显。示例(摘自我在这里的讨论。

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

现在,当一天到来时...该提醒是否应在

A)2019-Jul-27 10:30:00 Chile/Santiago = UTC time 2019-Jul-27 15:30:00

要么

B)2019-Jul-27 9:30:00 Chile/Santiago = UTC time 2019-Jul-27 14:30:00

没有正确的答案,除非有人知道约翰在告诉日历“请给我打电话”时的概念2019-Jul-27, 10:30:00 TZ=Chile/Santiago

他的意思是“民用日期时间”(“我城市的时钟说10:30时”)吗?在这种情况下,A)是正确的答案。

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

一些Date / Time API可以正确地实现这种区分:其中有Jodatime,它是下一个(第三个!)Java DateTime API(JSR 310)的基础。


1
需要考虑的另一个要点是,即使给定了时间和时区,“ API 13:00:00EDT”也至少有两个合理的含义。可以指的是已知发生在当地时间下午1点的某件事,这被认为是东部夏令时,或者是发生在东部时间13:00发生的那件事,这被认为是发生在遵守东部夏令时的地方。如果一个人有很多时区信息可能不正确的时间戳(例如数码相机文件)...
supercat

1
...了解它们是否反映了准确的本地时间或全球时间对于确定如何更正它们可能很重要。
2013年

3
显然,约翰想要A),因为无论现在的夏令时或时区如何,人们都在8:30上班。如果他们进入DST,他们将在8:30上班,当他们从DST离开时,他们将在8:30上班。如果该国决定搬到另一个时区,他们将在每天晚上8:30上班
Gianluca Ghettini

59

明确关注点的架构分离-要确切知道与用户交互的层,并且必须更改规范表示(UTC)的日期/时间。非UTC日期时间是表示形式(遵循用户本地时区),UTC时间是模型(后端和中间层保持唯一)。

另外,请确定您的实际受众是什么,您不必为什么服务以及在哪里划界。除非您在那里确实有重要的客户,然后再考虑仅用于该地区的面向用户的服务器,否则请勿触摸异国日历。

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

如果涉及历史审计义务(例如准确说出AZ的Jo在2年前于9月支付帐单的时间),则同时保留UTC和本地时间作为记录(您的转换表将随时间变化)。

为大量数据定义时间参考时区(例如文件,Web服务等)。说东海岸公司在CA设有数据中心-您需要询问并知道它们用作标准的内容,而不是假设一个或另一个。

不要相信嵌入在日期时间文本表示形式中的时区偏移量,也不要接受解析和跟踪它们。而是始终要求必须明确定义时区和/或参考区。您可以轻松接收带有PST偏移量的时间,但是该时间实际上是EST,因为这是客户端的参考时间,并且记录只是在PST中的服务器上导出的。


40

您需要了解Olson tz数据库,该数据库可从ftp://elsie.nci.nih.gov/pub http://iana.org/time-zones/获得。每年对它进行多次更新,以应对世界上不同国家冬季和夏季(标准时间和夏时制)时间之间(以及是否)切换时间的最后一刻变化。在2009年,最新版本是2009年代;在2010年是2010年;在2011年是2011年;在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库接口对于此类工作不是很有帮助。


奥尔森(Olson)的数据有所变化,部分原因是公元奥尔森(AD Olson)即将退休,部分原因是针对维护者的版权侵权提起了诉讼(现已撤销)。现在,时区数据库由IANA(互联网号码分配机构)负责管理,并且首页上有一个指向“时区数据库”的链接。现在讨论邮件列表tz@iana.org;公告清单是tz-announce@iana.org


12
Olson数据库还包含历史数据,这使其在处理DST时特别有用。例如,它知道在美国对应于2000年3月31日的UTC值不在DST中,而在美国对应于2008年3月31日的UTC值在DST中。
乔什·凯利

3
Unicode协会维护奥尔森大部头区ID和Windows时区ID之间的映射:unicode.org/repos/cldr-tmp/trunk/diff/supplemental/...
乔许·凯利

Unicode联合会页面的更新链接:unicode.org/repos/cldr-tmp/trunk/diff/supplemental/…– 2011
跨度

在哪里可以找到有关解析Olson文件的信息?我想将其编译到db表中,但无法弄清楚应如何读取文件
shealtiel 2011年

@gidireich:主要信息随数据一起找到,不容易理解。它的主要文档在zic.8手册页(或zic.8.txt)中。您需要tzcode2011i.tar.gz文件而不是文件,也需要tzdata2011i.tar.gz文件来获取此信息。
乔纳森·莱夫勒

26

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

请记住,偏移量并非始终是整数小时(例如,印度标准时间为UTC + 05:30)。

例如,合适的格式是元组(unix时间,以分钟为单位的偏移量)或ISO 8601


5
对于显示,偏移量是完美的。如果要进行更改,偏移量仍然不够。您还必须知道时区,因为新值可能需要不同的偏移量。
马特·约翰逊·品脱

23

跨越“计算机时间”和“人员时间”的界限是一场噩梦。主要的问题是,关于时区和夏令时的规则没有任何标准。各国可以随时自由更改其时区和夏令时规则。

某些国家(例如以色列,巴西)每年决定何时设置夏令时,因此无法预先知道夏令时的生效时间。其他人对DST生效时间有固定的(ish)规则。其他国家并没有全部使用DST。

时区与格林尼治标准时间不必是整小时的差异。尼泊尔为+5.45。甚至有+13时区。这意味着:

SUN 23:00 in Howland Island (-12)
MON 11:00 GMT 
TUE 00:00 in Tonga (+13)

都是同一时间,但3天不同!

时区的缩写以及在DST中时的变化也没有明确的标准,因此您最终会遇到以下情况:

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

最好的建议是尽可能远离当地时间,并坚持使用UTC。仅在最后可能的时间转换为当地时间。

在进行测试时,请确保您测试的是西半球和东半球的国家/地区,既有正在进行的DST,也没有进行的DST,以及没有使用DST的国家/地区(总共6个)。


关于以色列-这是正确的一半。尽管DST更改的时间不是儒略历中的特定日期-但有一个基于希伯来历的规则(2014年有所更改)。无论如何,总的想法是正确的-这件事可能并且确实会偶尔改变
Yaron U. 2014年

1
同样,AST =大西洋标准时间。至于“最佳建议”,这是一个好的起点,但是您需要阅读本页面的其余部分,并稍作祈祷,您可能会花一会儿时间。
DavidWalley

20

对于PHP:

PHP> 5.2中的DateTimeZone类已经基于其他人提到的Olson数据库,因此,如果您在PHP中而不是在DB中进行时区转换,则无需使用(难以理解的)Olson文件。

但是,PHP的更新频率不如Olson DB那样频繁,因此仅使用PHP的时区转换可能会给您带来过时的DST信息,并影响数据的正确性。尽管这种情况不会经常发生,但可能会发生,并且如果您在全球拥有大量用户,则可能发生。

要解决上述问题,请使用timezonedb pecl包。它的功能是更新PHP的时区数据。请在更新后频繁安装此软件包。(我不确定此软件包的更新是否完全跟在Olson更新之后,但是似乎更新的频率至少与Olson更新的频率非常接近。)


18

如果您的设计可以容纳它,请避免一起进行本地时间转换!

我知道有些听上去很疯狂,但考虑到UX:用户一眼就能看到,相对日期(今天,昨天,下周一)的处理时间比绝对日期(2010.09.17,9月17日,星期五)要快。而且,当您考虑得更多时,越接近日期,时区(和DST)的准确性就越重要now(),因此,如果您可以用+/- 1或2周的相对格式表示日期/日期时间,则其余时间日期可以是世界标准时间,对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. 区域不必直接对应于地缘政治边界,并且为了避免重复行(例如,美国的大多数州以相同的方式处理DST),您可以具有广泛的RegionClass条目,这些条目在另一个表中链接到更传统的列表州,国家等
  4. 对于美国这样的情况,在过去几年中,夏令时的开始和结束日期已发生变化,这很容易处理。
  5. 由于StartDateTime字段也可以存储时间,因此可以轻松处理2:00 AM标准转换时间。
  6. 世界上并非每个地方都使用1小时DST。这样可以轻松处理这些情况。
  7. 数据表是跨平台的,可以是一个单独的开源项目,几乎使用任何数据库平台或编程语言的开发人员都可以使用。
  8. 这可用于与时区无关的偏移量。例如,不时进行1秒调整以适应地球的自转,对公历及其内部的历史调整等。
  9. 由于这是在数据库表中,因此标准报表查询等可以利用数据,而无需花费业务逻辑代码。
  10. 如果需要,它也可以处理时区偏移,甚至可以考虑将区域分配给另一个时区的特殊历史情况。您所需要的只是一个初始日期,该日期以最小的开始日期为每个区域分配时区偏移量。这将需要为每个时区创建至少一个区域,但是您可以提出一些有趣的问题,例如:“ 1989年2月2日凌晨5:00,亚利桑那州尤马市和华盛顿州西雅图市之间的当地时间有何不同?” (只需从另一个中减去一个SUM())。

现在,这种方法或其他方法的唯一缺点是,本地时间到GMT的转换并不完美,因为任何对时钟有负偏移的DST更改都会重复给定的本地时间。恐怕没有简单的方法可以解决这个问题,这是首先存储本地时间是坏消息的原因。


8
您的数据库构想已经实现为Olson数据库。虽然夏令时确实会造成重叠,但指定夏令时可以帮助解决该问题。美国东部时间2:15和美国东部时间2:15是不同的时间。如其他文章所述,指定偏移量也可以解决歧义。
BillThor

5
保留自己的数据库的最大问题是保持数据库更新。Olson和Windows为您维护。由于表已经过时,因此我有不止一种情况导致DST转换错误。完全删除它们并依靠框架或OS级别的数据库要可靠得多。
马特·约翰逊

4
不要创建自己的数据库:始终依靠系统的API!
亚历克斯

15

最近,我在一个Web应用程序中遇到了一个问题,该问题在Ajax上回发到服务器端代码的日期时间与提供的日期时间不同。

这很可能与我在客户端上的JavaScript代码有关,该代码确定了以字符串形式发布回客户端的日期,因为JavaScript会针对时区和夏时制进行调整,并且在某些浏览器中会计算何时应用夏时制似乎与其他人有所不同。

最后,我选择完全删除客户端上的日期和时间计算,并使用整数键将其发布回服务器,然后将其转换为服务器上的日期时间,以实现一致的转换。

我的经验教训:除非绝对必要,否则请不要在Web应用程序中使用JavaScript日期和时间计算。


Javascript在客户端计算机上运行,​​而不是在服务器计算机上运行。Javascript的基本日期时间是客户端的日期时间,而不是服务器的日期时间。
BalusC,2010年

嗨BalusC。我理解了这一点(从我的原始文章:“建立日期的客户端上的Java代码脚本...”)。我的问题是某些客户端计算机以一种方式处理DST,而另一些以另一种方式处理。因此,为了避免浏览器怪异,我将所有处理过程移到了服务器端
Joon 2010年

@Joon-javascript实现中有一些您无法检测或允许的错误。因此,是的,永远不要将客户端ECMAScript日期计算用于重要的事情(尽管除此之外,您可以做得很好)。
RobG 2014年

14

我在两种类型的系统上都做到了这一点:“轮班计划系统(例如工厂工人)”和“天然气依赖管理系统”…

每天23到25个小时的工作很痛苦,所以8个小时的轮班需要7个小时或9个小时。问题是您会发现每个客户,甚至客户部门对于他们在这些特殊情况下的操作都创建了不同的规则(通常没有文档记录)。

最好在客户购买“现成”软件后才问客户一些问题。很少有客户在购买软件时就事先考虑过此类问题。

我认为在所有情况下,您都应该以UTC格式记录时间,并在存储日期/时间之前将其转换为本地时间。但是,即使使用夏令时和时区,也很难知道给定时间在哪。


6
我的女友(一名护士)在晚上上班,他们必须在DST切换期间写下时区。例如,CST 2AM vs CDT 2AM
Joe Phillips 2010年

1
是的,这很常见:计算每日(民用)平均时间时,您必须应对23、24或25小时的工作日。结果,当您计算增加1天时,就不会增加24小时!其次,不要尝试构建自己的时区代码;仅使用系统API。而且,不要忘了更新每个已部署的系统以获取最新的时区数据库
亚历克斯

12

对于网络而言,规则并不那么复杂...

  • 服务器端,使用UTC
  • 客户端,使用Olson
    • 原因:UTC偏移量不是夏令时安全的(例如,纽约是一年中的EST(UTC-5小时),一年中其余时间是EDT(UTC-4小时))。
    • 对于客户端时区确定,您有两个选择:

剩下的只是使用服务器端日期时间库的UTC /本地转换。好去...


2
这是一个很好的建议,但是它完全取决于应用程序。这对于友好的网站可能很好,但是如果您的应用程序存在任何法律/司法/财务方面的问题,有人可能会起诉(我已经处理了好几个),那么这过于简单了,因为存在不同类型的“时间”,如本页其他位置所述。
DavidWalley'17年

12

对于在服务器上运行的应用程序(包括网站和其他后端服务),应用程序应忽略服务器的时区设置。

常见建议是将服务器的时区设置为UTC。这确实是一个很好的最佳实践,但是它可以作为不遵循其他最佳实践的应用程序的创可贴。例如,服务可能正在写入具有本地时间戳而不是基于UTC的时间戳的日志文件,从而在夏时制回退过渡期间造成歧义。将服务器的时区设置为UTC将修复应用程序。但是,真正的解决方法是使应用程序首先使用UTC进行记录。

服务器端代码(包括网站)绝对不能期望服务器的本地时区特别重要。

在某些语言中,本地时区可以轻松地爬到应用程序代码中。例如,DateTime.ToUniversalTime.NET中的方法将从本地时区转换为UTC,并且该DateTime.Now属性返回本地时区的当前时间。另外,DateJavaScript中的构造函数使用计算机的本地时区。还有很多其他这样的例子。重要的是练习防御性编程,避免使用计算机本地时区设置的任何代码。

使用本地时区保留客户端代码,例如桌面应用程序,移动应用程序和客户端JavaScript。


11

将您的服务器设置为UTC,并确保将它们都配置为ntp或同等功能。

UTC避免了夏令时问题,并且不同步的服务器可能导致无法预测的结果,需要一段时间才能进行诊断。


9

处理存储在FAT32文件系统中的时间戳时要小心-它始终保存在本地时间坐标中(包括DST-请参见msdn article)。被烧死了。


好点。NTFS没有这个问题,但是有些人仍然使用FAT32-尤其是在USB密钥上。
马特·约翰逊·品脱

7

另一件事,请确保服务器已应用了最新的夏令时补丁。

去年,我们遇到了一种情况,即使我们使用的是基于UTC的系统,对于北美用户,我们的时间在三个星期的时间内始终保持一小时。

最终结果是服务器。他们只需要应用最新的补丁程序(Windows Server 2003)。


6

PHP的DateTimeZone::listAbbreviations()输出

此PHP方法返回一个包含一些“主要”时区(例如CEST)的关联数组,这些时区本身包含更特定的“地理”时区(例如欧洲/阿姆斯特丹)。

如果您正在使用这些时区及其偏移量/ DST信息,那么实现以下内容非常重要:

似乎每个时区的所有不同偏移量/ DST配置(包括历史配置)都包括在内!

例如,在此函数的输出中可以找到六次欧洲/阿姆斯特丹。直到1937年为止的阿姆斯特丹时间都有两次出现(偏移1172/4772);其中两个(1200/4800)用于1937年至1940年之间的时间;和两个(3600/4800)是自1940年以来使用的时间。

因此,您不能依赖此函数当前正确/正在使用的偏移/ DST信息

如果您想知道某个时区的当前偏移/ DST,则必须执行以下操作:

<?php
$now = new DateTime(null, new DateTimeZone('Europe/Amsterdam'));
echo $now->getOffset();
?>

5

如果您碰巧要维护正在运行DST的数据库系统,请仔细检查在秋季过渡期间是否需要关闭它们。Mandy DBS(或其他系统)不喜欢两次在(本地)时间传递同一点,这恰恰是您在秋天倒转时钟时会发生的情况。SAP通过(IMHO真正整洁)的解决方法解决了这一问题-他们没有让时钟倒转,而是让内部时钟以正常速度的一半运行了两个小时...


4

您是否正在使用.NET框架?如果是这样,让我介绍一下.NET 3.5中添加的DateTimeOffset类型。

该结构同时包含a DateTime和Offset(TimeSpan),后者指定DateTimeOffset实例的日期和时间与世界标准时间(UTC)之间的时差。

  • DateTimeOffset.Now静态方法将返回一个DateTimeOffset 实例,包括当前(当地)时间,以及本地偏差(如操作系统的区域信息定义)。

  • DateTimeOffset.UtcNow静态方法会返回一个 DateTimeOffset由UTC在当前时间点(因为如果你是在格林尼治)。

其他有用的类型是TimeZoneTimeZoneInfo类。


这不是解决所提出问题的方法。
caradrye 2014年


4

业务规则应始终在民事时间生效(除非法律另有规定)。请注意,民事时间是一团糟,但这是人们所使用的,所以这很重要。

在内部,将时间戳记保持为距民用时间秒数之类的时间。该时期并不重要,特别是(我赞成Unix纪元),但它确实使事情比其他更容易。假装leap秒根本就不存在,除非您正在做某些真正需要leap秒的事情(例如,卫星跟踪)。时间戳和显示时间之间的映射是应该应用DST规则的唯一点;规则经常更改(在全球范围内,每年几次;指责政客),因此您应确保不对映射进行硬编码。奥尔森的TZ数据库非常宝贵。


3
这里的问题是,民用时间(也称为日历时间)不能正确地表示单个时刻。如果您采用从秒到秒的方法,您是否真的要考虑到夏令时过渡所跳过或倒退的每一刻?可能不是。纪元基准仅适用于UTC或其他一些固定的瞬时参考。
马特·约翰逊·品脱

@MattJohnson因此是“业务规则”部分。如果规则说某事在早晨(或任何时间)的2:30发生,那么它将在一年中的某一天发生两次,而不是一年中的某天发生。如果客户不明确规则,这就是他们期望发生的事情
user253751 '16

在民用时间中保留内部时间戳可能非常棘手。自从某个纪元以来将其保持为几秒钟仍然比较棘手,我会说完全是危险的。我敢肯定有人在做,如果您非常小心的话,有可能使它在大多数时候都能正常工作,但它不能被视为最佳实践或一般建议。
Steve Summit

2

只是想指出似乎不正确或至少令人困惑的两件事:

始终按照不受夏时制影响的统一标准坚持时间。尽管UTC似乎最常被提及,但GMT和UTC却被不同的人提及。

出于(几乎)所有实际计算目的,UTC实际上是GMT。除非您看到带有小数秒的时间戳,否则您正在处理GMT,这使此区别变得多余。

存储时间戳时,请原样包含本地时间偏移(包括DST偏移)。

时间戳始终以GMT表示,因此没有偏移量。


1
本地时间偏移将使您可以重新创建事件的原始“墙上时间”。如果您不存储此附加字段,则此数据将丢失。
nogridbag

是的,除了UTC和GMT偏移是相反的。例如,UTC-0700与GMT + 0700相同。确实,如今,所有数字产品都应使用UTC。
Matt Johnson-Pint 2012年

1
@Matt:您确定UTC和GMT偏移量是相反的吗?我在哪里可以读到?
RetoHöhener13年

实际上,我认为我以前是不对的。GMT和UTC本身并没有倒置。就是有一些实现以相反的方式显示它们,例如IANA / Olson / TZDB数据库。看这里。您看到的相反偏移量的另一个区域是javascript的getTimezoneOffset()函数。
马特·约翰逊·品脱

@MattJohnson:这些都是hack,应该废除。
Alix Axel

2

这是我的经验:

(不需要任何第三方库)

  • 在服务器端,以UTC格式存储时间,以便数据库中的所有日期/时间值都采用单一标准,而与用户,服务器,时区或DST的位置无关。
  • 在用户界面层或发送给用户的电子邮件中,您需要根据用户显示时间。为此,您需要具有用户的时区偏移量,以便可以将此偏移量添加到数据库的UTC值中,这将导致用户的本地时间。您可以在用户注册时获取用户的时区偏移量,也可以在Web和移动平台中自动检测到它们。对于网站,JavaScript的功能getTimezoneOffset()方法是1.0版以来的标准,并且与所有浏览器兼容。(参考:http : //www.w3schools.com/jsref/jsref_getTimezoneOffset.asp

考虑DST更改时,这不起作用。 getTimezoneOffset返回调用日期和时间的偏移量。当时区转换为夏时制时间时,该偏移量可能会更改。请参阅时区标记Wiki “时区!=偏移量” 。
马特·约翰逊·品脱

1
如果要存储“在10:00做某事”之类的警报,由于DST偏移,不能将其存储在UTC中。您必须相对于当地时间存储它。
亚历克斯(Alex)

2

汤姆·斯科特(Tom Scott) 在Computerphile频道上YouTube有关时区视频也对该主题进行了生动有趣的描述。示例包括:

  • 萨摩亚(太平洋中的一个小岛)将时区向前移动了24小时,以简化与澳大利亚和新西兰的贸易,
  • 西岸(West Bank),有2个人在不同的时区,
  • 18世纪从儒略历转变为公历 (发生在20世纪的俄罗斯)。

1

实际上,kernel32.dll不会导出SystemTimeToTzSpecificLocation。但是,它确实导出以下两个:SystemTimeToTzSpecificLocalTime和TzSpecificLocalTimeToSystemTime ...


1

永远不要只依赖像

 new DateTime(int year, int month, int day, int hour, int minute, TimeZone timezone)

当由于DST导致某个日期时间不存在时,它们可以引发异常。相反,应构建自己的方法来创建此类日期。在它们中,捕获由于DST发生的所有异常,并需要用转换偏移量调整时间。DST可能会在不同的日期和不同的时间(对于巴西甚至在午夜)发生。


1

仅举一个例子来说明处理时间就是上面描述的巨大混乱,并且您永远不能沾沾自喜。在此页面上的几个地方,leap秒已被忽略。

几年前,Android操作系统使用GPS卫星获取UTC时间参考,但忽略了GPS卫星不使用leap秒的事实。直到除夕出现混乱时,没人注意到,当时苹果手机用户和安卓手机用户的倒计时大约相差15秒。

我认为此问题已得到解决,但您永远不知道这些“次要细节”何时会再次困扰您。


1
  • 永远不要存储没有UTC偏移量(或对时区的引用)的本地时间-例如,FAT32和C struct tm中不这样做的示例。¹
  • 了解时区是以下各项的组合
    • 一组UTC偏移量(例如,冬天+0100,夏天+0200)
    • 切换发生时间的规则(可能会随时间而变化:例如,在1990年代,欧盟将切换协调为3月和10月的最后一个星期日,标准时间02:00 / DST 03:00;以前是不同的成员国之间)。

¹的某些实现struct tm确实存储UTC偏移量,但这尚未成为标准。


-4

在处理数据库(特别是MySQL,但这适用于大多数数据库)时,我发现很难存储UTC。

  • 默认情况下,数据库通常使用服务器日期时间(即CURRENT_TIMESTAMP)。
  • 您可能无法更改服务器时区。
  • 即使您能够更改时区,您也可能会有第三方代码期望服务器时区是本地的。

我发现仅将服务器日期时间存储在数据库中,然后让数据库在SQL语句中将存储的日期时间转换回UTC(即UNIX_TIMESTAMP())会更容易。之后,您可以在代码中将datetime用作UTC。

如果您可以100%控制服务器和所有代码,则最好将服务器时区更改为UTC。


在“后备”样式的夏令时过渡期间,本地时间(服务器时间)可能不明确。如您所描述的那样使用是不可靠的。服务器的本地时间可能代表多个UTC时间。
马特·约翰逊

如果服务器位于合理的时区(即不执行DST的时区),则本地时间非常合适。
sayap
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.