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

将日期时间1/1/2006 2.00 AM从珀斯转换为Sri Jeyawardenepura时,将其转换为1/31/2005 11.30pm

从Sri Jeyawardenepura转换为珀斯的同时(1/31/2005 11.30pm),转换为1/1/2006 3.00 AM。

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

最佳答案

哇,这是双重打击!我只是偶然发现了这篇文章,因为它太旧了而且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日,珀斯才切换到该时间。在1月份,显然仍然是+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

我的 friend 们在那里,是 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引用源代码进行了更深入的研究,并且我相信这是在私有(private)静态方法TimeZoneInfo.GetIsDaylightSavingsFromUtc中发生的。我相信他们没有考虑到DST并不总是在同一日历年中启动和停止。

    在这种情况下,他们将2006调整规则与2005年一起应用,并在endTime1/2/2005之前获得startTime12/4/2005。他们确实试图调和应该在2006年(错误地加上一年),但是他们不认为数据的顺序颠倒了。

    这个问题可能会在冬季在任何夏令时开始的任何时区(例如澳大利亚)出现,并且在过渡规则更改的任何时候都会以一种或另一种形式出现(例如2006年)。

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

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

    10-07 23:18