如何在Windows和IANA时区之间进行翻译?

205
时区标签维基中所述,有两种不同风格的时区。
  • 由 Microsoft 提供的用于 Windows 和 .Net TimeZoneInfo 类的 (在 Windows 上运行时) 标识的时区,例如 "Eastern Standard Time".

  • 由 IANA 在 TZDB 中提供的并且在 Linux 或 OSX 上运行时 .NET TimeZoneInfo 类使用的被标识为 "America/New_York" 的时区。

许多基于互联网的 API 使用 IANA 时区,但由于各种原因,可能需要将其转换为 Windows 时区 ID,或反之亦然。

如何在 .Net 中实现这一点呢?

3个回答

269

当前状态:

从.NET 6开始,无论是哪个平台,只要安装了时区数据和ICU,都支持两种形式的时区,而这在Windows、Linux和MacOS的大多数安装中都有。请参见Tobias的回答

原始回答:

转换Windows和IANA时区标识符的数据主要来源于windowsZones.xml文件,该文件作为Unicode CLDR项目的一部分进行分发。最新的开发版本可以在这里找到。

然而,CLDR仅每年发布两次。由此产生的问题是, Windows更新的周期,以及IANA时区数据库的不规则更新,使得直接使用CLDR数据变得复杂。请记住,时区更改本身是由世界各国政府随意制定的,并非所有更改都能提前通知,以便在其有效日期之前进入这些发布周期。

还有一些其他边缘情况需要处理,这些情况严格来说不被CLDR覆盖,而且新的情况会不时出现。因此,我已将解决方案的复杂性封装到TimeZoneConverter微型库中,可以从Nuget安装。

使用这个库非常简单。下面是一些转换示例:

string tz = TZConvert.IanaToWindows("America/New_York");
// Result:  "Eastern Standard Time"

string tz = TZConvert.WindowsToIana("Eastern Standard Time");
// result:  "America/New_York"

string tz = TZConvert.WindowsToIana("Eastern Standard Time", "CA");
// result:  "America/Toronto"

在项目网站上有更多示例

需要认识到,虽然一个IANA时区可以映射到单个Windows时区,但反之不成立。单个Windows时区可能映射到多个IANA时区。这可以从以上示例中看出,其中Eastern Standard Time被映射到America/New_YorkAmerica/Toronto。TimeZoneConverter将提供由CLDR标记为"001"的时区,称为“黄金时区”,除非您明确提供国家代码并且在该国家/地区存在不同的时区匹配。

注:此答案随着时间的推移已经发生了变化,因此下面的评论可能与当前版本无关。请查看编辑历史记录以获取详细信息。谢谢。


1
当将 (GMT+05:30) Chennai, Kolkata, Mumbai, New Delhi 转换成 Asia/Calcutta 时,应该使用 Asia/Kolkata 方法。看起来 TzdbDateTimeZoneSource 包含旧的值。 - Anto Subash
1
@MattJohnson 在使用 IanaToWindows 方法转换 Asia/Kolkata 时失败了,但是使用旧名称 Asia/Calcutta 可以正常工作。您已经更新了 WindowsToIana 方法,但是 IanaToWindows 也存在同样的问题。还有一些其他无法工作的区域,如 America/Argentina/Buenos_AiresAmerica/Indiana/IndianapolisAsia/Kathmandu - Anto Subash
1
@AntoJSubash - 再次感谢你的细心观察!我已经修改了IanaToWindows方法来进行补偿。非常感谢! - Matt Johnson-Pint
2
@MattJohnson 我也同意 @sirrocco 的观察。像这样使用规范化的 ID var canonical = tzdbSource.CanonicalIdMap[ ianaZoneId ]; links = Enumerable.Repeat( canonical, 1 ).Concat( links ); 对我很有帮助。 - Johannes Rudolph
2
@sirrocco - 抱歉我没早点看到你的评论。已更新函数。谢谢! - Matt Johnson-Pint
显示剩余23条评论

23

从.NET 6开始,终于可以以跨平台的方式处理时区,因此这些手动解决方案不再需要。

TimeZoneInfo.FindSystemTimeZoneById(string) 方法会自动在任何平台上接受 Windows 或 IANA 时区,并在需要时进行转换。

// Both of these will now work on any supported OS where ICU and time zone data are available.
TimeZoneInfo tzi1 = TimeZoneInfo.FindSystemTimeZoneById("AUS Eastern Standard Time");
TimeZoneInfo tzi2 = TimeZoneInfo.FindSystemTimeZoneById("Australia/Sydney");

请注意,如链接所指定,基于Alpine Linux的.NET Core Docker镜像默认情况下没有安装必要的tzdata,因此必须在您的Dockerfile中安装它才能正确使用。


1
需要注意的是,Windows在Windows 10和11之间更改了其时区ID,使得使用其系统变得脆弱。 - Adam Heeg
@AdamHeeg,你有更具体的信息吗?根据https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/default-time-zones,我没有看到两个版本之间有任何区别,但我没有Windows 11机器来进行比较/验证。同意使用IANA ID,因为更新它们的过程更加透明,但是两者都不免受现实世界时区变化的影响! - Tobias J
我没有文档,只有一个真实的情况。我必须修复我们的代码库,在Windows 11上崩溃,因为一些Windows 10时区ID值在Win 11机器上不再存在。 - Adam Heeg
使用.NET 6 TimeZoneInfo方法,是否有一种方法可以使用IANA国家/城市对来获取更一般的时区?例如,从上面的例子:"Australia/Sydney"获取"AUS Eastern Standard Time"。 - JeffC
@JeffC 我不知道有没有,但你可以创建自己的问题,看看是否有人有办法! - Tobias J
显示剩余2条评论

5

我知道这是一个旧问题,但我有一个使用案例想要在此分享,因为这是我搜索时找到的最相关的帖子。我正在使用docker linux容器开发.NET Core应用程序,但是要在Windows服务器上部署。因此,我只需要我的docker linux容器支持Windows时区名称即可。通过以下方式,我无需更改应用程序代码即可使其正常运行:

cp /usr/share/zoneinfo/America/Chicago "/usr/share/zoneinfo/Central Standard Time"
cp /usr/share/zoneinfo/America/New_York "/usr/share/zoneinfo/Eastern Standard Time"
cp /usr/share/zoneinfo/America/Denver "/usr/share/zoneinfo/Mountain Standard Time"
cp /usr/share/zoneinfo/America/Los_Angeles "/usr/share/zoneinfo/Pacific Standard Time"

然后,在我的.NET代码中,以下内容可以在不做任何修改的情况下正常工作:TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time")


1
这是一些非常出色的创新思维!只要您涵盖了几个特定的时区,这似乎应该没问题。请记住,在美国不止这四个时区。为了覆盖当今50个州,您还需要添加以下链接:将America/Phoenix添加到“US Mountain Standard Time”,将Pacific/Honolulu添加到“Hawaiian Standard Time”,将America/Anchorage添加到“Alaskan Standard Time”,将America/Adak添加到“Aleutian Standard Time”。这并没有涵盖美国领土或历史上的差异,但可以让您开始工作。 - Matt Johnson-Pint
2
如果一个人的目的是覆盖整个世界或处理任何有效的时区标识符,我不建议采用这种方法。该列表过长且过于不稳定。 - Matt Johnson-Pint
如上所述,这个解决方法在.NET 6中已不再需要,但如果您要采用这种方法,建议考虑创建符号链接(例如 ln -s /usr/share/zoneinfo/America/Chicago "/usr/share/zoneinfo/Central Standard Time"),以防源代码被更新。 - Tobias J

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接