我正在使用 TimeZoneInfo.ConvertTime 方法将时间从一个转换为另一个。
将日期时间 2006 年 1 月 1 日凌晨 2 点从珀斯转换为 Sri Jeyawardenepura 时,其转换为 2005 年 1 月 31 日晚上 11 点 30 分
从 Sri Jeyawardenepura 到珀斯的同一时间(2005 年 1 月 31 日晚上 11 点 30 分)转换为 2006 年 1 月 1 日凌晨 3 点。
为什么时区转换时差一小时?
哇,这是双重打击!我只是偶然发现了这篇文章并且根本不会发布任何内容,因为它太旧了而且 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 Lanka和Perth。虽然两者都在 2006 年进行了过渡,但在这一天都没有接近它。不过,我认为我应该检查 usingDateTimeOffset
以避免任何歧义问题:
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 日才切换到那个。一月份它显然是静止+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
。有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
偏移量吗?我认为这是错误的,但是当我再次检查时发现TZDB数据是正确的。那时的斯里兰卡。直到四月+06:00
才切换。+05:30
因此,回顾一下 Whammys:
TimeZoneInfo.ConvertTime
功能似乎有缺陷。"Sri Lanka Standard Time"
不正确。最好总是使用 Noda Time 和 TZDB!
感谢 Jon Skeet 帮助确定第一个问题是班级"W. Australia Standard Time"
解释区域的方式。TimeZoneInfo
我对 .NET Framework 参考源代码进行了更深入的研究,我相信这发生在私有静态方法TimeZoneInfo.GetIsDaylightSavingsFromUtc
中。我相信他们没有考虑到 DST 并不总是在同一日历年开始和停止。
在这种情况下,他们将 2006 年调整规则与 2005 年一起应用,并在endTime
of1/2/2005
之前得到一个startTime
of 12/4/2005
。他们确实试图调和这应该是在 2006 年(通过错误地添加一年),但他们不认为数据是倒序的。
这个问题可能会出现在任何在冬季开始 DST 的时区(例如澳大利亚),并且它会在任何时候转换规则发生变化时以一种或另一种形式出现 - 它在 2006 年发生了变化。
我在这里提出了关于 Microsoft Connect的问题。
我提到的“第二次打击”只是因为斯里兰卡的历史数据不存在于 Windows 时区注册表项中。
只是为了在马特的回答中添加更多信息,BCL 似乎对自己的珀斯数据感到非常困惑。似乎认为在 2005 年底前后发生了两次转变——一次在世界标准时间下午 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移植到 .NET,称为Noda Time。
对于任何支持多个时区的项目,都值得认真考虑。
您在转换时间时是否考虑过夏令时?请参考以下链接,您将得到答案。显示的时间绝对正确
http://www.timeanddate.com/worldclock/timezone.html?n=196&syear=2000