2011-05-13 58 views
55

如何將以下內容轉換爲System.TimeZone或System.TimeZoneInfo?來自Olson時區的.NET TimeZoneInfo

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

這是我從第三方Web服務獲取的數據。

我假設偏移量是與UTC的差值,並且我被告知「America/Los_Angeles」是奧爾森時區。 Java在解析Java時區時沒有問題,但我需要將它解析爲C#TimeZoneInfo對象。

+1

沒有該名稱的時區。你得到的偏移量也沒有多大幫助,它只會告訴你偏移量是從UTC開始的-7小時。找到洛杉磯所在的太平洋時區並不是非常有用,它與UTC的偏差爲-8小時。由於夏令時有效,您將獲得-7。這將會改變。如果必須的話,你可以使用這個列表來翻譯:http://en.wikipedia.org/wiki/List_of_tz_database_time_zones – 2011-05-13 20:45:49

+1

TZ數據庫列表也可以作爲.NET庫使用:codeplex.com/zoneinfo但是,沒有幫助;它不會返回任何可映射回標準.NET TimeZoneInfo的東西。 GRRRR。 – 2011-05-16 17:08:33

+5

@HansPassant - America/Los_Angeles是來自IANA時區數據庫https://en.wikipedia.org/wiki/America/Los_Angeles的時區標識符。 – Matt 2014-10-03 14:10:48

回答

82

This Unicode.org page具有「奧爾森時區的Win32時區」表。從那裏,我創建了一個可愛的小C#輔助函數來從奧爾森時區字符串映射到.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; 
} 
+0

不幸的是,這張表是不完整的。 unicode.org提供的是Windows TZ - > Olson映射,而不是相反。奧爾森還有許多meta名稱,例如歐洲/蘇黎世,這些名稱沒有被反向映射所覆蓋。 – 2012-01-05 10:47:44

+0

很高興知道。你知道奧爾森名字的全面清單嗎?我很樂意更新這篇文章。 – 2012-01-05 15:36:06

+0

有一個更完整的列表在http://stackoverflow.com/questions/8372537/converting-olson-tzid-to-windows-time-zone ...我目前正在驗證其完整性和準確性的過程。 – 2012-01-05 21:34:04

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

有幫助,但不夠精確。多個時區具有相同的偏移量「UTC-07:00 Chihuahua,La Paz,Mazatlan」和「UTC-07:00 Arizona」具有相同的偏移量。 – 2011-05-13 19:21:31

11

我想出了一個小片段在http://unicode.org/repos/cldr/trunk/common/supplemental/windowsZones.xml

得到奧爾森從XML Windows時區映射列表(使用LINQ 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:

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]}\"}},"); 
     } 
    } 
} 
+0

不錯。我不知道它是否可以通過LINQ到XML更清潔。但是,謝謝。 – 2012-03-02 16:31:44

+2

@Hosney是的,但請不要編寫自動從unicode.org獲取文件的代碼。下載CLDR版本並將文件包含在您的應用中。此URL現在正在下載濫用。 – 2014-07-02 23:16:22

+0

該代碼不會從unicode.org獲取文件。正如你所看到的,代碼使用FileInfo來讀取本地存儲在應用程序路徑中的xml文件。 – H77 2014-07-03 00:45:14

2

更新:我已經從腳本中刪除了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)} 
+3

只是給你和任何讀者的說明 - 請不要無條件地下載這個XML文件。它正在下載濫用。真的,手動下載一次並使用它。謝謝。 – 2014-07-03 01:48:58

+2

@Jared;請介意將此代碼片斷直接從unicode.org SVN中取出'windowsZones.xml'嗎? (可以從磁盤讀取它或假定用戶手動下載)。目前,該文件的直接獲取會佔用他們網絡帶寬的很大一部分。這個請求剛剛在幾天前被加入了數百萬次點擊。這表明某人的代碼正在迭代很多次。雖然它不能解決這些命中的問題(因爲誰這樣做可能不會看到這個消息),但它會阻止未來發生這種情況發生。謝謝。 – Shervin 2014-07-03 01:49:35

+2

嘿, 我已經從腳本中刪除了該URL。它從來不打算經常運行。我個人只運行一次,每6個月左右一次。 – 2014-07-03 05:52:54

22

這裏是一個反向映射(TZDB - >視窗)使用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); 
} 

注意,這是有可能存在一個以上的映射(在這種情況下,這只是使用的第一個找到),或根本沒有映射(在這裏返回null)。

在最常用的時區,這應該工作得很好。但更好的解決方案是跳過使用TimeZoneInfo,並在整個應用程序中直接使用NodaTime,直接使用TZDB區域。

另請參閱:How to translate between Windows and IANA time zones?