来自奥尔森时区的.NET TimeZoneInfo

如何将以下内容转换为 System.TimeZone 或 System.TimeZoneInfo?

{
"timeZone": "America/Los_Angeles",
"currentOffsetMs": -25200000
}

这是我从第三方网络服务中得到的数据。

我假设偏移量是与协调世界时的差异,我被告知“美国/洛杉矶”是一个奥尔森时区。Java 将其解析为 JavaTimeZone 没有问题,但是我需要将其解析为 C # TimeZoneInfo 对象。

59247 次浏览

在将 currentOffsetM 转换为小时和剩余分钟之后,您可以枚举已定义的 TimeZoneInfo 对象:

foreach (TimeZoneInfo nextZone in TimeZoneInfo.GetSystemTimeZones())
{
int nextHours = nextZone.BaseUtcOffset.Hours + 24;     // To prevent negative numbers
int nextMinutes = nextZone.BaseUtcOffset.Minutes;
if (tzHours == nextHours && tzMinutes == nextMinutes)
{
myTimeZoneInfo = nextZone;
break;
}
}

这个 Unicode.org 页面 有一个“ Olson 时区到 Win32时区”表。从那里,我创建了一个不错的小 C # helper 函数,将 Olson 时区字符串映射到。NET TimeZoneInfo:

/// <summary>
/// Converts an Olson time zone ID to a Windows time zone ID.
/// </summary>
/// <param name="olsonTimeZoneId">An Olson time zone ID. See http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/zone_tzid.html. </param>
/// <returns>
/// The TimeZoneInfo corresponding to the Olson time zone ID,
/// or null if you passed in an invalid Olson time zone ID.
/// </returns>
/// <remarks>
/// See http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/zone_tzid.html
/// </remarks>
public static TimeZoneInfo OlsonTimeZoneToTimeZoneInfo(string olsonTimeZoneId)
{
var olsonWindowsTimes = new Dictionary<string, string>()
{
{ "Africa/Bangui", "W. Central Africa Standard Time" },
{ "Africa/Cairo", "Egypt Standard Time" },
{ "Africa/Casablanca", "Morocco Standard Time" },
{ "Africa/Harare", "South Africa Standard Time" },
{ "Africa/Johannesburg", "South Africa Standard Time" },
{ "Africa/Lagos", "W. Central Africa Standard Time" },
{ "Africa/Monrovia", "Greenwich Standard Time" },
{ "Africa/Nairobi", "E. Africa Standard Time" },
{ "Africa/Windhoek", "Namibia Standard Time" },
{ "America/Anchorage", "Alaskan Standard Time" },
{ "America/Argentina/San_Juan", "Argentina Standard Time" },
{ "America/Asuncion", "Paraguay Standard Time" },
{ "America/Bahia", "Bahia Standard Time" },
{ "America/Bogota", "SA Pacific Standard Time" },
{ "America/Buenos_Aires", "Argentina Standard Time" },
{ "America/Caracas", "Venezuela Standard Time" },
{ "America/Cayenne", "SA Eastern Standard Time" },
{ "America/Chicago", "Central Standard Time" },
{ "America/Chihuahua", "Mountain Standard Time (Mexico)" },
{ "America/Cuiaba", "Central Brazilian Standard Time" },
{ "America/Denver", "Mountain Standard Time" },
{ "America/Fortaleza", "SA Eastern Standard Time" },
{ "America/Godthab", "Greenland Standard Time" },
{ "America/Guatemala", "Central America Standard Time" },
{ "America/Halifax", "Atlantic Standard Time" },
{ "America/Indianapolis", "US Eastern Standard Time" },
{ "America/Indiana/Indianapolis", "US Eastern Standard Time" },
{ "America/La_Paz", "SA Western Standard Time" },
{ "America/Los_Angeles", "Pacific Standard Time" },
{ "America/Mexico_City", "Mexico Standard Time" },
{ "America/Montevideo", "Montevideo Standard Time" },
{ "America/New_York", "Eastern Standard Time" },
{ "America/Noronha", "UTC-02" },
{ "America/Phoenix", "US Mountain Standard Time" },
{ "America/Regina", "Canada Central Standard Time" },
{ "America/Santa_Isabel", "Pacific Standard Time (Mexico)" },
{ "America/Santiago", "Pacific SA Standard Time" },
{ "America/Sao_Paulo", "E. South America Standard Time" },
{ "America/St_Johns", "Newfoundland Standard Time" },
{ "America/Tijuana", "Pacific Standard Time" },
{ "Antarctica/McMurdo", "New Zealand Standard Time" },
{ "Atlantic/South_Georgia", "UTC-02" },
{ "Asia/Almaty", "Central Asia Standard Time" },
{ "Asia/Amman", "Jordan Standard Time" },
{ "Asia/Baghdad", "Arabic Standard Time" },
{ "Asia/Baku", "Azerbaijan Standard Time" },
{ "Asia/Bangkok", "SE Asia Standard Time" },
{ "Asia/Beirut", "Middle East Standard Time" },
{ "Asia/Calcutta", "India Standard Time" },
{ "Asia/Colombo", "Sri Lanka Standard Time" },
{ "Asia/Damascus", "Syria Standard Time" },
{ "Asia/Dhaka", "Bangladesh Standard Time" },
{ "Asia/Dubai", "Arabian Standard Time" },
{ "Asia/Irkutsk", "North Asia East Standard Time" },
{ "Asia/Jerusalem", "Israel Standard Time" },
{ "Asia/Kabul", "Afghanistan Standard Time" },
{ "Asia/Kamchatka", "Kamchatka Standard Time" },
{ "Asia/Karachi", "Pakistan Standard Time" },
{ "Asia/Katmandu", "Nepal Standard Time" },
{ "Asia/Kolkata", "India Standard Time" },
{ "Asia/Krasnoyarsk", "North Asia Standard Time" },
{ "Asia/Kuala_Lumpur", "Singapore Standard Time" },
{ "Asia/Kuwait", "Arab Standard Time" },
{ "Asia/Magadan", "Magadan Standard Time" },
{ "Asia/Muscat", "Arabian Standard Time" },
{ "Asia/Novosibirsk", "N. Central Asia Standard Time" },
{ "Asia/Oral", "West Asia Standard Time" },
{ "Asia/Rangoon", "Myanmar Standard Time" },
{ "Asia/Riyadh", "Arab Standard Time" },
{ "Asia/Seoul", "Korea Standard Time" },
{ "Asia/Shanghai", "China Standard Time" },
{ "Asia/Singapore", "Singapore Standard Time" },
{ "Asia/Taipei", "Taipei Standard Time" },
{ "Asia/Tashkent", "West Asia Standard Time" },
{ "Asia/Tbilisi", "Georgian Standard Time" },
{ "Asia/Tehran", "Iran Standard Time" },
{ "Asia/Tokyo", "Tokyo Standard Time" },
{ "Asia/Ulaanbaatar", "Ulaanbaatar Standard Time" },
{ "Asia/Vladivostok", "Vladivostok Standard Time" },
{ "Asia/Yakutsk", "Yakutsk Standard Time" },
{ "Asia/Yekaterinburg", "Ekaterinburg Standard Time" },
{ "Asia/Yerevan", "Armenian Standard Time" },
{ "Atlantic/Azores", "Azores Standard Time" },
{ "Atlantic/Cape_Verde", "Cape Verde Standard Time" },
{ "Atlantic/Reykjavik", "Greenwich Standard Time" },
{ "Australia/Adelaide", "Cen. Australia Standard Time" },
{ "Australia/Brisbane", "E. Australia Standard Time" },
{ "Australia/Darwin", "AUS Central Standard Time" },
{ "Australia/Hobart", "Tasmania Standard Time" },
{ "Australia/Perth", "W. Australia Standard Time" },
{ "Australia/Sydney", "AUS Eastern Standard Time" },
{ "Etc/GMT", "UTC" },
{ "Etc/GMT+11", "UTC-11" },
{ "Etc/GMT+12", "Dateline Standard Time" },
{ "Etc/GMT+2", "UTC-02" },
{ "Etc/GMT-12", "UTC+12" },
{ "Europe/Amsterdam", "W. Europe Standard Time" },
{ "Europe/Athens", "GTB Standard Time" },
{ "Europe/Belgrade", "Central Europe Standard Time" },
{ "Europe/Berlin", "W. Europe Standard Time" },
{ "Europe/Brussels", "Romance Standard Time" },
{ "Europe/Budapest", "Central Europe Standard Time" },
{ "Europe/Dublin", "GMT Standard Time" },
{ "Europe/Helsinki", "FLE Standard Time" },
{ "Europe/Istanbul", "GTB Standard Time" },
{ "Europe/Kiev", "FLE Standard Time" },
{ "Europe/London", "GMT Standard Time" },
{ "Europe/Minsk", "E. Europe Standard Time" },
{ "Europe/Moscow", "Russian Standard Time" },
{ "Europe/Paris", "Romance Standard Time" },
{ "Europe/Sarajevo", "Central European Standard Time" },
{ "Europe/Warsaw", "Central European Standard Time" },
{ "Indian/Mauritius", "Mauritius Standard Time" },
{ "Pacific/Apia", "Samoa Standard Time" },
{ "Pacific/Auckland", "New Zealand Standard Time" },
{ "Pacific/Fiji", "Fiji Standard Time" },
{ "Pacific/Guadalcanal", "Central Pacific Standard Time" },
{ "Pacific/Guam", "West Pacific Standard Time" },
{ "Pacific/Honolulu", "Hawaiian Standard Time" },
{ "Pacific/Pago_Pago", "UTC-11" },
{ "Pacific/Port_Moresby", "West Pacific Standard Time" },
{ "Pacific/Tongatapu", "Tonga Standard Time" }
};


var windowsTimeZoneId = default(string);
var windowsTimeZone = default(TimeZoneInfo);
if (olsonWindowsTimes.TryGetValue(olsonTimeZoneId, out windowsTimeZoneId))
{
try { windowsTimeZone = TimeZoneInfo.FindSystemTimeZoneById(windowsTimeZoneId); }
catch (TimeZoneNotFoundException) { }
catch (InvalidTimeZoneException) { }
}
return windowsTimeZone;
}

您可能希望查看 Jon Skeet 的 野田佳彦并完全放弃 TimeZoneInfo。野田-时间使用奥尔森时区,所以你的地图将是一块蛋糕。你使用它还有其他原因:

日期时间到底怎么了?

所以我的问题是,结果是使用野田时间

我想出的一个小代码片段,用于从 http://unicode.org/repos/cldr/trunk/common/supplemental/windowsZones.xml的 xml 获取 olson 到 Windows 时区映射的列表

private static void LoadMappingsO()
{
var file = new FileInfo("windowsZones.xml");
if (!file.Exists)
{
return;
}


var map = new Dictionary<string, string>();
using (var reader = file.OpenText())
{
var readerSettings = new XmlReaderSettings { XmlResolver = null, ProhibitDtd = false };


using (var xmlReader = XmlReader.Create(reader, readerSettings))
{
var document = new XPathDocument(xmlReader);
var navigator = document.CreateNavigator();


var nodes = navigator.Select("/supplementalData/windowsZones/mapTimezones/mapZone");


while (nodes.MoveNext())
{
var node = nodes.Current;
if (node == null) continue;


var olsonNames = node.GetAttribute("type", "").Split(' ');
var windowsName = node.GetAttribute("other", "");
foreach (var olsonName in olsonNames)
{
if (!map.ContainsKey(olsonName))
{
map.Add(olsonName, windowsName);
}
}
}
}
}


using (TextWriter tw = new StreamWriter("dict.txt", false))
{
foreach (var key in map.Keys)
{
tw.WriteLine(string.Format("\{\{\"{0}\", \"{1}\"}},", key, map[key]));
}
}
}

更新(使用 LinqXml) :

private static void LoadMappings()
{
var map = new Dictionary<string, string>();
var xdoc = XDocument.Load("windowsZones.xml");


var zones = xdoc.XPathSelectElements("/supplementalData/windowsZones/mapTimezones/mapZone");
foreach (var zone in zones)
{
var olsonNames = zone.Attribute("type")?.Value.Split(' ');
if (olsonNames == null)
continue;


var windowsName = zone.Attribute("other")?.Value;
if (string.IsNullOrWhiteSpace(windowsName))
continue;


foreach (var olsonName in olsonNames)
{
map[olsonName] = windowsName;
}
}


using (TextWriter tw = new StreamWriter("dict.txt", false))
{
foreach (var key in map.Keys)
{
tw.WriteLine($"\{\{\"{key}\", \"{map[key]}\"}},");
}
}
}

更新: 我已经从脚本中删除了 URL。请手动来源文件。这个脚本并不打算在 unicode.org 上不断运行不必要的加载。请看下面的评论。

这个 Powershell 脚本可以用来使用 unicode.org 中的当前 XML 文件生成 case 语句。它生成从 IANA 名称到 TimeZoneInfold 的映射。

    # Download the xml file.
$xml = [Xml] /// Load the XML content here


# Parse the fields we want from the XML.
$mappings1 = $xml.supplementalData.windowsZones.mapTimezones.mapZone | select Type,Other


# Extrapolate extra rows for entries that contain more than one IANA name seperated by spaces.
# Example: |<mapZone other="Alaskan Standard Time" territory="US" type="America/Anchorage America/Juneau America/Nome America/Sitka America/Yakutat"/>
$mappings2 = $mappings1 | %{
$mapping = $_
$_.Type.Split(" ") | %{
New-Object PSObject -Property @{type = $_; other = $mapping.other}
}
}


# Remove dup's
$mappings3 = $mappings2 | sort type -Unique


# Generate the case statements.
$mappings3 | %{ [String]::Format("case @""{0}"": return @""{1}"";", $_.Type, $_.Other)}

下面是使用 NodaTime的反向映射(tzdb-> windows)函数:

using NodaTime;
using NodaTime.TimeZones;


...


public TimeZoneInfo GetTimeZoneInfoForTzdbId(string tzdbId)
{
var mappings = TzdbDateTimeZoneSource.Default.WindowsMapping.MapZones;
var map = mappings.FirstOrDefault(x =>
x.TzdbIds.Any(z => z.Equals(tzdbId, StringComparison.OrdinalIgnoreCase)));
return map == null ? null : TimeZoneInfo.FindSystemTimeZoneById(map.WindowsId);
}

注意,可能存在多个映射(在这种情况下,它只使用找到的第一个映射) ,或者根本不存在映射(其中返回 null)。

在大多数常用的时区,这应该足够好的工作。但是更好的解决方案是完全跳过使用 TimeZoneInfo,只在整个应用程序中使用 NodaTime,直接使用您所拥有的 TZDB 区域。

参见: 如何在 Windows 和 IANA 时区之间进行转换?