【问题标题】:Storing date/times as UTC in database将日期/时间作为 UTC 存储在数据库中
【发布时间】:2011-02-04 13:07:26
【问题描述】:

我将日期/时间以 UTC 格式存储在数据库中,并在我的应用程序中根据特定时区将它们计算回本地时间。例如,我有以下日期/时间:

01/04/2010 00:00

说它是针对一个国家/地区的,例如英国遵守 DST(夏令时),在这个特定时间我们处于夏令时。当我将此日期转换为 UTC 并将其存储在数据库中时,它实际上存储为:

31/03/2010 23:00

因为 DST 的日期将调整为 -1 小时。当您在提交时观察 DST 时,这可以正常工作。但是,当时钟调回时会发生什么?当我从数据库中提取该日期并将其转换为本地时间时,该特定日期时间将被视为31/03/2010 23:00,而实际上它被处理为01/04/2010 00:00

如果我错了,请纠正我,但在将时间存储为 UTC 时这不是一个缺陷吗?

时区转换示例

基本上我正在做的是存储信息提交到我的系统的日期/时间,以便允许用户进行范围报告。这是我存储日期/时间的方式:

public DateTime LocalDateTime(string timeZoneId)
{
    var tzi = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId);
    return TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tzi).ToUniversalTime().ToLocalTime(); 
}

存储为 UTC:

var localDateTime = LocalDateTime("AUS Eastern Standard Time");
WriteToDB(localDateTime.ToUniversalTime());

【问题讨论】:

标签: c# datetime utc timezone


【解决方案1】:

您不会根据您是否当前观察 DST 更改日期 - 您根据是否在您描述的那一刻观察到 DST 来调整日期 /em>。因此,在 1 月份的情况下,您不会应用调整。

一个问题,但是 - 一些当地时间是模棱两可的。例如,英国 2010 年 10 月 31 日凌晨 1:30 可以表示 UTC 01:30 或 UTC 02:30,因为时钟从凌晨 2 点回到凌晨 1 点。您可以从以 UTC 表示的任何 即时 获取将在该时刻显示的本地时间,但该操作是不可逆的。

同样,您很可能有一个永远不会出现的当地时间 - 例如,2010 年 3 月 28 日凌晨 1:30 在英国没有出现 - 因为在凌晨 1 点时钟会向前跳到凌晨 2 点。

总而言之,如果您想及时表示一个瞬间,您可以使用 UTC 并获得一个明确的表示。如果您尝试表示特定时区的时间,则需要时区本身(例如欧洲/伦敦)以及即时的 UTC 表示或本地日期和时间以及该特定时间的偏移量(以消除 DST 转换的歧义)。另一种选择是存储UTC和它的偏移量;这使您可以在那一刻告诉当地时间,但这意味着您无法预测一分钟后的当地时间,因为您并不真正知道时区。 (这基本上是DateTimeOffset 存储的内容。)

我们希望在Noda Time 中让这件事变得相当容易处理,但您仍然需要意识到这是一种可能性。

编辑:

您显示的代码不正确。这就是为什么。我更改了代码的结构以使其更易于查看,但您会看到它正在执行相同的调用。

var tzi = TimeZoneInfo.FindSystemTimeZoneById("AUS Eastern Standard Time");
var aussieTime = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tzi);
var serverLocalTime = aussieTime.ToLocalTime(); 
var utcTime = serverLocalTime.ToUniversalTime();

所以,让我们现在考虑一下 - 我的当地时间是 13:38(伦敦时间 UTC+1),UTC 时间 12:38,悉尼时间 22:39。

您的代码将给出:

aussieTime = 22:39 (correct)
serverLocalTime = 23:39 (*not* correct)
utcTime = 22:39 (*not* correct)

您应该TimeZoneInfo.ConvertTimeFromUtc 的结果上调用ToLocalTime - 它会假定它是在UTC DateTime 上调用的(除非它实际上有一种DateTimeKind.Local,在这种情况下不会)。

因此,如果您在这种情况下准确地保存了 22:39,您并没有准确地保存 UTC 中的当前时间。

【讨论】:

  • 对不起,我的问题有点仓促!我的意思是,如果您使用 ConvertTimeFromUtc 转换时间,它会自动针对 DST 进行调整。但是,由于我将 DateTimes 作为 UTC 存储在数据库中,它会删除调整(如有必要)。因此,当我在未来退出这个日期时间时(当我们不再处于观察 DST 的时间时),日期时间不会重新调整回来......如果这有意义吗?我认为存储偏移量将是最好的解决方案,然后我确定日期是否需要调整。
  • @James:不,它不是那样工作的。是否应用 DST 取决于转换的日期,而不是当前日期。因此,“当我们不再处于遵守 DST 的时代时”是无关紧要的。
  • 啊,好吧,所以如果我把那个特定的日期拉出来并进行转换,它仍然应该按预期计算?
  • @James:是的,如果您准确地将其存储为 UTC,那么您应该能够在任何其他时区准确地显示它。
  • @Jon,我正在做的是总是通过ConvertFromTimeUtcToLocalTime 将当前日期/时间转换为本地时间(针对该特定时区)。然后当我将它存储在数据库中时,我使用ToUniversalTime 方法将时间转换为UTC。
【解决方案2】:

TimeZoneInfo.ConvertTimeFromUtc() 方法将解决您的问题:

using System;

class Program {
  static void Main(string[] args) {
    DateTime dt1 = new DateTime(2009, 12, 31, 23, 0, 0, DateTimeKind.Utc);
    TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");
    Console.WriteLine(TimeZoneInfo.ConvertTimeFromUtc(dt1, tz));
    DateTime dt2 = new DateTime(2010, 4, 1, 23, 0, 0, DateTimeKind.Utc);
    Console.WriteLine(TimeZoneInfo.ConvertTimeFromUtc(dt2, tz));
    Console.ReadLine();
  }
}

输出:

12/31/2009 11:00:00 PM 
4/2/2010 12:00:00 AM

您需要 .NET 3.5 或更高版本,并在可保留历史夏令时更改的操作系统(Vista、Win7 或 Win2008)上运行。

【讨论】:

  • @nobugz,我实际上正在使用该方法进行转换。我的问题只是基于一个理论,通过观察存储在我的数据库中的信息。
  • @James,我向您展示了该理论的结果。在我的机器上工作。您是否查看了我帖子底部的要求?你得到不同的结果吗?这就是继续提出问题的方法......
  • 不,我没有测试它,我只是在想(从查看存储​​在数据库中的日期/时间如何)如果我不是当前会发生什么> 在 DST 观察时间内。日期/时间是否会正确重新调整。但是,正如@Jon 所提到的,DST 是根据实际日期时间本身而不是当前日期时间考虑的。谢谢。
【解决方案3】:

您尝试将日期和时间存储为 UTC,这很好。通常最好和最容易将 UTC 视为 实际 日期和时间,而本地时间只是其的化名。如果您需要对日期/时间值进行任何数学运算以获取时间跨度,那么 UTC 绝对至关重要。我通常在内部将日期操作为 UTC,并且仅在向用户显示值时转换为本地时间(如果有必要)。

您遇到的错误是您错误地将本地时区分配给日期/时间值。在英国,在一月份将当地时间解释为夏令时是不正确的。您应该使用在时间值所代表的时间和位置有效的时区。

将时间倒回显示完全取决于系统的要求。您可以将时间显示为用户的本地时间或数据的源时间。但无论哪种方式,都应针对目标时区和时间适当地应用夏令时/夏令时调整。

【讨论】:

  • @Jeff,我正在使用基于时区的 ConvertTimeFromUtc 方法将 UTC 日期转换回来。所以我不是手动尝试调整夏令时,它是自动调整的实际方法。我会更改我的日期,因为我认为它令人困惑,这只是一个例子。
【解决方案4】:

这是一个巨大的缺陷,但它不是以 UTC 存储时间的缺陷(因为这是唯一合理的做法——存储当地时间总是是一场灾难)。这是夏时制概念的一个缺陷。 真正的问题是时区信息发生了变化。 DST 规则是动态的和历史的。他们在 2010 年在美国开始夏令时的时间与 2000 年开始时的时间不同。直到最近 Windows 甚至都没有包含这些历史数据,因此基本上不可能正确地做事。您必须使用tz database 才能正确处理。现在我用谷歌搜索了一下,似乎 .NET 3.5 和 Vista(我也假设 Windows 2008)已经做了一些改进,并且 System.TimeZoneInfo 实际上处理了历史数据。看看this

但基本上 DST 必须去。

【讨论】:

    【解决方案5】:

    您可以通过存储转换为 UTC 时使用的特定偏移量来解决此问题。在您的示例中,您会将日期存储为类似

    31/12/2009 23:00 +0100
    

    向用户显示此内容时,您可以使用相同的偏移量进行转换,或者您选择的当前本地偏移量。

    这种方法也有其自身的问题。时间是个乱七八糟的东西。

    【讨论】:

    • +1 是的,我认为这实际上可以解决问题。意味着当我重新计算它时,我将能够判断它是否被调整。
    【解决方案6】:

    如果我错了,请纠正我,但不是 存储时这有点缺陷 UTC 时间?

    是的。此外,调整的日子将有 23 或 25 小时,因此前一天的成语同时是当地时间 - 24 小时是错误的一年 2 天。

    解决方法是选择一个标准并坚持下去。将日期存储为 UTC 并显示为本地是非常标准的。只是不要使用在本地进行计算的快捷方式(+- somthing)=新时间,你就可以了。

    【讨论】:

      猜你喜欢
      • 2011-04-17
      • 2014-07-03
      • 2016-08-07
      • 2010-11-23
      • 1970-01-01
      • 2018-09-28
      • 2012-01-27
      • 1970-01-01
      • 2011-12-24
      相关资源
      最近更新 更多