【问题标题】:Converting time between Timezones在时区之间转换时间
【发布时间】:2012-04-04 08:08:17
【问题描述】:

我正在使用 TimeZoneInfo.ConvertTime 方法将时间从一个转换为另一个。

将日期时间 2006 年 1 月 1 日凌晨 2 点从珀斯转换为 Sri Jeyawardenepura 时,其转换为 2005 年 1 月 31 日晚上 11 点 30 分

将同一时间(2005 年 1 月 31 日晚上 11 点 30 分)从 Sri Jeyawardenepura 转换为珀斯时,它转换为 2006 年 1 月 1 日凌晨 3 点。

为什么时区转换会有一小时的差异?

【问题讨论】:

  • 您是否需要这种手动转换,或者您可以使用 DateTimeOffset 类为您的所有转换处理?
  • 能否发布一个展示此问题的代码示例?
  • @BoasEnkler 谢谢,不是手动转换,会试试 DateTimeOffset
  • @CodeInChaos 这里是示例代码:- timeBlock.StartTimeStamp = TimeZoneInfo.ConvertTime(timeBlock.StartTimeStamp, fromTimeZone, toTimeZone); timeBlock.EndTimeStamp = TimeZoneInfo.ConvertTime(timeBlock.EndTimeStamp, fromTimeZone, toTimeZone);
  • @LokiDil 这还不够。请创建一个工作示例,特别是获取from/toTimeZone 的代码。我怀疑其中一个时区的偏移量在该特定时间点发生了变化。

标签: c# .net timezone


【解决方案1】:

哇,这是双重打击!我只是偶然发现了这篇文章并且根本不会发布任何内容,因为它太旧了而且 OP 没有显示任何代码。但后来好奇心控制了我,所以我检查了它。

仅使用 .NET BCL:

string tzid1 = "W. Australia Standard Time"; // Perth
TimeZoneInfo tz1 = TimeZoneInfo.FindSystemTimeZoneById(tzid1);

string tzid2 = "Sri Lanka Standard Time"; // Sri Jeyawardenepura
TimeZoneInfo tz2 = TimeZoneInfo.FindSystemTimeZoneById(tzid2);

DateTime dt1 = new DateTime(2006, 1, 1, 2, 0, 0);
Debug.WriteLine(dt1); // 1/1/2006 2:00:00 AM
DateTime dt2 = TimeZoneInfo.ConvertTime(dt1, tz1, tz2);
Debug.WriteLine(dt2); // 12/31/2005 11:30:00 PM
DateTime dt3 = TimeZoneInfo.ConvertTime(dt2, tz2, tz1);
Debug.WriteLine(dt3); // 1/1/2006 3:00:00 AM

果然,有OP描述的差异。起初我认为这一定是由于某种 DST 问题,所以我检查了Sri LankaPerth。虽然两者都在 2006 年进行了过渡,但在这一天都没有接近它。不过,我认为我应该使用DateTimeOffset 进行检查以避免任何歧义问题:

string tzid1 = "W. Australia Standard Time"; // Perth
TimeZoneInfo tz1 = TimeZoneInfo.FindSystemTimeZoneById(tzid1);

string tzid2 = "Sri Lanka Standard Time"; // Sri Jeyawardenepura
TimeZoneInfo tz2 = TimeZoneInfo.FindSystemTimeZoneById(tzid2);

DateTime dt = new DateTime(2006, 1, 1, 2, 0, 0);
DateTimeOffset dto1 = new DateTimeOffset(dt, tz1.GetUtcOffset(dt));
Debug.WriteLine(dto1);  // 1/1/2006 2:00:00 AM +08:00
DateTimeOffset dto2 = TimeZoneInfo.ConvertTime(dto1, tz2);
Debug.WriteLine(dto2);  // 12/31/2005 11:30:00 PM +05:30
DateTimeOffset dto3 = TimeZoneInfo.ConvertTime(dto2, tz1);
Debug.WriteLine(dto3);  // 1/1/2006 3:00:00 AM +09:00

它仍然关闭。你可以看到它认为目标时间应该是+09:00,但珀斯直到 2006 年 12 月 3 日才切换到那个位置。一月份它显然仍然是+08:00

然后我想...Noda Time 来救援!

首先让我们检查是否使用相同的 Windows .NET BCL 时区。

string tzid1 = "W. Australia Standard Time"; // Perth
DateTimeZone tz1 = DateTimeZoneProviders.Bcl[tzid1];

string tzid2 = "Sri Lanka Standard Time"; // Sri Jeyawardenepura
DateTimeZone tz2 = DateTimeZoneProviders.Bcl[tzid2];

LocalDateTime ldt1 = new LocalDateTime(2006, 1, 1, 2, 0, 0);
ZonedDateTime zdt1 = ldt1.InZoneStrictly(tz1);
Debug.WriteLine(zdt1.ToDateTimeOffset()); // 1/1/2006 2:00:00 AM +08:00
ZonedDateTime zdt2 = zdt1.WithZone(tz2);
Debug.WriteLine(zdt2.ToDateTimeOffset()); // 12/31/2005 11:30:00 PM +05:30
ZonedDateTime zdt3 = zdt1.WithZone(tz1);
Debug.WriteLine(zdt3.ToDateTimeOffset()); // 1/1/2006 2:00:00 AM +08:00

嘿,这似乎解决了它,对吧?如果是这样,那将意味着问题与 Windows 时区数据有关,因为 Noda Time 的 BCL 提供程序使用完全相同的数据。所以TimeZoneInfo.ConvertTime 中一定有什么缺陷。 Whammy #1

为了检查一切是否正常,让我们对 IANA TZDB 数据进行同样的尝试。众所周知,它毕竟更准确:

string tzid1 = "Australia/Perth";
DateTimeZone tz1 = DateTimeZoneProviders.Tzdb[tzid1];

string tzid2 = "Asia/Colombo"; // Sri Jeyawardenepura
DateTimeZone tz2 = DateTimeZoneProviders.Tzdb[tzid2];

LocalDateTime ldt1 = new LocalDateTime(2006, 1, 1, 2, 0, 0);
ZonedDateTime zdt1 = ldt1.InZoneStrictly(tz1);
Debug.WriteLine(zdt1.ToDateTimeOffset()); // 1/1/2006 2:00:00 AM +08:00
ZonedDateTime zdt2 = zdt1.WithZone(tz2);
Debug.WriteLine(zdt2.ToDateTimeOffset()); // 1/1/2006 12:00:00 AM +06:00
ZonedDateTime zdt3 = zdt1.WithZone(tz1);
Debug.WriteLine(zdt3.ToDateTimeOffset()); // 1/1/2006 2:00:00 AM +08:00

还有,我的朋友们,Whammy #2。注意中间时间是使用+06:00 偏移量吗?我认为这是错误的,但是当我再次检查here 时,发现 TZDB 数据是正确的。斯里兰卡当时+06:00。直到四月才切换到+05:30

所以回顾一下 Whammys:

  • Windows TimeZoneInfo.ConvertTime 函数似乎存在缺陷。
  • "Sri Lanka Standard Time" 区域的 Windows 时区数据不正确。

最好总是使用 Noda Time 和 TZDB!

更新

感谢 Jon Skeet 帮助确定第一个问题是 "W. Australia Standard Time" 区域被 TimeZoneInfo 类解释的方式。

我对 .NET Framework 参考源代码进行了很多研究,我相信这发生在私有静态方法 TimeZoneInfo.GetIsDaylightSavingsFromUtc 中。我相信他们没有考虑到 DST 并不总是在同一日历年开始和停止。

在这种情况下,他们将 2006 年的调整规则与 2005 年一起应用,并在 12/4/2005startTime 之前得到 1/2/2005endTime。他们确实试图协调这应该是在 2006 年(通过错误地添加了一年),但他们不认为数据是倒序的。

这个问题可能会出现在任何在冬季开始 DST 的时区(例如澳大利亚),并且它会在过渡规则发生变化的任何时候以一种或另一种形式出现 - 它在 2006 年发生了变化。

我提出了一个问题on Microsoft Connect here

我提到的“第二次打击”只是因为斯里兰卡的历史数据不存在于 Windows 时区注册表项中。

【讨论】:

  • 不错的分析。感谢您提供详细信息。
  • ConvertTime 部分似乎在珀斯而不是斯里兰卡被破坏。在珀斯和 UTC 之间转换也有同样的问题。编辑:是的,那里发生了一些非常奇怪的事情。 Noda Time 没有找到过渡,但 BCL 在某些情况下会找到...
  • @JonSkeet - 很有趣。 BCL“西澳大利亚标准时间”的动态 DST 条目中可能有问题?
  • @MattJohnson:我认为TimeZoneInfo 在年初解释过渡的方式已经被打破了,基本上:(
  • @JonSkeet - 我认为斯里兰卡数据不正确的说法也很公平,因为它没有动态 DST 条目并假设它总是在 +5:30。但我认为许多其他 BCL 区域也是如此。
【解决方案2】:

只是在马特的回答中添加更多信息,B​​CL 似乎对自己的珀斯数据非常困惑。似乎认为在 2005 年底前后发生了两次过渡 - 一次在 UTC 下午 4 点,另一次在 8 小时后。

演示:

using System;

class Test
{    
    static void Main()        
    {
        var id = "W. Australia Standard Time"; // Perth
        var zone = TimeZoneInfo.FindSystemTimeZoneById(id);
        var utc1 = new DateTime(2005, 12, 31, 15, 59, 0, DateTimeKind.Utc);
        var utc2 = new DateTime(2005, 12, 31, 16, 00, 0, DateTimeKind.Utc);
        var utc3 = new DateTime(2005, 12, 31, 23, 59, 0, DateTimeKind.Utc);
        var utc4 = new DateTime(2006, 1, 1, 0, 0, 0, DateTimeKind.Utc);
        Console.WriteLine(zone.GetUtcOffset(utc1));
        Console.WriteLine(zone.GetUtcOffset(utc2));
        Console.WriteLine(zone.GetUtcOffset(utc3));
        Console.WriteLine(zone.GetUtcOffset(utc4));
    }
} 

结果:

08:00:00 // 3:59pm UTC
09:00:00 // 4:00pm UTC
09:00:00 // 11:59pm UTC
08:00:00 // 12:00am UTC the next day

这非常奇怪,可能Libyan time zone breakage有关 - 尽管它没有两个转换,只有一个错位。

【讨论】:

  • 我刚刚手动检查了 2005 年和 2006 年该区域的注册表动态 DST 数据中的所有位,并且在数据中似乎是正确的。你一定是对的,它在解释中。
  • 我想我找到了问题所在。请参阅我的答案底部的更新。
  • @MattJohnson:对。我不知道这是否与“利比亚问题”相同,但我不会感到惊讶。显然,解释调整规则的代码存在严重缺陷。
【解决方案3】:

您必须发布特定代码才能确定。可能有问题,例如一个转换应用了夏令时,而另一个则不应用。

时区管理可能存在细微差别。建议您查看this Jon Skeet blog 以获得一个很好的概述。

事实上,正确使用 .NET 时间类非常棘手,以至于 Jon 将 Joda-Time 移植到 .NET,称为 Noda Time

对于任何支持多个时区的项目都值得认真考虑。

【讨论】:

  • 我没有 VS2010 可以使用 nupkg :(
  • @LokiDil:显然您可以使用 Sharp Develop(免费)在旧式 VS 项目中安装 NuGet 包。不过我自己还没有这样做:stackoverflow.com/questions/8839009/…
  • @EricJ。如果您对我如何使用 Noda Time 调查此问题感兴趣,请参阅我的回答了解详细信息。
【解决方案4】:

您在转换时间时是否考虑过夏令时? 请参考以下链接,您将得到答案。显示的时间绝对正确

http://www.timeanddate.com/worldclock/timezone.html?n=196&syear=2000

【讨论】:

  • 其实,不,不正确。 OP是正确的,存在差异。详情见我的回答。
猜你喜欢
  • 2016-11-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-05
  • 1970-01-01
  • 1970-01-01
  • 2021-03-05
  • 2018-04-12
相关资源
最近更新 更多