Datetime 保存日期时间的最佳实践&;当数据依赖于日期时间时,数据库中的时区信息

Datetime 保存日期时间的最佳实践&;当数据依赖于日期时间时,数据库中的时区信息,datetime,timezone,utc,dst,datetimeoffset,Datetime,Timezone,Utc,Dst,Datetimeoffset,关于在DB中保存datetime&timezones信息有很多问题,但在总体层面上有更多问题。这里我想谈谈一个具体的案例 系统规格 我们有一个订单系统数据库 这是一个多租户系统,租户可以使用任意时区(它是任意的,但每个租户只有一个时区,保存在租户表中一次,从不更改) 业务规则需要包含在DB中 当租户向系统下订单时,订单号将根据其本地日期时间计算得出(它不是字面上的数字,而是某种标识符,如ORDR-13432-Year-Month-Day)。精确的计算目前并不重要,重要的是它取决于租户的本地

关于在DB中保存datetime&timezones信息有很多问题,但在总体层面上有更多问题。这里我想谈谈一个具体的案例

系统规格

  • 我们有一个订单系统数据库
  • 这是一个多租户系统,租户可以使用任意时区(它是任意的,但每个租户只有一个时区,保存在租户表中一次,从不更改)
业务规则需要包含在DB中

  • 当租户向系统下订单时,订单号将根据其本地日期时间计算得出(它不是字面上的数字,而是某种标识符,如
    ORDR-13432-Year-Month-Day
    )。精确的计算目前并不重要,重要的是它取决于租户的本地日期和时间
  • 我们还希望能够在系统级别上选择在某些UTC日期时间之间下的所有订单,而不考虑租户(对于一般系统统计/报告)
我们最初的想法

  • 我们最初的想法是在整个数据库中保存UTC日期时间,当然,保留租户相对于UTC的时区偏移,并让使用数据库的应用程序始终将日期时间转换为UTC,以便数据库本身始终使用UTC运行
方法1

  • 保存本地租户datetime对每个租户都很好,但是我们遇到了以下问题:

    SELECT * FROM ORDERS WHERE OrderDateTime BETWEEN UTCDateTime1 AND UTCDateTime2
    
这是有问题的,因为在这个查询中,
OrderDateTime
表示不同的时间点,具体取决于租户。当然,这个查询可能包括join to
Tenants
表,以获取本地日期时间偏移量,然后动态计算
OrderDateTime
,以进行调整。这是可能的,但不确定这是否是一个好方法

方法2

  • 另一方面,在保存UTC datetime时,由于UTC中的日/月/年可能不同于本地datetime中的日/月/年,因此当我们计算订单号时
让我们举一个极端的例子;假设承租人比UTC早6小时,其本地日期时间为2017-01-01 02:00。 UTC将是
2016-12-31 20:00
。此时下的订单应获得订单号“ORDR-13432-2017-1-1”,但如果保存UTC,则将获得
ORDR-13432-2016-12-31

在这种情况下,在DB中创建订单时,我们应该获取UTC日期时间、租户偏移量,并基于重新计算的租户本地时间编译OrderNumber,但仍然以UTC保存日期时间列

问题

  • 处理这种情况的首选方法是什么
  • 是否有一个很好的解决方案可以保存UTC日期时间,因为系统级报告对我们来说非常好
  • 如果采用节约UTC,方法2)是处理这些情况的好方法还是有更好/推荐的方法
  • [更新]

    根据Gerard Ashton和Hugo的评论:

    最初的问题不清楚承租人是否可以更改时区,以及如果政治当局更改时区属性或某个地区的时区会发生什么。 当然,这是一个极端重要的问题,但它不是这个问题的核心。我们可以在另一个问题中解决这个问题


    为了解决这个问题,我们假设承租人不会改变位置。该位置的时区属性或时区本身可能会更改,这些更改将在系统中与此问题分开处理。

    我建议始终在内部使用UTC,并且仅在向用户显示日期时转换为时区。所以我倾向于选择方法2

    如果有一条业务规则规定承租人的本地日期/时间必须是标识符的一部分,那么就这样吧。但在内部,您将订单日期保留为UTC

    以您的示例为例:其时区为
    UTC+06:00
    ,因此承租人的本地时间为
    2017-01-01 02:00
    ,相当于UTC中的
    2016-12-31 20:00

    订单标识符将为
    ORDR-13432-2017-1-1
    ,订单日期将为UTC
    2016-12-31 20:00Z

    要获取两个日期之间的所有订单,此查询是严格向前的:

    从OrderDateTime介于UTCDateTime1和UTCDateTime2之间的订单中选择*
    
    因为
    OrderDateTime
    是UTC格式

    如果要查找特定的租户,则可以获取相应的时区,相应地转换日期并搜索它。使用上述同一示例(承租人的时区为
    UTC+06:00
    ),以获取在
    2017-01-01
    (承租人当地时间)内发出的所有订单:

    ——获取租户时区
    --startUTC=承租人的本地2017-01-01 00:00转换为UTC(2016-12-31T18:00Z)
    --endUTC=承租人的本地2017-01-01 23:59:59.999转换为UTC(2017-01-01T17:59:59.999)
    从ORDERS中选择*,其中OrderDateTime介于startUTC和endUTC之间
    
    这将正确获取
    ORDR-13432-2017-1-1


    要在不同时区查询多个租户,这两种方法都需要联接,因此在这种情况下没有更好的方法


    除非您使用租户的本地日期/时间创建一个额外的列(UTC
    OrderDateTime
    转换为租户的时区)。这将是多余的,但它可以帮助您处理在多个时区中搜索的查询。如果这是一个合理的权衡,那将取决于这些查询的频率。

    雨果的回答基本上是正确的,但我要补充几点:

    • 存储客户的时区时,不要存储数字偏移。正如其他人指出的,与UTC的偏移量仅适用于单个点
      ... WHERE OrderDateTime >= @t1 AND OrderDateTime < @t2