解析数据库中存储的日期值的模糊时间



在英国,时钟在2014年10月26日凌晨02:00时拨回1小时。在这种情况下,英国的每个人在那天都要观察两次01:00到01:59之间的时间。

假设我有一个。net软件应用程序,其中日期和时间在特定时区非常重要。在这种情况下,当我在数据存储系统中看到10月26日01:00时,我应该推断什么?我应该存储时区,如BST和GMT,还是应该存储偏移值?

我通常对在这种情况下该怎么做感到困惑(希望我不是唯一一个困惑的人:D)。原因是时区缩写可以有多种含义,如BST:

  • 孟加拉国标准时间(标准时间)
  • 英国夏令时/英国夏令时

对于这些情况,特别是在。net应用程序中(BCL DateTime值类型和NodaTime解决方案都将受到赞赏),最佳实践是什么?

编辑1

有一些关于在UTC中持久化的建议。但是,想象一下,我需要知道这个应用程序中的时区。一个例子是机票申请。你会从一个时区起飞,降落在另一个时区。即使我们可以将日期和时间转换为UTC并在附近存储时区,我仍然无法找到模糊时间的适当解决方案,这是这个问题的全部要点。下面的代码可以完美地工作(BCL值类型,而不是nodeatime):

static void ConvertToAndFromUtcCorrectly()
{
const string ukTimeZoneId = "GMT Standard Time";
TimeZoneInfo ukTimeZone = TimeZoneInfo.FindSystemTimeZoneById(ukTimeZoneId);
// 3/30/2014 2:00:00 AM (BST)
DateTime aDateTimeInDst = new DateTime(2014, 3, 30, 2, 0, 0);
// 3/30/2014 1:00:00 AM (UTC)
DateTime aDateTimeInDstUtc = TimeZoneInfo.ConvertTimeToUtc(aDateTimeInDst, ukTimeZone);
// 3/30/2014 2:00:00 AM (BST)
DateTime backToUkTime = TimeZoneInfo.ConvertTimeFromUtc(aDateTimeInDstUtc, ukTimeZone);
}

下面这个失败了:

static void ConvertToAndFromUtcWrongly()
{
const string ukTimeZoneId = "GMT Standard Time";
TimeZoneInfo ukTimeZone = TimeZoneInfo.FindSystemTimeZoneById(ukTimeZoneId);
// 10/26/2014 1:00:00 AM (This is meant to be BST, not GMT)
DateTime ambiguousUkDateTime = new DateTime(2014, 10, 26, 1, 0, 0);
// 10/26/2014 1:00:00 AM (UTC)
DateTime ambiguousUkDateTimeUtc = TimeZoneInfo.ConvertTimeToUtc(ambiguousUkDateTime, ukTimeZone);
// 10/26/2014 1:00:00 AM (WHAT?)
DateTime backToUkTime = TimeZoneInfo.ConvertTimeFromUtc(ambiguousUkDateTimeUtc, ukTimeZone);
}

编辑2

为了更具体地回答我的问题:根据我上面的例子,你如何在数据存储系统中存储2014年10月26日01:00 AM(这是一个模糊的时间),并在。net应用程序中读取/写入此数据?

如何存储时间在很大程度上取决于上下文。特别是,存储某事发生的时间(过去时或现在时)与存储某事将要或应该发生的时间(将来时)是不同的问题。

过去/现在

对于过去或现在事件,您可以在DateTimeOffset或基于utc的DateTime之间进行选择。虽然两者都代表一个特定的明确的时间点,但DateTimeOffset也将跟踪本地时间值。这对于了解时间戳记录的时间是早晨还是晚上这样的事情很有用。如果您不关心这些事情,请使用基于utc的DateTime。(更多关于DateTime vs DateTimeOffset)

"全局持久,局部显示"这句格言适用于过去/现在事件。您可以将时间戳转换为用户的本地时区,也可以将其转换为您想要的任何时区。当观看者的上下文不一定与记录时间的上下文相同时,这很有帮助。

以航空公司为例:

  • 您可以将航班从伦敦起飞的时间记录为2014-10-26T01:00:00+01:00DateTimeOffset。正如你所指出的,1点钟的时间在这一天重复,因为回落的过渡。但是由于我们记录了偏移量,我们知道这个时间是第一个出现的01:00。

  • 我们也可以将此时刻记录为2014-10-26T00:00:00Z的基于utc的DateTime。唯一丢失的信息是实际的当地时间。

  • 如果我们(单独地)知道数据起源于"Europe/London"时区(或者如果你使用TimeZoneInfo使用ID"GMT Standard Time"),那么我们可以肯定地说这个时间是在BST (UTC+01:00)而不是GMT (UTC+00:00)。

  • 假设飞行时间为7小时,降落在纽约。那就是2014-10-26T07:00:00Z。它也可以用2014-10-26T08:00:00+01:00DateTimeOffset来表示,但这个偏移量不适合纽约。因此,我们应用"America/New_York"的目标时区(或者使用TimeZoneInfo的ID"东部标准时间")并得到2014-10-26T03:00:00-04:00Z。你现在知道航班在纽约当地时间凌晨3点降落。

未来

未来的事件要复杂得多,原因如下:

  • 你不一定知道偏移量是多少。您只知道您认为将是什么,基于当前已知的时区规则。一些政府在很短的时间内就改变了他们的时区偏移量或夏令时规则,这并不总是给系统更新留出时间。在调度系统中,保持时区更新是绝对关键的,而且很容易被忽视。

  • 大多数人为驱动的事件不能由UTC调度-特别是反复出现的事件。想象一个每天早上7点叫醒你的闹钟。如果你用UTC来安排时间,那么在转换之后,它将在6:00或8:00开始起飞(取决于你什么时候进行了原始转换,以及你是在处理春季还是秋季转换)。

    • 有一个例外。如果事件遵循诸如"每x小时"(或更小)之类的规则,那么您可以按照UTC进行调度,而不会出现问题。不过要小心,像"周三每隔x个小时"这样的规则并不适用于这种例外,因为即使确定这是否是周三也涉及时区。

  • 最好的方法是按照事件发生的当地时间来安排未来的事件。为此,您可以使用具有DateTimeKind.UnspecifiedDateTime

  • 在闹钟示例中,时区并不重要,因为您将使用当前有效的本地时区(即TimeZoneInfo.Local)。但是在航空公司的例子中,您绝对需要出发地和目的地时区。您应该将时区的完整ID作为字符串存储在数据库中。请记住,TimeZoneInfo使用Windows id,像"Eastern Standard Time"这样的标识符表示EST和EDT。

  • 然后,您需要一种方法来消除由夏令时回退过渡产生的歧义,并处理由春季前向过渡产生的间隙。这些通常是通过算法或策略完成的。

    • 例如,在回退期间使用第一个事件(日光事件)并跳过由spring-forward间隙创建的任何无效值是正常的。

    • 在航空公司的例子中,可能需要存储一个额外的布尔值或位来表示它的调度方式。或者,你可能有一些算法,根据估计的飞行时间和预定的到达时间计算出一个模糊的出发时间。

    • 再想想人的因素。如果飞机计划在凌晨1点起飞,然后他们发现这是第二个实例,乘客会怎么做?他们可能不太乐意在机场坐着等。如果他们认为这是第二次,结果却是第一次,所以他们错过了航班,他们也不会高兴的。我大胆猜测,许多航空公司会希望避免将起飞时间安排为后备过渡,以防止不满意的客户。

回顾一下-考虑上下文。这真的很重要,没有什么事情是单向的。

您可以在应用程序中使用UTC时间。UTC时间没有夏令时,所以它是完全线性的。

通过将时间存储为UTC,您的应用程序将始终确切地知道时间的含义。您可以将时间转换为本地时间以显示给用户,这自然意味着当夏令时更改时,他们将看到时间上的间隙或重叠,但在幕后,您的应用程序仍然确切地知道时间是什么。

这是如何后退:

第一次出现的1:00 A.M.到1:59 A.M.仍然相同,而第二次出现的1:00 A.M.到1:59 A.M.可以被重新分配为13:00 P.M.到13:59 P.M.(准P.M.)。时间)为正常时间。在军事时间中,0100到0159的第二次出现可以重新分配为2400到2459。所以在常规时间,你从下午13:59到凌晨2:00,而在军事时间从2459到0200。

相关内容

  • 没有找到相关文章

最新更新