协慌网

登录 贡献 社区

夏令时和时区最佳实践

我希望能够将这个问题及其答案作为处理夏令时的权威指南,特别是处理实际的变更问题。

如果您有任何要添加的内容,请执行此操作

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

例如,一个订单系统中的业务规则取决于订单的时间 - 如果时钟发生变化,规则可能不那么明确。如何保持订单的时间?当然有无数的场景 - 这只是一个说明性的场景。

  • 你是如何处理夏令时问题的?
  • 您的解决方案中有哪些假设? (在这里寻找背景)

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

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

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

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

答案


Summary of answers and other data: (please add yours)

Do:

  • Whenever you are referring to an exact moment in time, persist the time according to a unified standard that is not affected by daylight savings. (GMT and UTC are equivalent with this regard, but it is preferred to use the term UTC. Notice that UTC is also known as Zulu or Z time.)
  • If instead you choose to persist a time using a local time value, include the local time offset for this particular time from UTC (this offset may change throughout the year), such that the timestamp can later be interpreted unambiguously.
  • In some cases, you may need to store both the UTC time and the equivalent local time. Often this is done with two separate fields, but some platforms support a datetimeoffset type that can store both in a single field.
  • When storing timestamps as a numeric value, use Unix time - which is the number of whole seconds since 1970-01-01T00:00:00Z (excluding leap seconds). If you require higher precision, use milliseconds instead. This value should always be based on UTC, without any time zone adjustment.
  • If you might later need to modify the timestamp, include the original time zone ID so you can determine if the offset may have changed from the original value recorded.
  • When scheduling future events, usually local time is preferred instead of UTC, as it is common for the offset to change. See answer, and blog post.
  • When storing whole dates, such as birthdays and anniversaries, do not convert to UTC or any other time zone.
    • When possible, store in a date-only data type that does not include a time of day.
    • If such a type is not available, be sure to always ignore the time-of-day when interpreting the value. If you cannot be assured that the time-of-day will be ignored, choose 12:00 Noon, rather than 00:00 Midnight as a more safe representative time on that day.
  • Remember that time zone offsets are not always an integer number of hours (for example, Indian Standard Time is UTC+05:30, and Nepal uses UTC+05:45).
  • If using Java, use java.time for Java 8, or use Joda Time for Java 7 or lower.
  • If using .NET, consider using Noda Time.
  • If using .NET without Noda Time, consider that DateTimeOffset is often a better choice than DateTime.
  • If using Perl, use DateTime.
  • If using Python, use pytz or dateutil.
  • If using JavaScript, use moment.js with the moment-timezone extension.
  • If using PHP > 5.2, use the native time zones conversions provided by DateTime, and DateTimeZone classes. Be careful when using DateTimeZone::listAbbreviations() - see answer. To keep PHP with up to date Olson data, install periodically the timezonedb PECL package; see answer.
  • If using C++, be sure to use a library that uses the properly implements the IANA timezone database. These include cctz, ICU, and Howard Hinnant's"tz" library.
    • Do not use Boost for time zone conversions. While its API claims to support standard IANA (aka"zoneinfo") identifiers, it crudely maps them to POSIX-style data, without considering the rich history of changes each zone may have had. (Also, the file has fallen out of maintenance.)
  • If using Rust, use chrono.
  • Most business rules use civil time, rather than UTC or GMT. Therefore, plan to convert UTC timestamps to a local time zone before applying application logic.
  • Remember that time zones and offsets are not fixed and may change. For instance, historically US and UK used the same dates to 'spring forward' and 'fall back'. However, in 2007 the US changed the dates that the clocks get changed on. This now means that for 48 weeks of the year the difference between London time and New York time is 5 hours and for 4 weeks (3 in the spring, 1 in the autumn) it is 4 hours. Be aware of items like this in any calculations that involve multiple zones.
  • Consider the type of time (actual event time, broadcast time, relative time, historical time, recurring time) what elements (timestamp, time zone offset and time zone name) you need to store for correct retrieval - see "Types of Time" in this answer.
  • Keep your OS, database and application tzdata files in sync, between themselves and the rest of the world.
  • On servers, set hardware clocks and OS clocks to UTC rather than a local time zone.
  • Regardless of the previous bullet point, server-side code, including web sites, should never expect the local time zone of the server to be anything in particular. see answer.
  • Prefer working with time zones on a case-by-case basis in your application code, rather than globally through config file settings or defaults.
  • Use NTP services on all servers.
  • If using FAT32, remember that timestamps are stored in local time, not UTC.
  • When dealing with recurring events (weekly TV show, for example), remember that the time changes with DST and will be different across time zones.
  • Always query date-time values as lower-bound inclusive, upper-bound exclusive (>=, <).

Don't:

  • Do not confuse a "time zone", such as America/New_York with a "time zone offset", such as -05:00. They are two different things. See the timezone tag wiki.
  • Do not use JavaScript's Date object to perform date and time calculations in older web browsers, as ECMAScript 5.1 and lower has a design flaw that may use daylight saving time incorrectly. (This was fixed in ECMAScript 6 / 2015).
  • Never trust the client's clock. It may very well be incorrect.
  • Don't tell people to"always use UTC everywhere". This widespread advice is shortsighted of several valid scenarios that are described earlier in this document. Instead, use the appropriate time reference for the data you are working with. (Timestamping can use UTC, but future time scheduling and date-only values should not.)

Testing:

  • When testing, make sure you test countries in the Western, Eastern, Northern and Southern hemispheres (in fact in each quarter of the globe, so 4 regions), with both DST in progress and not (gives 8), and a country that does not use DST (another 4 to cover all regions, making 12 in total).
  • Test transition of DST, i.e. when you are currently in summer time, select a time value from winter.
  • Test boundary cases, such as a timezone that is UTC+12, with DST, making the local time UTC+13 in summer and even places that are UTC+13 in winter
  • Test all third-party libraries and applications and make sure they handle time zone data correctly.
  • Test half-hour time zones, at least.

Reference:

其他:

  • 大厅让你的代表结束美国夏令时的憎恶。我们总能希望......
  • 地球标准时间的大厅

我不确定我可以添加到上面的答案,但这里有几点我:

时代的类型

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

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

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

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

存放时间

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

偏移和名称

以上的一个例子是:

足球世界杯决赛比赛于 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 格式无法准确表示此时区。

好的,我想我已经完成了。

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

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

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

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

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

约翰在他的日历中记录了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)的基础。