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

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

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

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

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

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

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

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

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


当前回答

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

以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变化都会重复给定的本地时间。恐怕没有简单的方法来处理这个问题,这也是存储当地时间首先是坏消息的原因之一。

其他回答

在处理存储在FAT32文件系统中的时间戳时要小心——它总是保存在本地时间坐标中(包括DST——请参阅msdn文章)。被那一块烧伤了。

PHP的DateTimeZone::listAbstrations()输出

此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();
?>

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

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

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

如果涉及历史审计义务(例如准确地告诉亚利桑那州的Jo在2年前的9月支付账单的时间),则记录UTC和当地时间(您的转换表会随着时间的推移而改变)。

为批量提供的数据(如文件、web服务等)定义时间参考时区。如果东海岸公司在加利福尼亚州设有数据中心,您需要询问并了解他们使用什么作为标准,而不是假设其中之一。

不要相信嵌入在日期时间文本表示中的时区偏移,也不要接受解析和跟踪它们。相反,始终要求明确定义时区和/或参考区域。你可以很容易地用PST偏移量接收时间,但时间实际上是EST,因为这是客户端的参考时间,记录只是在PST中的服务器上导出的。

对于PHP:

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

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

要解决上述问题,请使用timezonedb pecl包。它的功能是更新PHP的时区数据。根据更新的频率安装此软件包。(我不确定这个软件包的更新是否完全遵循奥尔森的更新,但它的更新频率似乎至少非常接近奥尔森更新的频率。)

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

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

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

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