在数据库中将日期/时间存储为UTC

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

01/04/2010 00:00

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

31/03/2010 23:00

因为DST的日期将调整为-1小时。 当您在提交时观察DST时,此工作正常。 但是,当时钟调整回来时会发生什么? 当我从数据库中提取该日期并将其转换为当地时间时,特定日期时间将被视为31/03/2009 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()); 

您不会根据您当前是否正在观察它们来调整DST更改的日期 – 您可以根据在您描述的瞬间是否观察到DST来调整它。 因此,在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,伦敦),12:38 UTC,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格式保存当前时间。

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

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

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

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

 31/12/2009 23:00 +0100 

向用户显示此内容时,您可以根据需要使用相同的偏移量或其当前的本地偏移量。

这种方法也有其自身的问题。 时间是一件混乱的事情。

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)的操作系统上运行。

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

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

该修复程序正在挑选一个标准并坚持使用它。 将日期存储为UTC并显示为本地是非常标准的。 只是不要使用本地计算的快捷方式(+ – somthing)=新时间,你没事。

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

但基本上DST必须去。