我正在使用TimeZoneInfo.ConvertTime方法将时间从一个转换为另一个。
将日期时间1/1/2006 2.00 AM从珀斯转换为Sri Jeyawardenepura时转换为2005年1月31日下午11:30
在将同一时间(1/31/2005下午11点30分)从Sri Jeyawardenepura转换到珀斯时,它转换为1/1/2006 3.00 AM。
为什么时区转换会有一小时的差异?
答案 0 :(得分:11)
仅使用.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 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, 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
在那里,我的朋友们, Whammy#2 。请注意,中间时间正在使用+06:00
偏移量?我认为这是错误的,但当我再次检查here时,结果证明TZDB数据是正确的。那时斯里兰卡 在+06:00
。它直到四月才转为+05:30
。
所以回顾一下Whammys:
TimeZoneInfo.ConvertTime
功能似乎存在缺陷。"Sri Lanka Standard Time"
区域的Windows时区数据不正确。最好只使用Noda Time和TZDB!
感谢Jon Skeet帮助确定第一个问题与"W. Australia Standard Time"
类正在解释TimeZoneInfo
区域的方式有关。
我深入研究 更深入.NET Framework参考源代码,我相信这是在私有静态方法TimeZoneInfo.GetIsDaylightSavingsFromUtc
中发生的。我相信他们没有考虑到DST并不总是在同一日历年开始和停止。
在这种情况下,他们将2006年调整规则应用于2005年,并在endTime
1/2/2005
之前获得startTime
12/4/2005
。他们确实试图调和这应该是在2006年(通过错误地添加一年),但他们不认为数据是颠倒的。
这个问题可能会出现在冬天开始夏令时的任何时区(例如澳大利亚),并且在转换规则发生变化的任何时候它都会以某种形式出现 - 就像它在2006年所做的那样。 / p>
我提出了一个问题on Microsoft Connect here。
我提到的“第二次打击”只是因为斯里兰卡的历史数据在Windows时区注册表项中不存在。
答案 1 :(得分: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));
}
}
结果:
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相关 - 虽然没有两个过渡,但只有一个错位。
答案 2 :(得分:3)
您必须发布特定代码才能确定。可能存在例如问题白天时间由一次转换而不是另一次转换应用。
时区管理可能存在细微差别。建议您查看this Jon Skeet blog以获得精彩的概述。
事实上,正确使用.NET时间类是非常棘手的,Jon已经为.NET Joda-Time开了一个名为Noda Time的端口。
任何支持多个时区的项目都值得认真考虑。
答案 3 :(得分:1)
转换时间时,您是否考虑过Day节省的费用? 请参阅以下链接,您将得到答案。显示的时间绝对正确
http://www.timeanddate.com/worldclock/timezone.html?n=196&syear=2000