14

我将日期/时间作为 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());
4

6 回答 6

38

您不会根据您当前是否正在观察 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),UTC 的 12:38,悉尼的 22:39。

您的代码将给出:

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

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

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

于 2010-04-05T19:12:43.537 回答
2

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

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

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

于 2010-04-05T19:40:38.987 回答
1

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)上运行。

于 2010-04-05T20:45:09.240 回答
1

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

31/12/2009 23:00 +0100

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

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

于 2010-04-05T19:15:16.817 回答
0

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

但基本上 DST 必须去。

于 2010-04-05T19:38:46.083 回答
0

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

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

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

于 2010-04-05T19:13:18.187 回答