转换时区之间的时间

Lok*_*Dil 6 .net c# timezone

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

在将日期时间从1/6转换为珀斯至Sri Jeyawardenepura时,其转换为1/31/2005 11.30pm

在同一时间(1/31/2005下午11点30分)从Sri Jeyawardenepura转换到珀斯,它转换为1/1/2006 3.00 AM.

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

Mat*_*int 11

哇,这是Double Whammy!我只是偶然发现了这篇文章并且根本不会发布任何内容,因为它太旧了,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
Run Code Online (Sandbox Code Playgroud)

果然,OP所描述的差异.起初我认为这必定是由于某种DST问题,所以我检查了斯里兰卡珀斯.虽然两者都在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
Run Code Online (Sandbox Code Playgroud)

它仍然关闭.你可以看到它认为目标时间应该是+09:00,但珀斯直到2006年12月3日才改用它.1月它显然仍然存在+08:00.

那么我想...... 野田时间来救援!

首先,让我们使用相同的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
Run Code Online (Sandbox Code Playgroud)

嘿,好像修好了,对吧?如果是这样,那就意味着问题在于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
Run Code Online (Sandbox Code Playgroud)

在那里,我的朋友们,是Whammy#2.请注意,中间时间正在使用+06:00偏移量?我认为这是错误的,但当我在这里再次检查时,结果证明TZDB数据是正确的.斯里兰卡+06:00那个时候.+05:30直到4月才切换到.

所以回顾一下Whammys:

  • Windows TimeZoneInfo.ConvertTime功能似乎有缺陷.
  • 区域的Windows时区数据"Sri Lanka Standard Time"不正确.

最好只使用Noda Time和TZDB!

UPDATE

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

我挖了很多深入的.NET Framework参考源代码,而且我相信这是在私有静态方法发生TimeZoneInfo.GetIsDaylightSavingsFromUtc.我认为他们没有考虑到DST并不总是在同一日历年开始和停止.

在这种情况下,他们将与2005年2006年调整规则,并得到一个endTime1/2/2005在之前startTime12/4/2005.他们确实试图协调这应该是在2006年(通过错误地添加一年),但他们不认为数据是相反的顺序.

这个问题可能会出现在冬季开始夏令时的任何时区(例如澳大利亚),并且在转换规则发生变化的任何时候它都会以某种形式出现 - 就像它在2006年所做的那样.

我在这里提出了一个关于Microsoft Connect的问题.

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


Jon*_*eet 6

只是为Matt的答案添加更多信息,似乎BCL对其自己的珀斯数据非常困惑.似乎认为2005年底有两次转换 - 一次是在UTC时间下午4点,一次是八小时后.

演示:

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));
    }
} 
Run Code Online (Sandbox Code Playgroud)

结果:

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
Run Code Online (Sandbox Code Playgroud)

这非常离奇,可能利比亚时区的破裂有关 - 尽管没有两个过渡,只有一个错位.