我希望能够将这个问题及其答案作为处理夏令时的权威指南,特别是处理实际的变更问题。
如果您有任何要添加的内容,请执行此操作
许多系统依赖于保持准确的时间,问题在于由于夏令时改变时间 - 向前或向后移动时钟。
例如,在订单获取系统中有一个业务规则取决于订单的时间 - 如果时钟发生变化,规则可能不那么明确。如何保持订单的时间?当然有无数的场景 - 这只是一个说明性的场景。
同样重要的是,如果不是这样的话:
我会对编程,操作系统,数据持久性和该问题的其他相关方面感兴趣。
一般答案很好,但我也希望看到细节,特别是如果它们只在一个平台上可用。
做:
datetimeoffset
类型,它可以存储在单个字段中。1970-01-01T00:00:00Z
以来的整秒数(不包括闰秒)。如果需要更高的精度,请使用毫秒。此值应始终基于UTC,不进行任何时区调整。DateTimeOffset
通常是比DateTime
更好的选择。DateTime
和DateTimeZone
类提供的本机时区转换。使用DateTimeZone::listAbbreviations()
时要小心 - see answer。为了使PHP保持最新的Olson数据,请定期安装the timezonedb PECL包; see answer。>=
,<
)。别:
America/New_York
和“时区偏移”,如-05:00
。他们是两个不同的东西。见the timezone tag wiki。Date
对象在较旧的Web浏览器中执行日期和时间计算,因为ECMAScript 5.1及更低版本的a design flaw可能会错误地使用夏令时。 (这已在ECMAScript 6/2015中修复)。测试:
参考:
timezone
tag wiki page on Stack Overflowdst
timezone
其他:
虽然我没有尝试过,但我觉得引人注目的时区调整方法如下:
TZOffsets
:RegionClassId,StartDateTime和OffsetMinutes(int,以分钟为单位)。在表格中,存储当地时间变更的日期和时间列表,以及多少。表中的区域数量和日期数量取决于您需要支持的日期和地区范围。可以把它想象成是“历史”日期,即使日期应该包括未来的某些实际限制。
当您需要计算任何UTC时间的本地时间时,只需执行以下操作:
SELECT DATEADD('m', SUM(OffsetMinutes), @inputdatetime) AS LocalDateTime
FROM TZOffsets
WHERE StartDateTime <= @inputdatetime
AND RegionClassId = @RegionClassId;
您可能希望在应用程序中缓存此表,并使用LINQ或类似方法来执行查询而不是访问数据库。
这些数据可以从public domain tz database中提取出来。
这种方法的优点和脚注:
现在,这种方法或任何其他方法的唯一缺点是从本地时间到GMT的转换并不完美,因为任何对时钟具有负偏移的DST改变都会重复给定的本地时间。我担心,没有简单的方法可以解决这个问题,这也是存储当地时间的一个原因。
我已经在两种类型的系统上实现了这一点,“轮班规划系统(例如工厂工人)”和“气体依赖管理系统”......
23和25小时的长日是一个痛苦应对,因此8小时轮班需要7小时或9小时。问题是你会发现每个客户,甚至客户的部门都有不同的规则(通常没有记录)他们在这些特殊情况下做了什么。
在他们为您的“现成”软件付款之前,最好不要询问客户的一些问题。在购买软件时,很难找到预先考虑此类问题的客户。
我认为在所有情况下,您都应该记录UTC时间并在存储日期/时间之前转换为/从本地时间转换。但是,即使知道哪些时间占用了一段时间,也可能很难使用夏令时和时区。
我最近在一个Web应用程序中遇到了一个问题,在Ajax回发时,返回到我的服务器端代码的日期时间与提供的日期时间不同。
它很可能与客户端上的JavaScript代码有关,它构建了以字符串形式发回客户端的日期,因为JavaScript正在调整时区和夏令时,并且在某些浏览器中计算何时应用夏令时似乎与其他人不同。
最后,我选择完全删除客户端上的日期和时间计算,然后使用整数键将其发回给我的服务器,然后将其转换为服务器上的日期时间,以实现一致的转换。
我从中学到:除非你绝对必须,否则不要在Web应用程序中使用JavaScript日期和时间计算。
对于在服务器上运行的应用程序(包括网站和其他后端服务),应用程序应忽略服务器的时区设置。
常见的建议是将服务器的时区设置为UTC。这确实是一个很好的最佳实践,但它可以作为不遵循其他最佳实践的应用程序的创可贴。例如,服务可能正在写入具有本地时间戳而不是基于UTC的时间戳的日志文件,从而在夏令时回退过渡期间产生歧义。将服务器的时区设置为UTC将修复该应用程序。但是真正的解决方案是应用程序使用UTC开始记录。
服务器端代码(包括网站)绝不应该期望服务器的本地时区特别是任何东西。
在某些语言中,本地时区可以很容易地进入应用程序代码。例如,.NET中的DateTime.ToUniversalTime
方法将从本地时区转换为UTC,DateTime.Now
属性返回本地时区的当前时间。此外,JavaScript中的Date
构造函数使用计算机的本地时区。还有很多这样的例子。执行防御性编程非常重要,避免使用计算机本地时区设置的任何代码。
使用本地时区保留客户端代码,例如桌面应用程序,移动应用程序和客户端JavaScript。
对于网络来说,规则并不复杂......
其余的只是使用服务器端日期时间库的UTC /本地转换。很高兴...
将服务器设置为UTC,并确保它们都配置为ntp或等效服务器。
UTC避免夏令时问题,并且不同步的服务器可能会导致需要一段时间诊断的不可预测的结果。
处理存储在FAT32文件系统中的时间戳时要小心 - 它始终保存在本地时间坐标(包括DST - 请参阅msdn article)中。烧了那个。
另一件事是,确保服务器应用了最新的夏令时补丁。
去年我们遇到过一种情况,即使我们使用基于UTC的系统,我们的时间在北美用户的三周时间内一直持续一小时。
事实证明它最终是服务器。他们只需要应用最新的补丁(Windows Server 2003)。
DateTimeZone::listAbbreviations()
output这个PHP方法返回一个包含一些“主要”时区(如CEST)的关联数组,这些时区本身包含更具体的“地理”时区(如欧洲/阿姆斯特丹)。
如果您正在使用这些时区及其偏移/ DST信息,那么实现以下内容非常重要:
似乎包含了每个时区的所有不同偏移/ DST配置(包括历史配置)!
例如,欧洲/阿姆斯特丹可以在此功能的输出中找到六次。两次出现(抵消1172/4772)是阿姆斯特丹时间,直到1937年;两个(1200/4800)是1937年至1940年间使用的时间;两个(3600/4800)是自1940年以来使用的时间。
因此,您不能依赖此函数返回的偏移/ DST信息作为当前正确/正在使用的信息!
如果你想知道某个时区的当前偏移/ DST,你必须做这样的事情:
<?php
$now = new DateTime(null, new DateTimeZone('Europe/Amsterdam'));
echo $now->getOffset();
?>
如果您碰巧维护使用DST活动的数据库系统,请仔细检查是否需要在秋季过渡期间关闭它们。 Mandy DBS(或其他系统)也不喜欢在(本地)时间两次传递相同的点,这正是你在秋季转回时钟时发生的事情。 SAP已经通过(恕我直言)非常简洁的解决方案解决了这个问题 - 他们只是让内部时钟以正常速度的一半运行两个小时......
我不确定我能在上面的答案中添加什么,但这里有几点我:
您应该考虑四种不同的时间:
还有历史/交替时间。这些都很烦人,因为它们可能无法映射回标准时间。例如:朱利安日期,日期根据土星的农历,克林贡日历。
以UTC格式存储开始/结束时间戳效果很好。对于1,您需要与事件一起存储的事件时区名称+偏移量。对于2,您需要为每个区域存储本地时间标识符,并为每个查看器存储本地时区名称+偏移量(如果您处于紧缩状态,则可以从IP中获取此值)。对于3,以UTC秒存储,不需要时区。 4是1或2的特殊情况,具体取决于它是全局还是本地事件,但您还需要存储在时间戳中创建的内容,以便您可以判断在创建此事件之前或之后是否更改了时区定义。如果您需要显示历史数据,这是必要的。
以上的一个例子是:
足球世界杯决赛比赛于2010年7月11日19:00 UTC在南非(UTC + 2 - SAST)举行。
有了这些信息,我们可以历史地确定2010年WCS决赛发生的确切时间,即使南非时区定义发生变化,并且能够在查询数据库时向当地时区的观众显示。
您还需要使您的操作系统,数据库和应用程序tzdata文件保持同步,彼此之间以及与世界其他地方同步,并在升级时进行广泛测试。您依赖的第三方应用程序未正确处理TZ更改并非闻所未闻。
确保硬件时钟设置为UTC,如果您在世界各地运行服务器,请确保其操作系统也配置为使用UTC。当您需要从多个时区的服务器复制每小时轮换的apache日志文件时,这一点就变得很明显了。按文件名对它们进行排序仅在所有文件都使用相同的时区命名时才有效。这也意味着当您从一个框转到另一个框并且需要比较时间戳时,您不必在头脑中进行日期数学计算。
此外,在所有框上运行ntpd。
永远不要相信从客户端计算机获得的时间戳有效。例如,Date:HTTP标头或javascript Date.getTime()
调用。当用作不透明标识符时,或者在同一客户端上的单个会话期间进行日期数学运算时,这些都很好,但不要尝试将这些值与服务器上的内容交叉引用。您的客户端不运行NTP,并且可能不一定有可用于其BIOS时钟的电池。
最后,政府有时会做很奇怪的事情:
从1909-05-01到1937-06-30,荷兰的标准时间恰好是UTC的19分和32.13秒。使用HH:MM格式无法准确表示此时区。
好的,我想我已经完成了。
业务规则应始终适用于civil time(除非有立法另有说明)。请注意,民用时间是一团糟,但它是人们使用的,所以它是重要的。
在内部,将时间戳保持在像纪元的民间时间之类的东西中。 epoch并不重要(我赞成Unix epoch)但它确实使事情变得更容易。假设leap-seconds不存在,除非你正在做一些真正需要它们的东西(例如,卫星跟踪)。时间戳和显示时间之间的映射是应该应用DST规则的唯一点;规则经常变化(在全球范围内,一年几次;责怪政治家)所以你应该确保你不对映射进行硬编码。奥尔森的TZ database非常宝贵。
您使用的是.NET框架吗?如果是这样,让我向您介绍使用.NET 3.5添加的DateTimeOffset类型。
这种结构同时包含DateTime
和Offset(TimeSpan
),它指定了DateTimeOffset
实例的日期和时间与协调世界时(UTC)之间的差异。
DateTimeOffset.Now
静态方法将返回一个DateTimeOffset
实例,该实例由当前(本地)时间和本地偏移量(在操作系统的区域信息中定义)组成。DateTimeOffset.UtcNow
静态方法将返回一个DateTimeOffset
实例,该实例包含UTC中的当前时间(就像你在格林威治一样)。其他有用的类型是TimeZone和TimeZoneInfo类。
对于那些在.NET上挣扎的人来说,看看使用DateTimeOffset
和/或TimeZoneInfo
是值得的。
如果您想使用IANA/Olson time zones,或者发现内置类型不足以满足您的需求,请查看Noda Time,它为.NET提供了更智能的日期和时间API。
只是想指出两件似乎不准确或至少令人困惑的事情:
始终按照不受夏令时影响的统一标准保持时间。不同的人已经提到了GMT和UTC,尽管UTC似乎最常提到。
对于(几乎)所有实际计算目的,UTC实际上是GMT。除非你看到一个小数秒的时间戳,否则你正在处理GMT,这使得这种区分变得多余。
存储时间戳时,请按原样包括本地时间偏移量(包括DST偏移量)。
时间戳始终以GMT表示,因此没有偏移量。
这是我的经历: -
(不需要任何第三方库)
Tom Smith在Computerphile频道上的video about timezones on YouTube也有一个很好的,有趣的描述。例子包括:
实际上,kernel32.dll不会导出SystemTimeToTzSpecificLocation。然而,它确实导出以下两个:SystemTimeToTzSpecificLocalTime和TzSpecificLocalTimeToSystemTime ...
永远不要只依靠建设者
new DateTime(int year, int month, int day, int hour, int minute, TimeZone timezone)
由于DST,当某个日期时间不存在时,它们可以抛出异常。相反,构建自己的方法来创建此类日期。在其中,捕获由于DST而发生的任何异常,并使用转换偏移调整所需的时间。根据时区,夏令时可能发生在不同的日期和不同的时间(甚至是巴西的午夜)。
只是一个例子来证明处理时间是描述的巨大混乱,并且你永远不会自满。在这个页面的几个点上,闰秒被忽略了。
几年前,Android操作系统使用GPS卫星获取UTC时间参考,但忽略了GPS卫星不使用闰秒这一事实。直到新年前夜出现混乱时才有人注意到,当时苹果手机用户和Android手机用户的间隔时间相差约15秒。
我认为它已被修复,但你永远不知道这些“小细节”什么时候会回来困扰你。
struct tm
.¹¹struct tm
的一些实现确实存储了UTC偏移量,但这并未使其成为标准。
这是一个重要且令人惊讶的棘手问题。事实是,没有完全令人满意的标准来坚持时间。例如,SQL标准和ISO格式(ISO 8601)显然是不够的。
从概念的角度来看,人们通常会处理两种类型的时间 - 数据,并且很容易区分它们(上述标准没有):“物理时间”和“民用时间”。
“物理”时刻是物理学处理的连续通用时间线中的一个点(当然忽略相对论)。例如,这个概念可以用UTC充分编码 - 持久化(如果你可以忽略闰秒)。
“民用”时间是遵循民用规范的日期时间规范:此处的时间点由一组日期时间字段(Y,M,D,H,MM,S,FS)加上TZ(时区规范)完全指定。 (实际上也是“日历”;但我们假设我们将讨论限制在格里高利历)。时区和日历共同允许(原则上)从一个表示映射到另一个表示。但是,民用和物理时刻本质上是不同类型的大小,它们应该在概念上保持分离和区别对待(类比:字节数组和字符串)。
这个问题令人困惑,因为我们可以互换地谈论这些类型的事件,因为民事时代受政治变化的影响。对于未来的事件,问题(以及区分这些概念的必要性)变得更加明显。示例(取自我的讨论here。
约翰在他的日历中记录了日期时间2019-Jul-27, 10:30:00
,TZ = 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)是正确的。
一些日期/时间API获得了这种区别:其中包括Jodatime,它是下一个(第三个!)Java DateTime API(JSR 310)的基础。
在处理数据库(特别是MySQL,但这适用于大多数数据库)时,我发现很难存储UTC。
我发现在数据库中存储服务器日期时间更容易,然后让数据库将存储的日期时间转换回SQL语句中的UTC(即UNIX_TIMESTAMP())。之后,您可以在代码中使用日期时间作为UTC。
如果您100%控制服务器和所有代码,则最好将服务器时区更改为UTC。
明确关注点的架构分离 - 准确了解哪个层与用户交互,并且必须更改规范表示(UTC)的日期时间。非UTC日期时间是表示(遵循用户本地时区),UTC时间是模型(对于后端和中间层保持唯一)。
另外,决定你的实际观众是什么,你不需要服务什么,以及你在哪里画线。不要触摸异国情调的日历,除非您实际上有重要的客户,然后考虑仅针对该区域的单独的面向用户的服务器。
如果您可以获取并维护用户的位置,请使用位置进行系统的日期时间转换(例如.NET文化或SQL表),但如果日期时间对您的用户至关重要,则为最终用户提供选择替代的方法。
如果涉及历史审计义务(比如确切知道2月前Jo在AZ支付账单的时间是9月),那么保留UTC和当地时间进行记录(您的转换表将在一段时间内发生变化)。
为批量生成的数据定义时间参考时区 - 如文件,Web服务等。假设East Coast公司在CA中有数据中心 - 您需要询问并知道他们使用什么作为标准而不是假设其中一个。
不要信任嵌入在日期时间的文本表示中的时区偏移,并且不接受解析和遵循它们。而是始终要求必须明确定义时区和/或参考区域。您可以轻松地接收PST偏移的时间,但时间实际上是EST,因为这是客户端的参考时间,而记录只是在PST中的服务器上导出。
你需要了解Olson tz database,它可以从 ftp://elsie.nci.nih.gov/pub http://iana.org/time-zones/。它每年更新多次,以处理世界各地不同国家在冬季和夏季(标准和夏令时)之间切换的时间(以及是否)的最后时刻变化。 2009年,最后一个版本是2009年;在2010年,它是2010n;在2011年,它是2011n;截至2012年5月底,发布时间为2012c。请注意,有一组代码可以在两个单独的存档(tzcode20xxy.tar.gz和tzdata20xxy.tar.gz)中管理数据和实际时区数据本身。代码和数据都属于公共领域。
这是时区名称的来源,例如America / Los_Angeles(以及美国/太平洋等同义词)。
如果您需要跟踪不同的区域,那么您需要Olson数据库。正如其他人所建议的那样,您还希望以固定格式存储数据 - 通常是选择的UTC - 以及生成数据的时区记录。您可能希望区分当时与UTC的偏移量和时区名称;这可以在以后产生影响。此外,知道它目前是2010-03-28T23:47:00-07:00(美国/太平洋)可能会或可能不会帮助您解释2010-11-15T12:30的值 - 这可能是在PST中指定的(太平洋标准时间)而非PDT(太平洋夏令时)。
标准的C库接口对这类东西并没有太大的帮助。
奥尔森的数据有所改变,部分原因是因为A D Olson将很快退休,部分原因是因为版权侵权的维护者有一项(现已被驳回)法律诉讼。现在,时区数据库是在互联网号码分配机构IANA的主持下进行管理的,并且首页上有一个链接到'Time Zone Database'。讨论邮件列表现在是[email protected]
;公告名单是[email protected]
。
通常,在存储的时间戳中包括本地时间偏移量(包括DST偏移量):如果稍后要在其原始时区(和DST设置)中显示时间戳,则仅使用UTC是不够的。
请记住,偏移量并不总是整数小时(例如,印度标准时间是UTC + 05:30)。
例如,合适的格式是元组(unix时间,以分钟为单位的偏移量)或ISO 8601。
越过“计算机时间”和“人们时间”的界限是一场噩梦。主要的一点是,时区和夏令时的规则没有任何标准。各国可随时自由改变其时区和DST规则,而且确实如此。
一些国家,以色列,巴西,每年决定何时进行夏令时,因此无法提前知道(如果)DST何时生效。其他人有关于DST何时生效的固定(ish)规则。其他国家不使用夏令时。
时区不一定与格林威治标准时间完全不同。尼泊尔是+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的国家/地区(总共6个)。
对于PHP:
PHP> 5.2中的DateTimeZone类已经基于其他人提到的Olson DB,因此如果您在PHP中而不是在DB中进行时区转换,则可以免除使用(难以理解的)Olson文件。
但是,PHP没有像Olson DB那样频繁更新,因此仅使用PHP时区转换可能会留下过时的DST信息,并影响数据的正确性。虽然预计这种情况不会频繁发生,但如果全球拥有大量用户,情况可能会发生。
要解决上述问题,请使用timezonedb pecl package,它的功能是更新PHP的时区数据。按照更新的频率安装此软件包。 (我不确定这些软件包更新是否完全遵循Olson更新,但似乎更新的频率至少非常接近Olson更新。)
如果您的设计可以容纳它,请避免本地时间转换!
我知道有些人可能听起来很疯狂但是考虑一下用户体验:用户处理的时间比相对日期(今天,昨天,下周一)快于绝对日期(2010.09.17,周一9月17日)。当你考虑更多时,时区(和DST)的准确性越接近now()
,所以如果你能用相对格式表示日期/日期时间+/- 1或2周,其余的日期可以是UTC,对95%的用户来说都不会太重要。
这样,您可以以UTC格式存储所有日期,并以UTC格式进行相对比较,只显示相对日期阈值之外的用户UTC日期。
这也可以应用于用户输入(但通常以更有限的方式)。从只有{昨天,今天,明天,下周一,下周四}的下拉菜单中选择对于用户来说比日期选择器更简单,更容易。采摘日期是形式填充最痛苦的组成部分。当然,这对所有情况都不适用,但你可以看到它只需要一点巧妙的设计就可以使它变得非常强大。