转换时区之间的时间

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

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

同时将时间转换回来(1月31日下午11点30分)从Sri Jeyawardenepura转到珀斯,它转换为1/1/2006 3.00 AM。

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

哇,这是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 

果然,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 

它仍然关闭。 你可以看到它认为目标时间应该是+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 

嘿,这似乎修好了,对吧? 如果是这样,那就意味着问题在于Windows时区数据,因为Noda Time的BCL提供商使用完全相同的数据。 所以在TimeZoneInfo.ConvertTime必定存在一些实际上有缺陷的东西。 有傻瓜#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偏移量? 我认为这是错误的,但当我在这里再次检查时,结果certificateTZDB数据是正确的。 斯里兰卡当时的时间 +06:00 。 它直到4月才转为+05:30

所以回顾一下Whammys:

  • Windows TimeZoneInfo.ConvertTime函数似乎有缺陷。
  • "Sri Lanka Standard Time"区域的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之前获得1/2/2005时间。 他们确实试图协调这应该是在2006年(通过错误地添加一年),但他们不认为数据是颠倒的。

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

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

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

为了向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)); } } 

结果:

 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 

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

您必须发布特定代码才能确定。 可能存在一个问题,例如,通过一次转换而不是另一次转换应用日光时间。

时区管理可能有细微差别。 建议您查看此Jon Skeet博客,以获得精彩的概述。

事实上,正确使用.NET时间类是非常棘手的,Jon已经采用了Joda-Time to .NET的端口,称为Noda Time 。

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

转换时间时你有没有考虑过Day节省的费用? 请参阅以下链接,您将得到答案。 显示的时间绝对正确

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