Storing date/times as UTC in database
我将数据库中的日期/时间存储为UTC,并根据特定时区将其在我的应用程序中计算回本地时间。比方说我有以下日期/时间:
说这是一个国家,例如英国观察夏令时(夏令时),在这个特定的时间,我们在夏令时。当我将此日期转换为UTC并将其存储在数据库中时,它实际存储为:
由于日期将调整为夏令时的-1小时。当您在提交时观察DST时,此工作正常。但是,当时钟调整回来时会发生什么?当我从数据库中提取该日期并将其转换为本地时间时,特定日期时间将被视为
如果我错了,请纠正我但是在存储UTC时间时这不是一个缺陷吗?
时区转换的示例
基本上我正在做的是存储信息提交到我的系统的日期/时间,以便允许用户进行范围报告。这是我存储日期/时间的方式:
1 2 3 4 5 | public DateTime LocalDateTime(string timeZoneId) { var tzi = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId); return TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tzi).ToUniversalTime().ToLocalTime(); } |
存储为UTC:
1 2 | 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及其偏移量;这允许你告诉当时的当地时间,但这意味着你无法预测一分钟之后的当地时间,因为你真的不知道时区。 (这基本上是
我们希望在Noda Time中使这个相当容易处理,但你仍然需要意识到这是一种可能性。
编辑:
您显示的代码不正确。这就是原因。我已经改变了代码的结构,使其更容易看到,但你会看到它正在执行相同的调用。
1 2 3 4 | 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在悉尼。
您的代码将给出:
1 2 3 | aussieTime = 22:39 (correct) serverLocalTime = 23:39 (*not* correct) utcTime = 22:39 (*not* correct) |
你不应该在
因此,如果您在这种情况下准确保存22:39,则无法准确地以UTC格式保存当前时间。
您尝试将日期和时间存储为UTC是件好事。通常最好和最容易将UTC视为实际的日期和时间,当地时间只是假名。如果您需要对日期/时间值进行任何数学计算以获得时间跨度,那么UTC绝对是至关重要的。我通常在内部操作日期为UTC,并且只在向用户显示值时转换为本地时间(如果有必要)。
您遇到的错误是您错误地将本地时区分配给日期/时间值。 1月在英国将当地时间解释为夏令时区是不正确的。您应该使用在时间值表示的时间和位置生效的时区。
将时间转换回来进行显示完全取决于系统的要求。您可以将时间显示为用户的本地时间,也可以显示为数据的源时间。但无论哪种方式,夏令时/夏令时调整都应适当适用于目标时区和时间。
TimeZoneInfo.ConvertTimeFromUtc()方法将解决您的问题:
1 2 3 4 5 6 7 8 9 10 11 12 | 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(); } } |
输出:
1 2 | 12/31/2009 11:00:00 PM 4/2/2010 12:00:00 AM |
您需要.NET 3.5或更高版本,并在保持历史夏令时更改(Vista,Win7或Win2008)的操作系统上运行。
您可以通过存储转换为UTC时使用的特定偏移来解决此问题。在您的示例中,您将日期存储为类似的内容
1 | 31/12/2009 23:00 +0100 |
向用户显示此信息时,您可以根据需要使用相同的偏移量或其当前的本地偏移量。
这种方法也有其自身的问题。时间是一件混乱的事情。
这是一个巨大的缺陷,但它不是以UTC存储时间的缺陷(因为这是唯一合理的事情 - 存储本地时间总是一场灾难)。这是一个缺陷,是夏令时的概念。
真正的问题是时区信息会发生变化。 DST规则是动态和历史性的。他们在2010年从美国开始的DST在2000年开始时并不相同。直到最近,Windows甚至没有包含这些历史数据,所以基本上不可能正确地做事。您必须使用tz数据库才能正确完成。现在我只是用谷歌搜索它,似乎.NET 3.5和Vista(我也假设Windows 2008)已经做了一些改进,System.TimeZoneInfo实际上处理历史数据。看看这个。
但基本上DST必须去。
Correct me if I am wrong but isn't
this a bit of a flaw when storing
times as UTC?
是的。此外,调整的天数将是23或25小时,因此前一天的成语同时是当地时间 - 一年2天24小时是错误的。
该修复程序正在挑选一个标准并坚持使用它。将日期存储为UTC并显示为本地是非常标准的。只是不要使用本地计算的快捷方式(+ - somthing)=新时间,你没事。