夏令时和时区最佳实践

我希望将这个问题及其答案作为处理夏令时的最终指南,特别是处理实际变化。

如果你有什么要补充的,请

许多系统依赖于保持准确的时间,问题是由于夏令时而改变时间-将时钟向前或向后移动。

例如,一个订单接收系统中的业务规则依赖于订单的时间,如果时钟改变,规则可能不那么清楚。订单的时间应该如何持久化?当然有无数的场景-这个只是一个说明性的场景。

  • 你是如何处理夏令时问题的?
  • 什么假设是你解决方案的一部分?(在这里寻找上下文)

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

  • 你尝试了什么不起作用?
  • 为什么它不工作?

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

一般答案很好,但我也希望看到细节,特别是如果它们只在一个平台上可用。

474705 次浏览

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

在内部,将时间戳保持在民用时间秒从纪元。时代并不特别重要(我更喜欢unix纪元),但它确实比其他选择更容易。假装闰秒不存在,除非你正在做真的需要它们的事情(例如卫星跟踪)。时间戳和显示时间之间的映射是DST规则应该应用的唯一点;规则经常变化(在全球范围内,一年几次;责怪政治家)所以你应该确保你不硬编码映射。奥尔森的tz数据库是无价的。

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

做:

  • 每当您指的是一个精确的时刻时,请根据不受夏令时影响的统一标准持续时间。(GMT和UTC在这方面是等效的,但最好使用术语UTC。请注意,UTC也称为祖鲁Z时间。)
  • 如果您选择使用本地时间值持久化(过去)时间,请包含来自UTC的此特定时间的本地时间偏移量(此偏移量可能全年变化),以便稍后可以明确解释时间戳。
  • 在某些情况下,您可能需要存储两者 UTC时间和等效的本地时间。通常这是用两个单独的字段完成的,但某些平台支持datetimeoffset类型,可以将两者存储在一个字段中。
  • 将时间戳存储为数值时,请使用unix时间-这是自1970-01-01T00:00:00Z以来的整秒数(不包括闰秒)。如果您需要更高的精度,请使用毫秒代替。此值应始终基于UTC,没有任何时区调整。
  • 如果您以后可能需要修改时间戳,请包含原始时区ID,以便您可以确定偏移量是否与记录的原始值发生了变化。
  • 调度未来事件时,通常首选本地时间而不是UTC,因为偏移量更改很常见。见答案博客文章
  • 存储整个日期时,例如生日和周年纪念日,请勿转换为UTC或任何其他时区。
    • 如果可能,请存储在不包括一天中的时间的仅限日期的数据类型中。
    • 如果这样的类型不可用,请务必在解释值时始终忽略一天中的时间。如果您不能确定将忽略一天中的时间,请选择中午12:00,而不是午夜00:00作为当天更安全的代表性时间。
  • 请记住,时区偏移并不总是整数小时数(例如,印度标准时间为UTC+05:30,尼泊尔使用UTC+05:45)。
  • 如果使用Java,请在Java8及更高版本中使用java.time
  • 大部分java.time功能都被反向移植到三个十-后门库中的Java6和7。
  • 进一步适应了ThreeTenABP库中的早期Android(<26)。
  • 这些项目正式取代了古老的Joda-Time,现在在维护模式。Joda-Time,ThreeTen-Backport,额外三个java.time类和JSR 310由同一个人领导,斯蒂芬·科尔伯恩
  • 如果使用. NET,请考虑使用野田时间
  • 如果使用. NET而没有Noda Time,请考虑DateTimeOffset通常比DateTime更好。
  • 如果使用Perl,请使用时间戳
  • 如果使用Python 3.9或更高版本,请使用内置的zoneinfo来处理时区。否则,使用日期箭头。通常可以避免使用旧的pytz库。
  • 如果使用JavaScript,请避免使用旧的moment.js时刻时区库,因为它们不再被主动维护。有关更多详细信息,请参阅Moment.js项目状态。相反,请考虑卢克松date-fnsday.jsjs-joda
  • 如果使用PHP>5.2,请使用DateTimeDateTimeZone类提供的本机时区转换。使用DateTimeZone::listAbbreviations()-见答案时要小心。要使PHP保持最新的Olson数据,请定期安装的时区b 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小时。在涉及多个区域的任何计算中,请注意此类项目。
  • 考虑时间的类型(实际事件时间,广播时间,相对时间,历史时间,重复时间)您需要存储哪些元素(时间戳,时区偏移和时区名称)以进行正确的检索-请参阅这个答案中的“时间类型”。
  • 保持您的操作系统、数据库和应用程序tzdata文件在它们自己和世界其他地方之间同步。
  • 在服务器上,将硬件时钟和操作系统时钟设置为UTC而不是本地时区。
  • 不管前面的要点是什么,服务器端代码,包括网站,应该从未期望服务器的本地时区是任何特定的东西。见答案
  • 更喜欢在应用程序代码中根据具体情况处理时区,而不是通过配置文件设置或默认值全局处理。
  • 在所有服务器上使用NTP服务。
  • 如果使用FAT32,请记住时间戳存储在本地时间,而不是UTC。
  • 在处理反复出现的事件(例如每周电视节目)时,请记住时间会随着DST而变化,并且会因时区而异。
  • 始终将日期时间值查询为下限包含,上限排他>=<)。

不要:

  • 不要混淆“时区”,例如America/New_York和“时区偏移”,例如-05:00。它们是两个不同的东西。请参阅时区标签wiki
  • 不要在较旧的Web浏览器中使用JavaScript的Date对象来执行日期和时间计算,因为ECMAScript 5.1及更低版本的设计缺陷可能会错误地使用夏令时。(这在ECMAScript 6/2015中已修复)。
  • 永远不要相信客户的时钟,它很可能是错误的。
  • 不要告诉人们“在任何地方都要使用UTC”。这种普遍的建议是对本文档前面描述的几个有效场景的短视。相反,请为您正在处理的数据使用适当的时间参考。(时间戳可以使用UTC,但未来的时间调度和仅限日期的值不应该。)

测试:

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

参考:

其他:

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

这很可能与我在客户端上的JavaScript代码有关,这些代码将日期作为字符串发布回客户端,因为JavaScript正在调整时区和夏令时,并且在某些浏览器中,何时应用夏令时的计算似乎与其他浏览器不同。

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

我从中学习到:不要在Web应用程序中使用JavaScript日期和时间计算,除非您必须这样做。

保持您的服务器设置为UTC,并确保它们都配置为ntp或等效的。

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

我在两种类型的系统上遇到了这个问题,“轮班计划系统(例如工厂工人)”和“气体依赖管理系统)。

23和25小时长的一天是一个痛苦的应付,所以8小时轮班需要7小时或9小时。问题是,你会发现每个客户,甚至客户部门都有不同的规则,他们已经创建(通常没有记录)在这些特殊情况下他们做什么。

有些问题最好不要问客户,直到他们为您的“现成”软件付费之后。很少有客户在购买软件时预先考虑此类问题。

我认为在所有情况下,您都应该以UTC记录时间,并在存储日期/时间之前转换为/从本地时间。然而,即使知道给定的时间在夏令时和时区也很困难。

一般来说,如果您以后想在其原始时区(和DST设置)中显示时间戳,仅在存储的时间戳中显示包括本地时间偏移(包括DST偏移):UTC是不够的。

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

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

虽然我没有尝试过,但我觉得有吸引力的时区调整方法如下:

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

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

在表中,存储日期和时间列表本地时间更改,以及更改了多少。表中区域的数量和日期的数量将取决于您需要支持的日期范围和世界的区域。将此视为“历史”日期,即使日期应该包括实际限制的未来。

当您需要计算任何UTC时间的本地时间时,只需这样做:

SELECT DATEADD('m', SUM(OffsetMinutes), @inputdatetime) AS LocalDateTimeFROM   TZOffsetsWHERE  StartDateTime <= @inputdatetimeAND RegionClassId = @RegionClassId;

您可能希望在您的应用程序中缓存此表并使用LINQ或一些类似的方法来执行查询,而不是访问数据库。

这些数据可以从公共领域z数据库中提取出来。

这种方法的优点和脚注:

  1. 代码中没有任何规则,您可以随时调整新区域或日期范围的偏移量。
  2. 您不必支持所有日期或区域范围,您可以根据需要添加它们。
  3. 区域不必直接对应于地缘政治边界,并且为了避免重复行(例如,美国的大多数州以相同的方式处理DST),您可以在另一个表中拥有广泛的区域类别条目,这些条目链接到更传统的州、国家等列表。
  4. 对于像美国这样的情况,DST的开始和结束日期在过去几年中发生了变化,这很容易处理。
  5. 由于StartDateTime字段也可以存储时间,因此可以轻松处理2:00 AM标准转换时间。
  6. 世界上并非所有地方都使用1小时的DST。这很容易处理这些情况。
  7. 该数据表是跨平台的,可以是一个单独的开源项目,可以由使用几乎任何数据库平台或编程语言的开发人员使用。
  8. 这可以用于与时区无关的偏移。例如,不时发生的1秒调整,以调整地球的自转,公历和公历内的历史调整等。
  9. 由于这是在数据库表中,标准报表查询等可以利用数据,而无需通过业务逻辑代码。
  10. 如果您愿意,这也可以处理时区偏移,甚至可以考虑将一个地区分配给另一个时区的特殊历史情况。您只需要一个初始日期,为每个地区分配一个时区偏移,并提供最小的开始日期。这需要为每个时区创建至少一个地区,但可以让您提出有趣的问题,例如:“1989年2月2日上午5:00,亚利桑那州尤马和华盛顿州西雅图之间的本地时间有什么区别?”(只需从另一个中减去一个SUM())。

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

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

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

“物理”时间瞬间是物理学处理的连续通用时间线中的一个点(当然忽略相对论)。例如,这个概念可以在UTC中充分编码(如果你可以忽略闰秒)。

“民用”时间是遵循民用规范的日期时间规范:这里的时间点完全由一组日期时间字段(Y,M,D,H,MM,S,FS)加上TZ(时区规范)(实际上也是“日历”;但让我们假设我们将讨论限制在公历)。时区和日历(原则上)共同允许从一种表示映射到另一种表示。但民用和物理时间瞬间是根本不同的量值类型,它们应该在概念上保持分离并区别对待(类似于:字节数组和字符串)。

这个问题令人困惑,因为我们可以互换地谈论这些类型的事件,并且因为内战时期会受到政治变化的影响。这个问题(以及区分这些概念的必要性)在未来的事件中变得更加明显。

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

现在,当那一天到来的时候…

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=智利/圣地亚哥"。

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

或者他的意思是“时间的物理瞬间”,连续体中的一个点我们宇宙的时间线,说,“当下一次日食在这种情况下,答案B)是正确的。

一些日期/时间API正确地进行了这种区分:其中,Jodatime是下一个(第三个!)JavaDateTime API(JSR 310)的基础。

如果您碰巧维护DST处于活动状态的数据库系统,请仔细检查是否需要在秋季转换期间关闭它们。Mandy DBS(或其他系统)不喜欢在(本地)时间内两次传递同一点,这正是当您将打卡返回时发生的情况。SAP通过一个(IMHO非常巧妙的)解决方法解决了这个问题——他们不是将时钟返回,而是让内部时钟以通常速度的一半运行两个小时……

您需要了解Olsontz数据库,它可从ftp://elsie.nci.nih.gov/pubhttp://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库接口对这种东西并不是很有帮助。


奥尔森数据发生了变化,部分原因是奥尔森很快就要退休了,部分原因是对维护人员侵犯版权的诉讼(现已驳回)。时区数据库现在由互联网号码分配机构IANA管理,首页上有一个指向时区数据库的链接。讨论邮件列表现在是tz@iana.org;公告列表是tz-announce@iana.org

跨越“计算机时间”和“人类时间”的界限是一场噩梦。主要原因是时区和夏令时的管理规则没有任何标准。各国可以随时自由更改其时区和夏令时的规则,他们确实这样做了。

一些国家,例如以色列,巴西,每年都决定何时实施夏令时,因此不可能提前知道DST何时生效。其他国家对DST何时生效有固定的规则。其他国家根本不使用夏令时。

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

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

都是同样的时间,但三天不同!

时区的缩写也没有明确的标准,以及它们在DST中如何变化,所以你最终会得到这样的东西:

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

最好的建议是尽量远离当地时间,尽量使用UTC时间。只在最后一刻转换为当地时间。

测试时,请确保您测试的国家在西半球和东半球,与DST都在进行中,而不是和一个国家,不使用DST(共6)。

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

时间类型

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

  1. 事件时间:例如,国际体育赛事发生的时间,或加冕/死亡/等,这取决于事件的时区,而不是观众的时区。
  2. 电视时间:例如,一个特定的电视节目在当地时间晚上9点在世界各地播出。考虑在您的网站上发布结果(例如美国偶像)时很重要
  3. 相对时间:例如:这个问题有一个开放的赏金在21小时内关闭。这很容易显示
  4. 电视节目每周一晚上9点播出,即使DST改变了。

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

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

存储时间

  • 始终以UTC存储时间
  • 转换为显示的本地时间(本地时间由查看数据的用户定义)
  • 存储时区时,您需要名称、时间戳和偏移量。这是必需的,因为政府有时会更改其时区的含义(例如:美国政府更改了DST日期),并且您的应用程序需要优雅地处理事情…例如:LOST剧集在DST规则更改之前和之后显示的确切时间戳。

偏移量和名称

上面的一个例子是:

世界杯足球赛决赛发生在南非(UTC+2--SAST)2010年7月11日19:00 UTC

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

系统时间

您还需要保持操作系统、数据库和应用程序tzdata文件彼此同步,并与世界其他地方同步,并在升级时进行广泛测试。您依赖的第三方应用程序没有正确处理TZ更改的情况并非闻所未闻。

确保硬件时钟设置为UTC,如果您在世界各地运行服务器,请确保他们的操作系统也配置为使用UTC。当您需要从多个时区的服务器复制每小时轮换的apache日志文件时,这一点就变得很明显。仅当所有文件都以相同的时区命名时,按文件名对它们进行排序才有效。这也意味着当您从一个盒子ssh到另一个盒子并需要比较时间戳时,您不必在脑海中进行日期计算。

此外,在所有框上运行ntpd。

客户

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

琐事

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

荷兰的标准时间是正好19分32.13秒根据1909-05-01的法律,UTC之前到1937-06-30这个时区不能准确表示为HH: MM格式。

好的,我想我完成了。

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

此外,决定你的实际受众是什么,你不需要服务什么,你在哪里划清界限。除非您在那里确实有重要客户,否则不要接触异国情调的日历,然后考虑仅针对该地区的单独面向用户的服务器。

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

如果存在历史审计义务涉及(例如确切地告诉Jo在AZ 2年前9月支付账单的时间)保持UTC和当地时间用于记录(您的转换表将在一段时间内发生变化)。

为批量数据定义时间参考时区-像文件,网络服务等。假设东海岸公司在加利福尼亚州有数据中心-你需要询问并知道他们使用什么作为标准,而不是假设一个或另一个。

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

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

去年我们遇到了这样的情况,在北美用户的三周时间里,我们的时间一直是一小时,即使我们使用的是基于UTC的系统。

事实证明最终是服务器。他们只需要应用最新的补丁(WindowsServer2003)。

处理存储在FAT32文件系统中的时间戳时要小心-它总是持久化在本地时间坐标中(包括DST-请参阅msdn文章)。

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

我知道对一些人来说这听起来可能很疯狂,但想想用户体验:用户处理附近的相对日期(今天,昨天,下周一)的速度比绝对日期(2010.09.17,9月17日星期五)的速度要快。当你考虑得更多时,时区(和DST)的准确性越接近日期now()就越重要,所以如果你能以相对格式表达+/-1或2周的日期/日期时间,其余的日期可以是UTC,对95%的用户来说并不重要。

通过这种方式,您可以将所有日期存储在UTC中,并以UTC进行相对比较,只需在相对日期阈值之外向用户显示UTC日期。

这也适用于用户输入(但通常是以更有限的方式)。从只有{昨天,今天,明天,下周一,下周四}的下拉列表中进行选择对用户来说比日期选择器简单得多。日期选择器是表单填写中最令人痛苦的组件之一。当然,这并不适用于所有情况,但您可以看到,只需要一点聪明的设计就可以使其变得非常强大。

对于PHP:

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

但是,PHP的更新频率不如Olson DB,因此仅使用PHP时区转换可能会给您留下过时的DST信息并影响数据的正确性。虽然预计不会经常发生这种情况,但它可能会发生,如果您在全球拥有大量用户,则可能会发生0。

要解决上述问题,请使用timezonedb pe l包。它的功能是更新PHP的时区数据。安装此包的频率与更新的频率相同。(我不确定此包的更新是否完全遵循Olson更新,但它的更新频率似乎至少非常接近Olson更新的频率。)

实际上,kernel32.dll不会导出SystemTimeToTzSpecificLocalTime,但会导出以下两个:SystemTimeToTzSpecificLocalTime和TzSpecificLocalTimeToSystemTime…

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

始终坚持时间根据一个统一的标准,不是受夏令时影响。GMT和UTC已被提及不同的人,虽然UTC似乎最常被提及。

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

当包含本地时间偏移量(包括DST偏移量)时存储时间戳。

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

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

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

我发现在数据库中存储服务器日期时间更容易,然后让数据库在SQL语句中将存储的日期时间转换回UTC(即UNIX_TIMESTAMP())。

如果您对服务器和所有代码拥有100%的控制权,最好将服务器时区更改为UTC。

您是否正在使用. NET框架?如果是这样,让我向您介绍使用. NET 3.5添加的时间戳日期时间戳类型。

此结构包含DateTime和Offset(TimeSpan),后者指定DateTimeOffset实例的日期和时间与国际协调时间(UTC)之间的差异。

  • DateTimeOffset.Now静态方法将返回DateTimeOffset由当前(本地)时间和本地偏移组成的实例(在操作系统的区域信息中定义)。

  • DateTimeOffset.UtcNow静态方法将返回一个DateTimeOffset由UTC当前时间组成的实例(如如果你在格林威治)。

其他有用的类型是时区时区信息类。

永远不要只依赖像

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

当某个日期时间由于DST而不存在时,他们可以抛出异常。相反,构建您自己的创建此类日期的方法。在其中,捕获由于DST发生的任何异常,并使用转换偏移调整所需的时间。根据时区,DST可能发生在不同的日期和不同的时间(甚至在巴西的午夜)。

对于网络,规则并不复杂…

  • 服务端,使用UTC
  • 客户端,使用奥尔森
    • 原因:UTC补偿不是日光节约安全的(例如,纽约是EST(UTC-5小时)一年的一部分,EDT(UTC-4小时)一年的其余部分)。
    • 对于客户端时区确定,您有两个选项:

其余的只是使用您的服务器端日期时间库进行UTC/本地转换。

对于那些在. NET上挣扎的人,看看使用DateTimeOffset和/或TimeZoneInfo是否值得。

如果您想使用IANA/Olson时区,或者发现内置类型不足以满足您的需求,请查看野田时间,它为. NET提供了更智能的日期和时间API。

PHP的#0输出

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

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

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

例如,Europe/Amsterdam可以在此函数的输出中找到六次。两次出现(偏移量1172/4772)表示1937年之前使用的阿姆斯特丹时间;两次出现(1200/4800)表示1937年至1940年之间使用的时间;两次出现(3600/4800)表示1940年以来使用的时间。

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

如果你想知道某个时区的当前偏移/DST,你必须这样做:

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

以下是我的经验:

(不需要任何第三方库)

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

当涉及到在服务器上运行的应用程序时,包括网站和其他后端服务,应用程序应忽略服务器的时区设置。

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

服务器端代码(包括网站)应该从未期望服务器的本地时区是特定的。

在某些语言中,本地时区可以很容易地渗透到应用程序代码中。例如,. NET中的DateTime.ToUniversalTime方法将本地时区转换为UTC,DateTime.Now属性返回本地时区的当前时间。此外,JavaScript中的Date构造函数使用计算机的本地时区。像这样的例子还有很多。练习防御性编程很重要,避免任何使用计算机本地时区设置的代码。

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

Computerphile频道上的汤姆·斯科特的关于YouTube时区的视频也对该主题进行了很好且有趣的描述。例如:

  • 萨摩亚(太平洋上的一个岛屿)将时区提前24小时,以便与澳大利亚和新西兰进行交易。
  • 西岸,其中2个人群遵循不同的时区,
  • 18世纪从朱利安日历更改为格列高利日历(发生在20世纪的俄罗斯)。

仅举一个例子来证明处理时间是所描述的巨大混乱,并且您永远不能自满。

几年前,Android操作系统使用GPS卫星来获取UTC时间参考,但忽略了GPS卫星不使用闰秒的事实。直到新年前夜出现混乱,当时苹果手机用户和Android手机用户大约相隔15秒进行倒计时,才有人注意到。

我认为它已经被修复了,但你永远不知道这些“小细节”什么时候会回来困扰你。

  • 永远不要在没有UTC偏移量(或对时区的引用)的情况下存储本地时间-如何不这样做的示例包括C中的FAT32和struct tm
  • 了解时区是以下内容的组合
    • 一组UTC偏移(例如冬季+0100,夏季+0200)
    • 转换发生的规则(可能会随着时间的推移而变化:例如,在20世纪90年代,欧盟将转换统一为3月和10月的最后一个星期日,标准时间02:00/夏令时03:00;以前,成员国之间的情况有所不同)。

struct tm的一些实现确实存储了UTC偏移量,但这并没有使其成为标准。