.NET TimeZoneInfo de la zona horaria de Olson

¿Cómo puedo convertir lo siguiente en System.TimeZone o System.TimeZoneInfo?

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

Estos son los datos que obtengo de un servicio web de terceros.

Supongo que el desplazamiento es la diferencia con UTC, y me dijeron que el “America / Los_Angeles” es un huso horario de Olson. Java no tiene problemas para analizar esto en Java TimeZone, pero necesito analizar esto en un objeto C # TimeZoneInfo.

Esta página de Unicode.org tiene una tabla de “zona horaria de Olson a zona horaria de Win32”. A partir de ahí, creé una pequeña y agradable función auxiliar C # para mapear desde la cadena de zona horaria de Olson a .NET TimeZoneInfo:

 ///  /// Converts an Olson time zone ID to a Windows time zone ID. ///  /// An Olson time zone ID. See http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/zone_tzid.html.  ///  /// The TimeZoneInfo corresponding to the Olson time zone ID, /// or null if you passed in an invalid Olson time zone ID. ///  ///  /// See http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/zone_tzid.html ///  public static TimeZoneInfo OlsonTimeZoneToTimeZoneInfo(string olsonTimeZoneId) { var olsonWindowsTimes = new Dictionary() { { "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; } 

Aquí hay una función de mapeo inverso (tzdb -> windows) usando NodaTime :

 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); } 

Tenga en cuenta que es posible que haya más de un mapeo (en cuyo caso esto solo usa el primero encontrado), o no hay mapeo (donde esto devuelve nulo).

En las zonas horarias más comúnmente utilizadas, esto debería funcionar bastante bien. Pero la mejor solución sería omitir el uso de TimeZoneInfo y simplemente usar NodaTime en toda la aplicación, directamente con la zona TZDB que tenga.

Vea también: ¿Cómo traducir entre las zonas horarias de Windows y IANA?

Es posible que desee examinar Noda-Time de Jon Skeet y abandonar TimeZoneInfo por completo. Noda-Time usa los husos horarios de Olson, por lo que su mapeo sería pan comido. Hay otras razones por las que puede querer usarlo:

¿Qué pasa con DateTime de todos modos?

ASÍ la pregunta mía cuyo resultado fue usar Noda-Time

Un pequeño fragmento que se me ocurrió para obtener una lista de olson a las asignaciones de zonas horarias de Windows desde xml en http://unicode.org/repos/cldr/trunk/common/supplemental/windowsZones.xml

 private static void LoadMappingsO() { var file = new FileInfo("windowsZones.xml"); if (!file.Exists) { return; } var map = new Dictionary(); 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])); } } } 

ACTUALIZACIÓN (utilizando Linq Xml):

 private static void LoadMappings() { var map = new Dictionary(); 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]}\"}},"); } } } 

ACTUALIZACIÓN: he eliminado la URL del script. Por favor fuente el archivo de forma manual. Esta secuencia de comandos no fue pensada para ejecutarse constantemente, lo que genera una carga innecesaria en unicode.org. Ver los comentarios a continuación.

Este script de Powershell se puede usar para generar una statement de caso usando el archivo XML actual de unicode.org. Genera asignaciones de nombres de IANA a TimeZoneInfoId.

  # 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: | $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)} 

Después de convertir los currentOffsetMs en horas y minutos restantes, puede enumerar los objetos TimeZoneInfo definidos:

 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; } }