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

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

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

例如,一个订单接受系统中的业务规则取决于订单的时间——如果时钟发生变化,规则可能就不那么清晰了。应如何保持订单的时间?当然,有无数的场景——这只是一个例证。

你是如何处理日光节约问题的?您的解决方案包含哪些假设?(在此处查找上下文)

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

你尝试了什么但没有奏效?为什么不起作用?

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

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


当前回答

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

其他回答

虽然我没有尝试过,但我认为时区调整的方法很有说服力,如下所示:

以UTC格式存储所有内容。创建具有三列的表TZOffsets:RegionClassId、StartDateTime和OffsetMinutes(int,以分钟为单位)。

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

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

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

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

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

这种方法的优点和脚注:

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

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

我不知道我能为上面的答案补充什么,但我有几点:

时间类型

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

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

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

在UTC中存储开始/结束时间戳效果良好。对于1,您需要与事件一起存储事件时区名称+偏移量。对于2,您需要为每个区域存储一个本地时间标识符,并为每个观众存储一个当地时区名称+偏移量(如果您遇到困难,可以从IP中导出)。对于3,以UTC秒存储,不需要时区。4是1或2的特殊情况,这取决于它是全局事件还是本地事件,但您还需要存储在时间戳创建的,以便您可以判断在创建此事件之前或之后是否更改了时区定义。如果需要显示历史数据,这是必要的。

存储时间

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

偏移和名称

上述示例如下:

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

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

系统时间

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

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

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

客户

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

琐事

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

荷兰的标准时间是正好19分32.13秒在1909-05-01年法律规定的UTC之前通过1937-06-30。此时区不能使用HH:MM格式。

好的,我想我完了。

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

23小时和25小时的工作日是一个很难应付的问题,8小时的轮班需要7小时或9小时。问题是,你会发现每个客户,甚至客户的部门都有他们在这些特殊情况下创建的不同规则(通常没有记录)。

有些问题最好不要问客户,直到他们为您的“现成”软件支付了费用。很少有客户在购买软件时会提前考虑这类问题。

我认为在任何情况下,您都应该使用UTC记录时间,并在存储日期/时间之前转换为当地时间。然而,即使知道一个给定的时间是在哪,也很难使用夏令时和时区。

永远不要只依赖像这样的构造函数

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

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

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

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

时间的“物理”瞬间是物理学处理的连续宇宙时间线中的一个点(当然忽略了相对论)。例如,如果可以忽略闰秒,则可以在UTC中对这个概念进行适当的编码。

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

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

约翰在日历上记录了某个日期的事件提醒2019-Jul-27,10:30:00,TZ=智利/圣地亚哥,(已偏移GMT-4,因此它对应于UTC 2019-Jul-27 14:30:00)。但总有一天未来,该国决定将TZ偏移量改为GMT-5。

现在,当这一天到来。。。该提醒是否应在

A) 2019-Jul-27 10:30:00智利/圣地亚哥=UTC时间2019-Jur-27 15:30:00?

or

B) 2019-Jul-27 9:3:00智利/圣地亚哥=UTC时间2019-Jul-27 14:30:00?

没有正确的答案,除非你知道约翰在概念上的意思当他告诉日历“请在2019年7月27日10:30:00给我打电话TZ=智利/圣地亚哥”。

他是说“公民约会时间”吗10:30“)?在这种情况下,A)是正确答案。

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

一些Date/Time API正确地实现了这一区别:其中的Jodatime是下一个(第三个!)Java DateTime API(JSR 310)的基础。