Handling timezones with daylight saving in C#
我们有一个与航空相关的应用程序,特别是航班。
时间必须存储在本地,所以我选择使用UTC时间+偏移,但现在我意识到这是一个糟糕的选择:
通过将时区存储为偏移量,我忘记了原始时区,这对处理夏令时有影响。
例如,我可以在Alpine,UT中存储时间作为UTC时间和-6偏移,以及在亚利桑那州凤凰城的时间作为UTC时间和-6偏移。
但是当夏令时来临时,阿尔卑斯山的时间会发生变化,而菲尼克斯则不然。
所以,我需要存储正确的时区,我已经看到有不同的列表,不同的语法,所以我假设有不同的标准。
在C#中,使用本地时区存储本地时间以使其适用于夏令时更改的最佳选择是什么?
-
正如你所提到的那样,存储本地时间是一个糟糕的选择,我也同意它。我建议如果您可以开始更改当前程序以存储UTC时区(为了具有向后兼容性),您可以存储UTC和本地时区值。那就是你不需要处理所有边缘情况,因为这个国家有白天节省的时间和时间。您可以依赖系统dll根据用户的区域设置将UTC值转换为本地值。
-
"时间必须存储在当地"。我真的很抱歉。这就是疯狂的大门。必须处理夏令时只会使疯狂之旅更快。请接受以前走过这条路的人的建议,为了你自己的理智 - 重新考虑这种方法。
-
用户的区域设置与此无关,因为在航空中,显示的时间始终位于机场的位置。问题是,如果时间存储在UTC + Offset并且夏令时开始,我无法进行更正,因为如果我只存储偏移量,我不知道该时间属于哪个区域。
-
@Zohar:是的,这就是我在这里写的原因:D我想知道如何正确存储它,因为UTC + Offset不适用于夏令时
-
将它们存储在UTC中,以当地时间显示。需要时转换。
-
但什么是"本地"? Alpine是UTC - 6 +夏令时,Phoenix是UTC - 6。因此本地区域是UTC +偏移+本地节省的夏令时,我需要找到如何存储"本地"的含义,它在夏令时方面属于哪个区域
-
你在某个地方的客户端上显示它们。让客户告诉你它的时区。
-
不在航空:如果您正在寻找洛杉矶 - >纽约航班,航班起飞是洛杉矶时间,航班到达时间是纽约时间,这与客户无关。
-
然后,您应该查找城市及其当前时区的数据库。有些东西像worldtimeserver.com(和其他人),也许其中一个有公共API使用。
-
刚刚参加这个讨论。我可以告诉你,"始终以UTC存储"的建议并不总是正确的。背景很重要。例如,如果您要存储飞机离开或降落在机场的时间,则首选基于UTC的时间。如果希望本地时间显而易见,则可以使用具有本地时间和UTC偏移的DateTimeOffset。但是,当描述飞机计划离开的未来时间时,例如在乘客售票系统中,您不应使用UTC,而应使用本地时间和时区ID或机场代码。
-
您能告诉我们更多关于您的日期/时间值存在的背景吗?您是在跟踪正在进行的航班(例如:从飞行员或空中交通管制员的角度来看),还是您正在使用预定的航班时刻表(从乘客的角度来看)?
-
航班始终按当地时间(飞行员或乘客)安排,例如我们下午6点/纽约。我们曾经将所有转换为UTC,这很适合存储过去的航班;现在我们正在增加当前和未来的航班,因为它不仅仅是我们购买的数据,还有飞行员为我们提供的数据:如果飞行员告诉我们下午5点/美国犹他州盐湖城,我们不能只为UTC添加+6因为我们不会考虑夏令时。因此存储时间偏移也不能很好地工作。
-
理想情况下,我们需要知道时区,而不是作为偏移,而是作为文字"时间"......"区域",我们知道该区域的偏移和夏令时状态。例如,UT和AZ都是-6,但是半年它们仍然有不同的时间。一旦我们知道UTC / Local变得无关紧要,因为我们可以随意翻译
从问题评论中的讨论中,我了解到您正在处理航班时刻表 - 即未来航班打算离开的时间。这确实是本地时间比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将是一个很好的选择,恕我直言。您不仅可以获得出色的时区支持,而且还可以使用类似LocalTime或LocalDateTime的类型,这些类型与所描述的场景完美匹配。
-
好吧,我不得不承认,这似乎是一个比我建议的更好的解决方案。 +1。以UTC格式存储日期时间的一点是它简化了飞行持续时间的计算。我确信如果存储为本地日期时间也可以完成,但它可能会更复杂。
-
实际上,当您使用这些值进行计算时,您必须知道UTC的偏移量 - 您可以通过TimeZoneInfo上的GetUtcOffset或ConvertTime*方法或通过各种Noda Time API获取。但是,从这些事情的经验来看,我建议在航空业中,必须要小心计算飞行时间。相反,决定出发时间或到达时间是否具有权威性,并应用已知的飞行持续时间来实现另一个。换句话说,departure + duration = arrival,而不是arrival - departure = duration。
-
还要考虑我在博客底部第五次更新中提到的有关2016年埃及时区变化的参考资料。阿联酋航空926号航班提前一小时离开18名乘客。作为一家迪拜拥有的航空公司,他们认为他们的到达时间更重要,并且算数为arrival - duration = departure。由于埃及在最后一刻修改了他们的时区DST规则,这家总部位于迪拜的航空公司无法及时更新他们的时间表。
-
埃及航空当天的航班准时出发,但比预期晚了一个小时。对于其中一个,拥有旧数据并且arrival - departure = duration的在线网站最终计算出一小时的飞行时间。
-
我经常觉得常规数学规则在现实生活中不适用有趣...我不得不承认我的建议更为一般,因为我从未研究过任何与航空交通有关的事情(我曾在一些国际系统上工作过从世界各地的许多不同来源获取数据,并且必须将所有沙拉同步到一个结构良好的单一框架中 - 而这就是UTC保存我的理智的时候)。无论如何,正如我在之前的评论中所写,我认为你的答案在这个问题上比我的好。
-
是的。对于那些同步沙拉故事,UTC是王道。尤其是日志文件关联等。当前的时间戳事件使所有这些数据过去事件,因此所有具有时区的有趣业务变得大多无关紧要。 :)
-
实际上这既是过去也是未来的事件。 没有考虑到政治上的疯狂,比如你在博客上发表的埃及混乱局面,或者几乎每年都有关于夏令时的政治疯狂。 那个客户曾经让他们的IT人员在02:00到01:00或者每年两次03:00手动设置时钟......事实上,时间和时区确实需要比普通Joe想象的要多得多。
正如我在评论中写的那样,不要存储本地日期。而是将datetime值存储为UTC,并在需要显示时转换为本地日期时间。
您可以使用TimeZoneInfo类的ConvertTimeFromUtc方法。
这意味着您还必须保留一个位置列表以及它们所关联的TimeZoneInfo - 例如,
Jerusalem将与Israel Standard Time相关联,
Rome W. Europe Standard Time,
Hawaii,Hawaiian 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。
-
让我试一试;我需要检查一下这个城市名单有多广泛
-
正如Marvin在评论中所写,有些网站致力于处理时区问题,也许你可以使用第三方API。
-
我正在看NodaTime,因为它似乎可以帮助我的情况。
-
也许;我没有NodaTime的经验,但我一直在阅读有关它的好东西。
-
Noda Time允许您使用标准的IANA时区标识符,例如"America/Los_Angeles"或"Asia/Jerusalem"。有关详细信息,请参阅时区标记wiki中的"时区数据库"。
-
注意,这个答案在如何在特定Windows时区中从UTC转换为本地时间方面很好,但它没有解决OP的上下文问题。"总是UTC"不是最好的建议。它适用于许多情况,但不是全部。
-
@MattJohnson我同意它并不总是最好的选择,但是每当你必须使用不同的时区时,我发现最好把你所有的时间存储在一个每个人都可以参考的时区。 UTC就是这样。
-
当然,但在许多情况下,这种逻辑会崩溃。在未来安排或使用预定时间就是其中之一。另一个是使用仅限日期的值(例如生日)。关于这个已经写了很多,所以我不会无人机。只要注意任何"总是"的建议 - 如果有不止一种方法可以做某事,那么可能有边缘情况需要这种方式。