关于.net:在C#中使用夏令时处理时区

Handling timezones with daylight saving in C#

我们有一个与航空相关的应用程序,特别是航班。

时间必须存储在本地,所以我选择使用UTC时间+偏移,但现在我意识到这是一个糟糕的选择:

通过将时区存储为偏移量,我忘记了原始时区,这对处理夏令时有影响。

例如,我可以在Alpine,UT中存储时间作为UTC时间和-6偏移,以及在亚利桑那州凤凰城的时间作为UTC时间和-6偏移。

但是当夏令时来临时,阿尔卑斯山的时间会发生变化,而菲尼克斯则不然。

所以,我需要存储正确的时区,我已经看到有不同的列表,不同的语法,所以我假设有不同的标准。

在C#中,使用本地时区存储本地时间以使其适用于夏令时更改的最佳选择是什么?


从问题评论中的讨论中,我了解到您正在处理航班时刻表 - 即未来航班打算离开的时间。这确实是本地时间比UTC时间更重要的情况。

由于您有当地的出发时间和地点(例如:盐湖城下午5:00),那么您应该在您的数据库中存储预定的出发时间两个值:

  • 17:00 - 出发时的相关当地时间
  • SLC - 时间相关的位置

如果这是此航班的特定事件,那么您也应该存储日期:

  • 2018-06-01T17:00 - 出发的具体相关当地时间
  • SLC - 当地时间相关的地点

这些是与您的业务用例在上下文相关的详细信息。不要将它们转换为UTC而忽略它们。

也就是说,您可以考虑将它们存储为DateTimeOffset(2018-06-01T17:00-06:00),这使得对于给定实例转换为UTC微不足道。但是这种方法存在两个问题:

  • 它不能用于重复,因为偏移可能会改变。
  • 即使对于单个实例,偏移也可能会发生变化 - 如果控制时区的政府决定在该事件发生之前更改其标准偏移或夏令时规则。如果您采用DateTimeOffset方法或基于UTC的方法,则必须准备好在面对此类更改时重新计算未来事件。 (有关这方面的更多信息,请参阅我的博客文章:关于时区变化和时区混乱的时间在埃及不可避免。还有无数其他例子。)

关于位置 - 因为您正在使用上下文适用于航空业的数据,我建议使用IATA机场代码,例如我在上面显示的SLC。在其他情况下,可以存储IANA时区标识符(如America/Denver)或Windows时区标识符(如Mountain Standard Time)。

您可能会发现我的"机场时区"要点(代码和输出表)对于使用IATA机场代码非常有用。您必须决定数据将如何流经您的系统。如果您在Windows上运行并希望使用TimeZoneInfo类将时间转换为不同的时区,则使用此处显示的Windows时区ID。如果您想使用IANA时区ID,请考虑使用Noda Time,或者您可以使用我的TimeZoneConverter库。这里有几种不同的选择,所以仔细探索它们并选择对你有意义的选项。

Noda Time将是一个很好的选择,恕我直言。您不仅可以获得出色的时区支持,而且还可以使用类似LocalTimeLocalDateTime的类型,这些类型与所描述的场景完美匹配。


正如我在评论中写的那样,不要存储本地日期。而是将datetime值存储为UTC,并在需要显示时转换为本地日期时间。
您可以使用TimeZoneInfo类的ConvertTimeFromUtc方法。

这意味着您还必须保留一个位置列表以及它们所关联的TimeZoneInfo - 例如,
Jerusalem将与Israel Standard Time相关联,
Rome W. Europe Standard Time
HawaiiHawaiian Standard Time
等等。 (我敢打赌,你可以在某处找到这样的在线列表。)
请注意,ConvertTimeFromUtc方法也可以为您处理夏令时问题。

然后你可以做这样的事情来获得当地时间的位置:

1
2
3
4
5
DateTime GetLocalDateByCityName(DateTime utc, string cityName)
{
    var timeZoneInfoId = GetTimeZoneInfoIdByCityName(string cityName);
    return TimeZoneInfo.ConvertTimeFromUtc(utc, TimeZoneInfo.FindSystemTimeZoneById(timeZoneInfoId);
}

当然,在GetTimeZoneInfoIdByCityName中,您可以获得特定城市的TimeZoneInfoId。