c# - 在时区之间转换时间

标签 c# .net timezone

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

在将日期时间 1/1/2006 2.00 AM 从珀斯转换为 Sri Jeyawardenepura 时,它转换为 1/31/2005 11.30pm

同时将同一时间(2005 年 1 月 31 日晚上 11 点 30 分)从 Sri Jeyawardenepura 转换回珀斯时,它转换为 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 LankaPerth .虽然两者都在 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

还有,我的 friend 们,Whammy #2。请注意,中间时间使用的是 +06:00抵消?我以为这是错误的,但当我再次检查时 here事实证明,TZDB 数据是正确的。斯里兰卡+06:00当时。它没有切换到 +05:30直到四月。

所以回顾一下 Whammys:

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

最好始终使用 Noda Time 和 TZDB!

更新

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

我对 .NET Framework 引用源代码进行了很多的挖掘,我相信这发生在私有(private)静态方法中 TimeZoneInfo.GetIsDaylightSavingsFromUtc .我相信他们没有考虑到 DST 并不总是在同一日历年开始和停止。

在这种情况下,他们将 2006 年调整规则应用于 2005 年,并获得 endTime1/2/2005startTime 之前的 12/4/2005 .他们确实试图调和这应该是 2006 年(通过错误地添加年份),但他们不认为数据顺序颠倒。

这个问题可能会出现在任何在冬季开始夏令时的时区(例如澳大利亚),并且它会在转换规则发生变化时以一种或另一种形式出现 - 它在 2006 年发生了变化。

我提出了一个问题 on Microsoft Connect here .

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

关于c# - 在时区之间转换时间,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10007208/

相关文章:

c# - 如何在 Ruby 应用程序中匹配 C# 中 Unicode 字符串的 MD5 散列输出?

c# - 如何使用异步调用检查 Web 服务是否可用

GMT 中的 Java 时区代码帮助

c# - 如何从抛出异常的方法中通过 out/ref 参数获取值?

iphone - Objective-c 如何在 GMT 时区获取没有时间分量的日期?

swift - 两个 dateTimes 之间的差异(以秒为单位)因时区不同而异 - Swift 3

c# - 序列化类型对象时检测到循环引用...如何解决?

c# - EF 5-6.1.1 中的 NullReferenceException 具有两个相同类型的导航属性

c# - 正则表达式包含 [ :alnum:] POSIX character class did not match string with C#, 但在线工具评估正常

c# - 如何在 C# 中更新 Cloudwatch 自定义指标?