关于在数据库中保存日期时间和时区信息有很多问题,但更多的是总体水平。这里我想讲一个具体的案例。
系统规格
- 我们有一个订单系统数据库
- 它是一个多租户系统,租户可以使用任意时区(它是任意的,但每个租户只有一个时区,保存在租户表中一次并且永不更改)
DB需要覆盖业务规则
- 当租户向系统下订单时,订单号根据本地日期时间计算(它实际上不是一个数字,而是某种标识符,例如
ORDR-13432-Year-Month-Day
)。目前精确计算并不重要,重要的是它取决于租户本地日期时间
- 我们还希望能够在系统级别选择放置在某些 UTC 日期时间之间的所有订单,而不管租户如何(用于一般系统统计/报告)
我们最初的想法
- 我们最初的想法是在整个数据库中保存 UTC 日期时间,当然,保持租户相对于 UTC 的时区偏移,并让使用数据库的应用程序始终将日期时间转换为 UTC,以便数据库本身始终使用 UTC 运行。
方法一
这是有问题的,因为OrderDateTime
此查询中的意思是根据租户的不同时刻。当然,这个查询可能包括 join toTenants
表获取本地日期时间偏移量,然后计算OrderDateTime
即时进行调整。这是可能的,但不确定这是否是一个好方法?
方法2
- 另一方面,当保存 UTC 日期时间时,当我们计算 OrderNumber 时,因为 UTC 中的日/月/年可能与本地日期时间中的不同
让我们举个极端的例子;假设租户比 UTC 早 6 小时,他的本地日期时间是2017-01-01 02:00
。
世界标准时间将是2016-12-31 20:00
。此时下的订单应获取 OrderNumber'ORDR-13432-2017-1-1'
但如果保存 UTC 则会得到ORDR-13432-2016-12-31
.
在这种情况下,在数据库中创建订单时,我们应该获取 UTC 日期时间、租户偏移量,并根据重新计算的租户本地时间编译 OrderNumber,但仍以 UTC 保存 DateTime 列。
问题
- 处理这种情况的首选方法是什么?
- 是否有一个很好的解决方案来保存 UTC 日期时间,因为由于系统级报告,该解决方案对我们来说非常好?
- 如果要保存 UTC,方法 2) 是处理这些情况的好方法还是有更好/推荐的方法?
[UPDATE]
基于 Gerard Ashton 和 Hugo 的评论:
最初的问题不清楚租户是否可以更改时区以及如果政治当局更改时区属性或某些领土的时区会发生什么。
当然,这是极其重要的,但这不是这个问题的中心。我们可以在一个单独的问题中解决这个问题。
为了回答这个问题,我们假设租户不会改变位置。该位置的时区属性或时区本身可能会发生变化,这些变化将在系统中与该问题分开处理。
Hugo的回答大部分是正确的,但我会补充几个要点:
当您存储客户的时区时,请勿存储数字偏移量。正如其他人指出的那样,与 UTC 的偏移仅适用于单个时间点,并且很容易因 DST 和其他原因而更改。相反,您应该存储时区标识符,最好是 IANA 时区标识符作为字符串,例如"America/Los_Angeles"
。阅读更多内容时区标签 wiki https://stackoverflow.com/tags/timezone/info.
-
Your OrderDateTime
字段应该绝对代表 UTC 时间。但是,根据您的数据库平台,您可以选择多种存储方式。
例如,如果使用 Microsoft SQL Server,一个好的方法是将本地时间存储在datetimeoffset
列,保留与 UTC 的偏移量。请注意,您在该列上创建的任何索引都将基于 UTC 等效值,因此在执行范围查询时您将获得良好的查询性能。
如果使用其他数据库平台,您可能希望将 UTC 值存储在timestamp
场地。有些数据库还有timestamp with time zone
,但要明白这并不意味着stores时区或偏移量,它只是意味着它可以在您存储和检索值时隐式为您进行转换。如果您打算始终代表 UTC,那么通常timestamp
(没有时区)或只是datetime
更合适。
-
由于上述任一方法都将存储 UTC 时间,因此您还需要考虑如何执行需要本地时间值索引的操作。例如,您可能需要根据用户所在时区的日期创建每日报告。为此,您需要按本地日期进行分组。如果您尝试在查询时根据 UTC 值计算该值,您最终将扫描整个表。
处理这个问题的一个好方法是为本地创建一个单独的列date
(或者甚至可能是当地的datetime
根据您的需要,但是not a datetimeoffset
or timestamp
)。这可以是您单独填充的完全隔离的列,也可以是基于其他列的计算/计算列。在索引中使用此列,以便您可以按本地日期进行筛选或分组。
-
如果您采用计算列方法,您将需要知道如何在数据库中的时区之间进行转换。有些数据库有一个convert_tz
内置函数可以理解 IANA 时区标识符。
如果您使用的是 Microsoft SQL Server,则可以使用新的AT TIME ZONE https://learn.microsoft.com/en-us/sql/t-sql/queries/at-time-zone-transact-sqlSQL 2016 和 Azure SQL DB 中的函数,但仅适用于 Microsoft 时区标识符。要使用 IANA 时区标识符,您需要第三方解决方案,例如我的SQL Server 时区支持 https://github.com/mj1856/SqlServerTimeZoneSupport项目。
-
在查询时,避免使用BETWEEN
陈述。它是完全包容的。它适用于整个日期,但是当您有时间参与时,最好进行半开范围查询,例如:
... WHERE OrderDateTime >= @t1 AND OrderDateTime < @t2
例如,如果@t1
是今天的开始,@t2
将是明天的开始。
关于评论中讨论的用户时区已更改的场景:
-
如果您选择计算数据库中的本地日期,则您需要担心的唯一情况是位置或企业是否在没有发生“区域分割”的情况下切换时区。区域分割是指引入新的时区标识符,该标识符覆盖已更改的区域,包括其旧规则和新规则。
例如,在撰写本文时添加到 IANA tzdb 的最新区域是America/Punta_Arenas
,当智利南部决定保留 UTC-3 而智利其他地区 (America/Santiago
) 在 DST 结束时返回 UTC-4。
但是,如果两个时区边界上的一个小地区决定更改它们所遵循的一侧,并且区域分割不合理,那么您可能会针对旧数据使用新时区的规则。
如果您单独存储本地日期(在应用程序中计算,而不是在数据库中计算),那么您不会有任何问题。用户将时区更改为新时区,所有旧数据仍然完好无损,并且新数据与新时区一起存储。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)