在将日期时间从1/6转换为珀斯至Sri Jeyawardenepura时,其转换为1/31/2005 11.30pm
在同一时间(1/31/2005下午11点30分)从Sri Jeyawardenepura转换到珀斯,它转换为1/1/2006 3.00 AM.
为什么时区转换会有一小时的差异?
解决方法
仅使用.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,2,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 Lanka和Perth.虽然两者都在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,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,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,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.直到4月才转到05:30.
所以回顾一下Whammys:
> Windows TimeZoneInfo.ConvertTime函数似乎有缺陷.
>“斯里兰卡标准时间”区域的Windows时区数据不正确.
最好只使用Noda Time和TZDB!
UPDATE
感谢Jon Skeet帮助确定第一个问题是TimeZoneInfo类正在解释“W. Australia Standard Time”区域的方式.
我深入研究了.NET Framework参考源代码,我相信这是在私有静态方法TimeZoneInfo.GetIsDaylightSavingsFromUtc中发生的.我认为他们没有考虑到DST并不总是在同一日历年开始和停止.
在这种情况下,他们将2006年调整规则应用于2005年,并在2005年12月4日开始时间之前获得1/2/2005的结束时间.他们确实试图协调这应该是在2006年(通过错误地添加一年),但他们不认为数据是相反的顺序.
这个问题可能会出现在冬季开始夏令时的任何时区(例如澳大利亚),并且在转换规则发生变化的任何时候它都会以某种形式出现 – 就像它在2006年所做的那样.
我提出了一个问题on Microsoft Connect here.
我提到的“第二次打击”只是因为斯里兰卡的历史数据在Windows时区注册表项中不存在.