首页 文章

来自Olson时区的.NET TimeZoneInfo

提问于
浏览
61

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

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

这是我从第三方Web服务获得的数据 .

我假设偏移是与UTC的差异,我被告知“America / Los_Angeles”是奥尔森时区 . Java将此解析为Java TimeZone没有问题,但我需要将其解析为C#TimeZoneInfo对象 .

6 回答

  • 25

    This Unicode.org page有一个"Olson time zone to Win32 time zone"表 . 从那里,我创建了一个很好的小C#帮助函数,从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;
    }
    
  • 11

    这是使用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区域 .

    另见:How to translate between Windows and IANA time zones?

  • 3

    您可能想要查看Jon Skeet的Noda-Time并完全放弃TimeZoneInfo . Noda-Time使用Olson时区,因此您的映射将是一块蛋糕 . 您可能还想使用它的其他原因:

    What's wrong with DateTime anyway?

    SO question of mine the outcome of which was to use Noda-Time

  • 11

    我想出了一个小片段来获取一个olson到Windows时区映射的列表来自xml的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<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]));
            }
        }
    }
    

    UPDATE (Using Linq Xml):

    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]}\"}},");
            }
        }
    }
    
  • 93

    更新:我已从脚本中删除了URL . 请手动获取文件 . 此脚本无意在unicode.org上不断运行不必要的负载 . 请参阅以下评论 .

    此Powershell脚本可用于使用unicode.org中的当前XML文件生成case语句 . 它生成从IANA名称到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: |<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)}
    
  • 2

    将currentOffsetMs转换为小时和剩余分钟后,您可以枚举定义的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;
        }
    }
    

相关问题