Daylight saving time and time zone best practices
我希望把这个问题和它的答案作为处理日光节约时间的权威指南,特别是处理实际的转换。
如果你有什么要补充的,请补充
许多系统都依赖于准确的时间,问题是由于日光节约导致的时间变化——时钟向前或向后移动。
例如,在订单接收系统中有业务规则,它依赖于订单的时间——如果时钟发生变化,规则可能不那么清晰。订单的时间应该如何持久化?当然,有无数的场景——这只是一个说明性的场景。
你如何处理夏时制的问题?你的解决方案中有哪些假设?(在这里寻找上下文)同样重要,甚至更重要的是:
你做了什么没有成功的尝试?为什么没有成功?我对编程,操作系统,数据持久性和其他相关方面的问题感兴趣。
一般的答案很好,但我也想看看细节,特别是如果它们只在一个平台上可用。
答案汇总及其他数据:(请添加您的)
做的事:
无论何时,只要你指的是一个确切的时间点,都要按照不受夏令时影响的统一标准来坚持时间。(GMT和UTC在这方面是等价的,但它更倾向于使用UTC这个术语。注意,UTC也被称为Zulu或Z time。)相反,如果您选择使用本地时间值来持久化时间,则从UTC中包含此特定时间的本地时间偏移量(此偏移量可能在全年中都发生变化),以便稍后可以清楚地解释时间戳。在某些情况下,可能需要同时存储UTC时间和等效的本地时间。通常这是通过两个单独的字段来完成的,但是一些平台支持测试:
当测试,确保测试在西方国家,东部、北部和南部半球(事实上每个季度全球,所以4地区),与DST在进步而不是(8),和一个国家不使用DST(另一个4覆盖所有地区,总共12)。测试DST的转换,即当您当前处于夏季时,从冬季选择一个时间值。测试边界用例,例如一个时区是UTC+12,使用DST,使本地时间在夏天是UTC+13,甚至在冬天是UTC+13测试所有第三方库和应用程序,确保它们正确处理时区数据。至少测试半小时的时区。参考:
堆栈溢出的详细其他:
游说你的代表结束这令人憎恶的是DST。我们总是希望……游说地球标准时间我不确定我能给上面的答案补充些什么,但以下是我的一些观点:
类型的倍
你应该考虑以下四个不同的时间:
事件时间:例如,国际体育赛事发生的时间,或加冕/死亡/等等。这取决于事件的时区,而不是查看器的时区。电视时间:特定的电视节目在当地时间晚上9点在世界各地播出。当你考虑在你的网站上发布(比如美国偶像)的结果时,这一点很重要相对时间:这个问题的有效期为21小时。这很容易显示循环播放时间:例如:电视节目每周一晚上9点播出,即使DST发生变化。还有历史/交替时间。这很烦人,因为它们可能无法映射回标准时间。朱利安日期,日期根据土星的阴历,克林贡历法。
在UTC中存储开始/结束时间戳效果很好。对于1,您需要事件时区名+偏移量与事件一起存储。对于2,您需要一个与每个区域一起存储的本地时间标识符和一个为每个查看器存储的本地时区名称+偏移量(如果您处于紧急状态,可以从IP派生出这个值)。对于3,存储在UTC秒,不需要时区。4是1或2的特殊情况,这取决于它是全局事件还是本地事件,但是您还需要存储在timestamp时创建的,这样您就可以知道在创建此事件之前或之后时区定义是否发生了更改。如果需要显示历史数据,这是必要的。
<存储倍/ hh2 >总是在UTC中存储时间转换为显示的本地时间(本地由查看数据的用户定义)在存储时区时,需要名称、时间戳和偏移量。这是必需的,因为政府有时会更改时区的含义(例如:美国政府更改了DST日期),您的应用程序需要优雅地处理事情……《迷失》剧集播出的确切时间戳显示了DST规则改变前后的时间。
偏移和名称
上述情况的一个例子是:
The soccer world cup finals game
happened in South Africa (UTC+2--SAST)
on July 11, 2010 at 19:00 UTC.
有了这些信息,即使南非的时区定义发生了变化,我们也可以从历史上确定2010年WCS决赛的确切时间,并能够在观众查询数据库时向他们所在的本地时区的观众显示该时间。
系统时间
您还需要保持操作系统、数据库和应用程序tzdata文件之间以及与世界其他地方的同步,并在升级时进行广泛的测试。您所依赖的第三方应用程序没有正确处理TZ更改,这不是没有听说过的。
确保硬件时钟设置为UTC,如果您在世界各地运行服务器,请确保它们的操作系统也配置为使用UTC。当您需要从多个时区的服务器上每小时复制旋转的apache日志文件时,这一点就很明显了。只有当所有文件的名称具有相同的时区时,才可以按文件名排序。这还意味着,当您从一个机器ssh到另一个机器并需要比较时间戳时,您不必在脑子里计算日期。
同样,在所有的机器上运行ntpd。
客户
永远不要相信从客户机机器获得的时间戳是有效的。例如,日期:HTTP头,或者javascript
琐事
最后,政府有时会做一些非常奇怪的事情:
Standard time in the Netherlands was
exactly 19 minutes and 32.13 seconds
ahead of UTC by law from 1909-05-01
through 1937-06-30. This time zone
cannot be represented exactly using
the HH:MM format.
好的,我想我做完了。
这是一个重要而又棘手的问题。事实上,对于坚持时间并没有一个完全令人满意的标准。例如,SQL标准和ISO格式(ISO 8601)显然是不够的。
从概念上看,通常处理两种类型的时间-日期数据,区分"物理时间"和"民用时间"很方便(上述标准没有)。
"物理"时间瞬间是物理学处理的连续宇宙时间轴上的一个点(当然忽略了相对论)。这个概念可以在UTC中得到充分的编码——例如(如果可以忽略闰秒)。
"民用"时间是一种遵循民用规范的datetime规范:这里的时间点完全由一组datetime字段(Y、M、D、H、MM、S、FS)和TZ(时区规范)(实际上也是一个"日历";但让我们假设我们的讨论仅限于公历)。时区和日历(原则上)允许从一种表示映射到另一种表示。但是民用时间瞬间和物理时间瞬间本质上是不同类型的大小,它们应该在概念上保持分离,并以不同的方式对待(一个类比:字节数组和字符串)。
这个问题是令人困惑的,因为我们把这些类型的事件交替地说,而且因为公民时代受制于政治变化。问题(以及区分这些概念的需要)在未来的事件中变得更加明显。例子(摘自我在这里的讨论。
John在他的日历中记录了datetime上某个事件的提醒
现在,当那一天到来的时候……这个提醒应该在何时触发
A)
或
B)
没有正确的答案,除非你知道约翰概念上的意思当他告诉日历"请给我打电话"时。
他的意思是"民间的约会时间"吗10:30")?在这种情况下,A)是正确答案。
或者他的意思是"物理时间的瞬间",一个连续的点我们宇宙的时间线说,"下次日食时。发生"。在这种情况下,答案B)是正确的。
一些日期/时间api正确地区分了这一点:其中Jodatime是下一个(第三个!)Java DateTime API (JSR 310)。
明确关注点的体系结构分离——确切地知道哪个层与用户交互,并且必须更改规范表示(UTC)的日期/时间。非UTC日期时间是表示(遵循用户本地时区),UTC时间是模型(对于后端和中间层保持惟一)。
同时,决定你的真正受众是什么,你不需要提供什么,以及你的底线在哪里。除非你真的有重要的客户在那里,然后考虑为那个地区单独的面向用户的服务器,否则不要碰那些奇怪的日历。
如果可以获取和维护用户的位置,请使用位置进行系统的日期-时间转换(例如. net文化或SQL表),但如果日期-时间对用户至关重要,则为最终用户提供一种选择覆盖的方法。
如果涉及到历史审计义务(比如准确地告诉AZ的Jo在2年前的9月什么时候付款),那么将UTC和本地时间都记录下来(您的转换表将在一段时间内更改)。
定义大容量数据(如文件、web服务等)的时间引用时区。假设东海岸公司在CA中有数据中心——您需要询问并了解他们使用什么作为标准,而不是假设其中一个。
不要相信嵌入在日期-时间文本表示中的时区偏移量,也不要接受解析和跟踪它们。相反,总是要求必须显式定义时区和/或引用区域。你可以很容易地收到时间与PST偏移,但时间实际上是EST,因为这是客户端的参考时间和记录刚刚导出的服务器是在PST。
您需要了解Olson tz数据库,该数据库可以从ftp://elsie.nci.nih.gov/pub http://iana.org/time-zones/获得。它每年都会更新多次,以应对世界各地不同国家在冬季和夏季(标准时间和日光节约时间)之间经常在最后时刻发生的变化。2009年,最后一次发布是2009年;2010年是2010年;2011年是2011年;2012年5月底,发布日期为2012年2月。注意,在两个单独的归档(tzcode20xx .tar.gz和tzdata20xx .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的数据已经移动,部分原因是D Olson很快就要退休了,部分原因是对维护人员提起了版权侵权诉讼(现已被驳回)。时区数据库现在由IANA管理,IANA是互联网数字分配机构,在首页有一个"时区数据库"的链接。讨论邮件列表现在是
一般情况下,在存储的时间戳中包含本地时间偏移量(包括DST偏移量):如果稍后希望在原始时区(和DST设置)中显示时间戳,仅UTC是不够的。
请记住,偏移量并不总是整数小时数(例如,印度标准时间是UTC+05:30)。
例如,合适的格式是元组(unix时间,以分钟为单位偏移)或ISO 8601。
跨越"计算机时间"和"人的时间"的边界是一场噩梦。主要的一个原因是,对于时区和夏令时的规定没有某种标准。各国在任何时候都可以自由地更改时区和DST规则,而且他们确实这样做了。
有些国家,如以色列、巴西,每年都决定什么时候实行夏令时,所以不可能提前知道夏令时何时生效。其他国家则规定了DST生效的时间。其他国家并不完全使用DST。
时区不必与格林尼治标准时间完全不同。尼泊尔是+ 5.45。甚至还有+13的时区。这意味着:
1 2 3 | SUN 23:00 in Howland Island (-12) MON 11:00 GMT TUE 00:00 in Tonga (+13) |
都是同一时间,却有3天不同!
对于时区的缩写也没有明确的标准,以及在DST中它们是如何变化的,所以你会得到这样的结果:
1 2 3 | 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那样频繁地更新,因此仅仅使用PHPs时区转换可能会使您得到过时的DST信息,并影响数据的正确性。虽然这种情况不会经常发生,但如果您在全球拥有大量用户,那么这种情况可能会发生,而且将会发生。
为了解决上述问题,使用timezonedb pecl包,它的功能是更新PHP的时区数据。在更新这个包时,要尽可能频繁地安装它。(我不确定这个包的更新是否完全遵循Olson更新,但它的更新频率似乎至少非常接近Olson更新。)
如果您的设计可以适应它,那么请同时避免本地时间转换!
我知道对一些人来说这听起来可能有点疯狂,但是想想UX:用户处理的时间很近,相对的日期(今天、昨天、下周一)比绝对的日期(2010.09.17、9月17日星期五)要快。当你多想想,时区的准确性(DST)更重要是
通过这种方式,您可以将所有日期存储在UTC中,并在UTC中进行相对比较,只需向用户显示相对日期阈值之外的UTC日期。
这也可以应用于用户输入(但通常以一种更有限的方式)。对于用户来说,从只有{昨天、今天、明天、下周一、下周四}的下拉列表中进行选择要比使用日期选择器简单得多。日期选择器是表单填写中最容易引起疼痛的部分。当然,这并不适用于所有的情况,但是您可以看到,只需要一点巧妙的设计就可以使它非常强大。
虽然我还没有尝试过,但我觉得时区调整的方法很有吸引力,如下:
将所有内容存储在UTC中。
创建一个包含三个列的表
在表中,存储本地时间更改的日期和时间列表,以及更改了多少时间。表中区域的数量和日期的数量将取决于需要支持的日期范围和世界范围。把这看作是一个"历史"日期,即使这些日期应该在一定程度上包括将来。
当您需要计算任何UTC时间的本地时间时,只需这样做:
1 2 3 4 | SELECT DATEADD('m', SUM(OffsetMinutes), @inputdatetime) AS LocalDateTime FROM TZOffsets WHERE StartDateTime <= @inputdatetime AND RegionClassId = @RegionClassId; |
您可能希望在应用程序中缓存这个表,并使用LINQ或其他类似的方法来执行查询,而不是访问数据库。
这些数据可以从公共域tz数据库中提取。
这种方法的优点和脚注:
代码中不包含任何规则,您可以轻松地为新的区域或日期范围调整偏移量。您不必支持所有日期或区域,您可以根据需要添加它们。区域不必直接对应于地缘政治边界,并且为了避免重复的行(例如,美国的大多数州以相同的方式处理DST),您可以在另一个表中拥有广泛的RegionClass条目,这些条目链接到更传统的州、国家列表,等等。对于美国这样的情况,DST的开始和结束日期在过去几年发生了变化,这非常容易处理。由于StartDateTime字段也可以存储时间,所以可以轻松地处理凌晨2:00的标准转换时间。并不是世界上所有地方都使用1小时DST。这很容易处理这些情况。数据表是跨平台的,可以是一个单独的开放源码项目,可以由几乎使用任何数据库平台或编程语言的开发人员使用。这可以用于与时区无关的偏移量。例如,为适应地球自转而不时发生的1秒钟的调整、公历的历史调整等。由于这是在数据库表中,所以标准报告查询等可以利用数据,而无需遍历业务逻辑代码。如果您愿意,它还可以处理时区偏移,甚至可以考虑将一个区域分配给另一个时区的特殊历史情况。您所需要的只是一个初始日期,它为每个区域分配一个时区偏移量,并具有最小的开始日期。这将需要为每个时区创建至少一个区域,但这将允许您提出一些有趣的问题,比如:"1989年2月2日凌晨5点,亚利桑那州尤马市和华盛顿州西雅图市的当地时间有什么不同?"(用另一个和减去一个和)现在,这种方法或其他方法的唯一缺点是,从本地时间到GMT的转换并不完美,因为任何对时钟具有负偏移量的DST更改都会重复给定的本地时间。恐怕没有简单的方法来处理这个问题,这就是为什么存储本地时间首先是坏消息的原因之一。
我偶然发现了两种类型的系统,"轮班计划系统(例如工厂工人)"和"燃气依赖管理系统"……
每天工作23和25小时是一件痛苦的事情,8小时的轮班需要7或9小时。问题是,您会发现每个客户,甚至客户的部门都有不同的规则,它们创建了关于在这些特殊情况下他们所做的事情的不同规则(通常没有文档说明)。
有些问题最好不要问客户的,直到他们支付了你的"现成"软件。在购买软件时,很少有客户会预先考虑这类问题。
我认为在任何情况下,您都应该用UTC记录时间,并在存储日期/时间之前转换为/从本地时间。然而,即使知道哪个时间是在给定的时间可能是困难的日光节约和时区。
我最近在一个web应用程序中遇到了一个问题,在Ajax回发中,返回到服务器端代码的datetime与提供出去的datetime不一样。
它最有可能与我的JavaScript代码在客户端建立发布回客户机的日期字符串,因为JavaScript是调整时区和夏令时,和在一些浏览器应用日光节约时计算似乎比其他人不同。
最后,我选择完全删除客户机上的日期和时间计算,并以一个整数键将其返回到服务器,然后将其转换为服务器上的日期时间,以便实现一致的转换。
我从中学到的:除非迫不得已,否则不要在web应用程序中使用JavaScript计算日期和时间。
当涉及到在服务器上运行的应用程序时,包括web站点和其他后端服务,应用程序应该忽略服务器的时区设置。
常见的建议是将服务器的时区设置为UTC。这确实是一个很好的最佳实践,但是对于不遵循其他最佳实践的应用程序,它只是一个创可贴。例如,一个服务可能使用本地时间戳而不是基于utc的时间戳来编写日志文件,从而在夏令时回退转换期间产生歧义。将服务器的时区设置为UTC将修复该应用程序。然而,真正的解决方案是让应用程序首先使用UTC进行日志记录。
服务器端代码(包括web站点)永远不应该期望服务器的本地时区是特定的。
在某些语言中,本地时区很容易渗透到应用程序代码中。例如,. net中的
使用本地时区保留客户端代码,如桌面应用程序、移动应用程序和客户端JavaScript。
对于网络来说,规则并没有那么复杂……
服务器端,使用UTC客户端,使用奥尔森原因:UTC偏移量不是日光节约安全的(例如,纽约是一年中东部时间(UTC - 5小时)的一部分,东部时间(UTC - 4小时)的其余时间)。对于客户端时区的确定,您有两个选项:1)设置用户区域(更安全)资源:Web-ready Olson tz HTML下拉列表和JSON2)自动检测区资源:jsTimezoneDetect剩下的只是使用服务器端datetime库进行UTC/本地转换。好了……
将您的服务器设置为UTC,并确保它们都配置为ntp或同等级别。
UTC避免了日光节约时间的问题,而不同步的服务器可能会导致不可预测的结果,需要一段时间来诊断。
在处理存储在FAT32文件系统中的时间戳时要小心——它总是保存在本地时间坐标中(包括DST)(请参阅msdn文章)。在那上面烧伤了。
另一件事,确保服务器应用了最新的日光节约补丁。
去年我们遇到过这样的情况,我们的时间连续三周被北美用户挤出一个小时,即使我们使用的是基于UTC的系统。
结果是服务器出了问题。他们只是需要一个最新的补丁应用(Windows Server 2003)。
PHP的
输出
这个PHP方法返回一个包含一些"主要"时区(如CEST)的关联数组,这些时区本身包含更具体的"地理"时区(如欧洲/阿姆斯特丹)。
如果您正在使用这些时区和它们的偏移/DST信息,实现以下几点非常重要:
似乎每个时区的所有不同的偏移/DST配置(包括历史配置)都包括在内!
例如,欧洲/阿姆斯特丹可以在这个函数的输出中找到6次。有两次(偏移量1172/4772)是1937年以前使用的阿姆斯特丹时间;两个(1200/4800)是1937年至1940年间使用的;两个(3600/4800)用于1940年以来的时间。
因此,您不能依赖于此函数返回的偏移量/DST信息是否正确/正在使用!
如果你想知道某个时区的当前偏移量/DST,你必须这样做:
1 2 3 4 | <?php $now = new DateTime(null, new DateTimeZone('Europe/Amsterdam')); echo $now->getOffset(); ?> |
商业规则应该始终适用于民用时间(除非法律另有规定)。要知道文明时间是一团乱麻,但它是人们使用的,所以它是重要的。
在内部,将时间戳设置为类似于从纪元开始的民用时间秒。历元并不特别重要(我支持Unix历元),但是它确实使事情比其他选择更容易。假设闰秒不存在,除非您正在做一些真正需要它们的事情(例如,卫星跟踪)。时间戳与显示时间之间的映射是应用DST规则的惟一点;规则变化频繁(在全球范围内,每年数次;因此,您应该确保没有硬编码映射。奥尔森的TZ数据库是无价的。
如果您碰巧维护了运行DST活动的数据库系统,请仔细检查它们是否需要在秋季的转换期间关闭。Mandy DBS(或其他系统)不喜欢在(本地)时间内两次传递相同的点,这正是秋季时钟倒转时所发生的情况。SAP解决了这个问题,采用了一种(我认为非常简洁)的变通方法——他们没有让时钟倒转,而是让内部时钟以通常速度的一半运行两个小时……
您正在使用.NET框架吗?如果是这样,让我向您介绍DateTimeOffset类型,它是在. net 3.5中添加的。
这个结构包含一个
其他有用的类型还有TimeZone和TimeZoneInfo类。
对于那些在. net上与之斗争的人,看看使用
如果您想使用IANA/Olson时区,或者发现内置类型不足以满足您的需求,请查看Noda time,它为. net提供了更智能的日期和时间API。
我只想指出两件看起来不准确或者至少令人困惑的事情:
Always persist time according to a unified standard that is not
affected by daylight savings. GMT and UTC have been mentioned by
different people, though UTC seems to be mentioned most often.
对于(几乎)所有的实际计算目的,UTC实际上是GMT。除非您看到的时间戳是分数秒,否则您要处理的是GMT,这使得这种区别变得多余。
Include the local time offset as is (including DST offset) when
storing timestamps.
时间戳总是用GMT表示,因此没有偏移量。
这是我的经验
(不需要任何第三方库)
在服务器端,以UTC格式存储时间,以便数据库中的所有日期/时间值都在一个标准中,而不管用户、服务器、时区或DST的位置如何。在UI层或发送给用户的电子邮件中,您需要根据用户显示时间。为此,您需要有用户的时区偏移量,以便可以将该偏移量添加到数据库的UTC值中,从而得到用户的本地时间。您可以在用户注册时获取用户的时区偏移量,也可以在web和移动平台中自动检测它们。对于网站,JavaScript函数getTimezoneOffset()方法是1.0版以来的标准方法,兼容所有浏览器。(参考:http://www.w3schools.com/jsref/jsref_getTimezoneOffset.asp)Tom Scott在YouTube上的Computerphile频道上关于时区的视频也很好地描述了这个话题。例子包括:
萨摩亚(太平洋上的一个岛屿)将时区向前移动了24小时,以方便与澳大利亚和新西兰的贸易,约旦河西岸,那里有两个人生活在不同的时区,18世纪由儒略历改为公历日历(发生在20世纪的俄罗斯)。永远、永远不要在没有UTC偏移量(或对时区的引用)的情况下存储本地时间——在C.1中包括FAT32和
实际上,内核32.dll并不导出SystemTimeToTzSpecificLocation。但是它确实导出了以下两个:SystemTimeToTzSpecificLocalTime和TzSpecificLocalTimeToSystemTime…
永远不要只依赖像这样的构造函数
1 | new DateTime(int year, int month, int day, int hour, int minute, TimeZone timezone) |
当某个日期时间由于DST而不存在时,它们可以抛出异常。相反,构建您自己的方法来创建这样的日期。在其中,捕获由于DST而发生的任何异常,并使用转换偏移量调整所需的时间。根据时区的不同,DST可能发生在不同的日期和时间(甚至在巴西的午夜)。
只要举一个例子,就能证明处理时间是描述的巨大混乱,而且你永远不能自满。在本页的几个地方,跳过秒被忽略了。
几年前,Android操作系统使用GPS卫星获取UTC时间参考,但忽略了GPS卫星不使用闰秒的事实。没人注意到,直到除夕出现了混乱,苹果手机用户和安卓手机用户相隔约15秒倒数。
我想这个问题已经解决了,但你永远不知道这些"小细节"什么时候还会困扰你。
在处理数据库(特别是MySQL,但这适用于大多数数据库)时,我发现很难存储UTC。
默认情况下,数据库通常使用服务器datetime(即CURRENT_TIMESTAMP)。您可能无法更改服务器时区。即使能够更改时区,也可能有第三方代码希望服务器时区是本地的。我发现在数据库中存储服务器datetime更简单,然后让数据库将存储的datetime转换回UTC(即SQL语句中的UNIX_TIMESTAMP())。之后,可以在代码中使用datetime作为UTC。
如果您对服务器和所有代码都有100%的控制权,那么最好将服务器时区更改为UTC。