什么足以将来自多个时区的日期/时间存储在数据库中以进行准确计算?

Posted

技术标签:

【中文标题】什么足以将来自多个时区的日期/时间存储在数据库中以进行准确计算?【英文标题】:What is enough to store dates/times in the DB from multiple time zones for accurate calculations? 【发布时间】:2012-08-20 19:51:50 【问题描述】:

这是一个困难的问题。事实上,它似乎很难,SQL 标准和大多数主要数据库在它们的实现中都没有线索。

将所有日期时间转换为 UTC 可以方便地比较记录,但会丢弃时区信息,这意味着您不能对它们进行计算(例如,将 8 个月添加到存储的日期时间),也不能在它们所在的时区检索它们存储在里面。所以幼稚的方法已经过时了。

除了时间戳之外,存储与 UTC 的时区偏移量(例如,postgres 中带有时区的时间戳)似乎就足够了,但不同的时区可以在一年中的某个时间点具有相同的偏移量,而在 6 个月后又可以具有不同的偏移量由于夏令时。例如,您现在(8 月)可以在 UTC-4 拥有纽约和智利,但 11 月 4 日之后,纽约将是 UTC-5,智利(9 月 2 日之后)将是 UTC-3。因此,仅存储偏移量也不允许您进行准确的计算。像上面的幼稚方法一样,它也会丢弃信息。

如果您将时区标​​识符(例如 America/Santiago)与时间戳一起存储会怎样?这将允许您区分智利日期时间和纽约日期时间。但这还不够。如果您要存储到期日期,例如 6 个月后的午夜,并且 DST 规则发生变化(不幸的是政客们喜欢这样做),那么您的时间戳将是错误的,并且到期可能发生在晚上 11 点或凌晨 1 点。这对您的应用程序可能或可能不是什么大问题。所以使用时间戳也会丢弃信息。

似乎要真正准确,您需要使用时区标识符存储本地日期时间(例如,使用非时区感知时间戳类型)。为了支持更快的比较,您可以缓存它的 utc 版本,直到您使用的时区 db 更新,然后如果缓存值已更改,则更新它。因此,这将是 2 个简单的时间戳类型加上一个时区标识符和某种外部 cron 作业,用于检查时区数据库是否已更改并为缓存的时间戳运行适当的更新查询。

这是一个准确的解决方案吗?还是我还缺少什么?能不能做得更好?

我对 mysql、SQL Server、Oracle、PostgreSQL 和其他处理 TIMESTAMP WITH TIME ZONE 的 DBMS 的解决方案感兴趣。

【问题讨论】:

既然你提到了 SQL Server,我会指出新的(有点新,2008 年)DateTimeOffset 数据类型... 可能 让这更容易一点:msdn.microsoft.com/en-us/library/bb630289(v=sql.105).aspx总的来说,我认为您的方法是正确的。存储做出准确判断所需的所有信息……这通常意味着至少要有 UTC 和 TZ 信息。 @Tim UTC 时间戳是不够的,您还需要本地日期时间。从丹的评论“9 月 1 日,美国/纽约下午 1 点”将始终是下午 1 点。但是,如果它被转换为 UTC,并且夏令时规则发生了变化,你就失去了你的意思是纽约下午 1 点的信息(重读我的最后一段) @DanGrossman “9 月 1 日,1PM America/New_York”永远不会变成 12PM 或 2PM,无论 DST 做什么。 如果您将它们存储为 GMT/UTC 并单独命名时区那么这是不正确的。纽约的 DST 是 UTC-4 和 UTC-5 之间的差异。因此 12PM (UTC-4) 存储为 4PM UTC。但是如果 01-Sep 更改为 DST (出于政治原因)那么您存储的 4PM UTC 将被读取为 11AM(UCT-5)。这就是他的问题的重点。 @eloff - 这就是为什么我说“至少”......你可能需要存储更多,我认为你的方法是全面的。但是(从 Windows/.Net 的角度来看)如果您存储 TZ 和 UTC,您可以计算存储时的本地时间,即使规则在此期间发生了变化。时区信息类(同样,说的是 Windows/.Net)足够聪明,可以知道规则何时生效。其他平台应该有类似的库/逻辑,尽管很可能有例外情况(例如,Windows XP 的日期早于 2007 年有问题)。 @Tim 我想你已经在评论中回答了我的问题。深入研究 IANA 时区数据库,我可以看到它确实包含重建过去时间所需的信息。所以你是对的,UTC 时间戳和时区足以在正确的本地时间重建它们。未来的日期需要本地时间和时区(出于性能原因,可能需要缓存的 UTC 时间戳。) 【参考方案1】:

你已经很好地总结了这个问题。可悲的是,答案是按照您所描述的去做。

要使用的正确格式确实取决于时间戳应该代表什么的语用学。它通常可以分为过去和未来的事件(尽管有例外):

过去的事件可以而且通常应该被存储为永远不会被重新解释的东西。 (例如:带有数字时区的 UTC 时间戳)。如果应保留指定的时区(以向用户提供信息),则应将其分开。

未来的事件需要您描述的解决方案。本地时间戳和命名时区。这是因为您想在时区规则更改时更改该事件的“实际”(UTC) 时间。

我会质疑时区转换是否会产生这样的开销?它通常很快。如果您看到非常显着的性能影响,我只会经历缓存的痛苦。有(正如您所指出的)一些需要缓存的大型操作(例如根据实际(UTC)时间对数十亿行进行排序。

如果您出于性能原因需要在 UTC 中缓存未来的事件,那么可以,您需要建立一个流程来更新缓存的值。根据数据库的类型,这可能由系统管理员完成,因为 TZ 规则很少更改。

【讨论】:

嗯,我正在考虑常见的用途,例如 SELECT * FROM ... ORDER BY created_time。如果您必须根据时区标识符和本地时间戳为包含数十亿条记录的表中的每条记录执行一些临时转换,则无法有效地执行此操作。因此,缓存的 UTC 值对于在 DB 端有效地进行比较是必要的。 同意。与所有数据库一样,您需要先了解容量和使用的查询类型,然后才能进行性能调整。我的直觉是,简单地对十亿行进行排序将比添加翻译 TZ 的开销大得多。但是您和OP是对的;可能需要缓存。 是的,我的直觉说你的直觉是正确的,但是如果你有一个缓存 utc 值的索引,那么数据库可能能够使用索引按排序顺序返回行(取决于优化器的直觉认为这比排序更快。) @couling 你错了。时区规则在过去的时间不会改变。如果您更改“实际”(UTC)时间,您实际上是在更改时间(使其早于或晚于原来的时间)。突然间,与 tz 规则未更改的其他国家的其他记录相比,该记录的排序方式将有所不同。 @Eddy 时区规则会随着过去的时间而改变,如果有人迟到了补丁。我不是在这里轻率,我已经看到它发生了。【参考方案2】:

如果您关心偏移量,则应存储实际偏移量。存储时区标识符与时区可以并且确实随时间变化不同。通过存储时区偏移量,您可以计算出事件发生时正确的本地时间,而不是根据当前偏移量计算本地时间。如果了解实际发生的时区事件很重要,您可能仍需要存储时区标识符。

请记住,时间是一种物理属性,但时区是一种政治属性。

【讨论】:

您刚刚给出了我的论点的反面,即偏移量不够,您需要时区标识符。偏移量不够,看我的解释。但我不确定你的论点是否时区标识符也不够。请记住,在我的解决方案中,我们存储本地时间,并且引用 Dan Grossman 的话:“September 1st, 1PM America/New_York”无论 DST 做什么,都永远不会变成 12PM 或 2PM。但是用于比较的缓存 UTC 时间戳呢,当 DST 规则改变时,过去日期的相对排序顺序会改变吗? 他们确实改变了,请参阅 couling 的回答。【参考方案3】:

如果您转换为 UTC,您可以订购和比较记录 如果您添加时区的名称,您可以用它的原始 tz 来表示它,并能够添加/减去时间段,如周、月等(而不是经过的时间)。

在您的问题中,您说这还不够,因为 DST 可能会更改。 DST 使计算日期(除了经过的时间)变得复杂且代码密集。就像您需要代码来处理闰年一样,您需要考虑对于给定的数据/期间是否需要应用 DST 校正。几年来,答案是肯定的,而其他年份的答案是否定的。 请参阅this wiki page,了解这些规则变得多么复杂。

存储偏移量基本上是存储这些计算的结果。计算出的偏移那个给定的时间点有效,并且不能按原样应用到您建议的稍后或更早的时间点你的问题。您对 UTC 时间进行计算,然后根据当时在该时区中的活动规则将结果时间转换为所需的时区。

请注意,在第一次世界大战之前,任何地方都没有任何 DST,并且数据库中的日期/时间系统可以完美地处理这些情况。

【讨论】:

【参考方案4】:

我对 MySQL、SQL Server、Oracle、PostgreSQL 和其他处理 TIMESTAMP WITH TIME ZONE 的 DBMS 的解决方案感兴趣。

Oracle 会即时转换为 UTC,但会根据您传递的内容保留时区或 UTC 偏移量。 Oracle(正确地)在时区和 UTC 偏移之间产生差异,并返回您传递给您的内容。这只需要额外的两个字节。

Oracle 在 UTC 中对 TIMESTAMP WITH TIME ZONE 进行所有计算。这对添加月份没有影响,但对添加天数有影响,因为没有夏令时。请注意,计算结果必须始终是有效的时间戳,例如将一个月添加到 1 月 31 日将在 Oracle 中引发异常,因为 2 月 31 日不存在。

【讨论】:

以上是关于什么足以将来自多个时区的日期/时间存储在数据库中以进行准确计算?的主要内容,如果未能解决你的问题,请参考以下文章

如何在存储过程 MySql 中设置默认时区

在存储日期和时间的应用程序中如何处理多个时区?

AngularJS,如何将多个输入组合成一个 ng 模型?具体来说,将日期、时间和时区输入组合到 datetime 对象中

使用带时区的时间戳提取日期

使用 pytz 将日期时间从一个时区转换为另一个时区

如何使用 JPA 和 Hibernate 在 UTC 时区中存储日期/时间和时间戳