201

As described in the timezone tag wiki, there are two different styles of time zones.

  • Those provided by Microsoft for use with Windows and the .Net TimeZoneInfo class (when running on Windows) are identified by a value such as "Eastern Standard Time".

  • Those provided by IANA in the TZDB, and used by the .NET TimeZoneInfo class when running on Linux or OSX, are identified by a value such as "America/New_York".

Many Internet-based APIs use the IANA time zones, but for numerous reasons one might need to convert this to a Windows time zone id, or vice-versa.

How can this be accomplished in .Net?

Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575

3 Answers3

263

Current Status:

Starting with .NET 6, both forms of time zones are supported on any platform that has both time zone data and ICU installed, which is most installations of Windows, Linux, and MacOS. See Tobias's answer.

Original Answer:

The primary source of the data for conversion between Windows and IANA time zone identifiers is the windowsZones.xml file, distributed as part of the Unicode CLDR project. The latest dev version can be found here.

However, CLDR is released only twice annually. This, along with the periodic cadence of Windows updates, and the irregular updates of the IANA time zone database, makes it complicated to just use the CLDR data directly. Keep in mind that time zone changes themselves are made at the whim of the world's various governments, and not all changes are made with sufficient notice to make it into these release cycles before their respective effective dates.

There are a few other edge cases that need to be handled that are not covered strictly by the CLDR, and new ones pop up from time to time. Therefore, I've encapsulated the complexity of the solution into the TimeZoneConverter micro-library, which can be installed from Nuget.

Using this library is simple. Here are some examples of conversion:

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"

There are more examples on the project site.

It's important to recognize that while an IANA time zone can be mapped to a single Windows time zone, the reverse is not true. A single Windows time zone might be mapped to more than one IANA time zone. This can be seen in the above examples, where Eastern Standard Time is mapped to both America/New_York, and to America/Toronto. TimeZoneConverter will deliver the one that CLDR marks with "001", known as the "golden zone", unless you specifically provide a country code and there's a match for a different zone in that country.

Note: This answer has evolved over the years, so comments below may or may not apply to the current revision. Review the edit history for details. Thanks.

Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
  • what's the nodatime version referred to? – shyamnathan Jan 31 '14 at 17:58
  • @shyamnathan - This requires Noda Time version 1.1.0 or greater. – Matt Johnson-Pint Jan 31 '14 at 19:02
  • 1
    using this method when converting `(GMT+05:30) Chennai, Kolkata, Mumbai, New Delhi` gives `Asia/Calcutta` it should be `Asia/Kolkata`. it seems like the `TzdbDateTimeZoneSource` contains old values. – Anto Subash Mar 17 '14 at 12:06
  • @AntoJSubash - Good observation! The CLDR mappings don't necessarily follow links to the canonical IDs. (They intentionally require them to be fixed once set.) Fortunately, Noda Time has all the data it needs to resolve the canonical ID for any alias. I updated the answer above to do just that. Now you should find that `WindowsToIana("India Standard Time")` returns `Asia/Kolkata`. – Matt Johnson-Pint Mar 17 '14 at 15:55
  • 1
    @MattJohnson while converting the `Asia/Kolkata` using `IanaToWindows` method, it fails. but it works with `Asia/Calcutta` which is the old name.you have updated the method `WindowsToIana` but `IanaToWindows` also has the same problem. few other zones which are not working are `America/Argentina/Buenos_Aires`,`America/Indiana/Indianapolis` ,`Asia/Kathmandu`. – Anto Subash Mar 26 '14 at 08:59
  • 1
    @AntoJSubash - Again, great observation! I have edited the `IanaToWindows` method to compensate. Thanks very much! – Matt Johnson-Pint Mar 26 '14 at 17:38
  • @JonSkeet - Perhaps these should be in Noda Time vNext? – Matt Johnson-Pint Mar 26 '14 at 17:39
  • 1
    @MattJohnson: I think I'd probably add them to WindowsMapping... file a feature request and I'll take a closer look when I get the chance :) – Jon Skeet Mar 26 '14 at 17:46
  • 1
    I ran into an issue if the timezone name was "US/Mountain" - the links collection was empty. So when that happens I just revert the search - searching keys and getting the values. That worked for me - I don't know if it's the best approach – sirrocco Mar 12 '15 at 15:26
  • 2
    @MattJohnson I second @sirrocco's observation. Using the canonical id too like `var canonical = tzdbSource.CanonicalIdMap[ ianaZoneId ]; links = Enumerable.Repeat( canonical, 1 ).Concat( links );` did the trick for me. – Johannes Rudolph Jun 01 '15 at 15:46
  • @JohannesRudolph Thanks for the feedback! I've updated the functions accordingly. – Matt Johnson-Pint Jun 01 '15 at 17:21
  • 2
    @sirrocco - Sorry I didn't see your comment sooner. Updated the functions. Thanks! – Matt Johnson-Pint Jun 01 '15 at 17:21
  • @JonSkeet MattJohnson hi guys, I need this right now and found this thread from 2014. What's the status now? Any complete IanaToWindows method directly in the NodaTime? Thanks! – rouen Jul 01 '15 at 14:49
  • @rouen: No, there's nothing new in Noda Time at the moment for this - you can use this code though. – Jon Skeet Jul 01 '15 at 14:57
  • @JonSkeet Thanks for info. I am bit confused about versions. At github I can see 1.3.x as active branch and at nuget there is 2.0.0-alpha. Where should I get the latest stable library from? – rouen Jul 01 '15 at 15:03
  • @rouen: Use 1.3.x - as per the "alpha" designation, 2.0.0 isn't stable yet. – Jon Skeet Jul 01 '15 at 15:05
  • 1
    I see a few cases when taking timezone Ids from android/iOS that are not converted to windows timezones. For instance, "Asia/Gaza", "Asia/Hebron". Am I missing something? Any pointers are greatly appreciated. – Rohan Aug 12 '15 at 09:51
  • Rohan is right, also "Asia/Calcutta" and "Asia/Saigon" (both returned by android app) wont be converted. I currently maintain my own dictionary of these exceptions and "translate" (to "Asia/Kolkata" and "Asia/Ho_Chi_Minh" respectively) before conversion, but I would like this to be fixed in library eventually. – rouen Sep 02 '15 at 10:38
  • 1
    Great solution, however I've found that IanaToWindows was pretty slow and did some minor improvements that made it more than 100x times faster on my machine. I've tried to do the edit but it was rejected. So if someone will have similar performance issues here is the possible solution: http://stackoverflow.com/review/suggested-edits/9542560 – sarh Sep 17 '15 at 16:04
  • 1
    @sarh - Yes, I rejected it, and actually updated the map with one more missing piece. The edit you propose may be faster, but eliminates a class of mappings. If speed is your primary concern, simply iterate through this function for all zones and save/cache the results in a dictionary. Then use the dictionary at run time. – Matt Johnson-Pint Sep 17 '15 at 16:05
  • The goal of my edit was to avoid ContainsKey&GetByKey dictionary anti-pattern and to make it a bit faster. I didn't even think that it will give more than x100 speed-up. No need in cache if acceptable performance can be obtained with a minor code cleanup. – sarh Sep 18 '15 at 08:32
  • Here is my final version with your fix: http://pastebin.com/hXGgxTFv. BTW, thanks for update - it reduced number of unresolved android timezones from 53 to 39. – sarh Sep 18 '15 at 08:46
  • Thanks I used this database along with some other things to create a bash script: https://gist.github.com/CMCDragonkai/a53df086cce25319d7530eb4b2ca1da9 – CMCDragonkai Aug 12 '16 at 07:41
  • Given a .NET `TimeZone` or `TimeZoneInfo` how do I know if I need to pass the second `territoryCode` parameter and what value to use? – xr280xr Aug 06 '21 at 02:41
  • Important note that it isn't guaranteed to always work. While it worked fine when locally developing, deploying to Windows Server 2019 Datacenter with .Net 6 or 7 installed it can't find the IANA names as ICU support was not present - as noted [here](https://github.com/dotnet/docs/issues/30319). – Michael Brown Nov 20 '22 at 23:01
  • Adding a [direct link](https://github.com/dotnet/runtime/issues/62329#issuecomment-985704238) to the workaround needed if running Server Windows 2016/2019. Unfortunately it tripled the size of my application deployment, but it does indeed work. – Michael Brown Nov 20 '22 at 23:31
23

Starting with .NET 6, it is finally possible to work with time zones in a cross-platform manner, so these manual workarounds are no longer needed.

The TimeZoneInfo.FindSystemTimeZoneById(string) method automatically accepts either Windows or IANA time zones on either platform and converts them if needed.

// 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");

Note that, as specified on the link, the .NET Core Alpine Linux-based Docker images do not have the necessary tzdata installed by default, so it must be installed in your Dockerfile for this to work correctly.

Tobias J
  • 19,813
  • 8
  • 81
  • 66
  • 1
    should be noted that windows has changed their time zone ID between windows 10 and 11, making using their system fragile. – Adam Heeg Jan 17 '22 at 15:47
  • @AdamHeeg do you have more specifics on this? Based on https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/default-time-zones I'm not seeing any differences between the two versions but I don't have a Windows 11 machine to compare/verify. Agree that IANA IDs are preferred since the process of updating them is more transparent, but neither is immune to changes in real-world time zones! – Tobias J Jan 20 '22 at 16:54
  • I do not have documentation, only a real world situation. I had to fix our code base which crashed in windows 11 because some of the windows 10 time zone id values no longer existed on the win 11 machine. – Adam Heeg Feb 24 '22 at 12:12
  • With the .NET 6 TimeZoneInfo methods, is there a way to use the IANA Country/City pair to get the more general timezone? Example from above: "Australia/Sydney" to get "AUS Eastern Standard Time" – JeffC Mar 15 '23 at 22:32
  • @JeffC not that I know of but you may want to create your own question to see if anyone has a way! – Tobias J Mar 16 '23 at 04:16
5

I know this is an old question, but I had a use case I though I would share here, since this is the most relevant post I found when searching. I was developing a .NET Core app using a docker linux container, but for deployment on a windows server. So I only needed my docker linux container to support the windows timezone names. I got this working without changing my application code by doing the following:

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"

Then, in my .NET code, the following worked without any modification: TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time")

EverPresent
  • 1,903
  • 17
  • 21
  • 1
    That's some pretty great out-of-the box thinking! This seems like it should be ok, as long as you are covering a few specific time zones. Do keep in mind that there are more than those four in the US. To cover the 50 states in present day, you'll also need to add links for `America/Phoenix` to `"US Mountain Standard Time"`, `Pacific/Honolulu` to `"Hawaiian Standard Time"`,`America/Anchorage` to `"Alaskan Standard Time"`, and `America/Adak` to `"Aleutian Standard Time"`. That doesn't cover US territories or historical discrepancies, but will get you started. – Matt Johnson-Pint Jul 25 '19 at 19:08
  • 2
    I wouldn't recommend this approach if one's intent is to cover the whole world or to deal with any valid time zone identifier. The list is too long and too volatile for that. – Matt Johnson-Pint Jul 25 '19 at 19:08
  • As mentioned above, this workaround is no longer needed with .NET 6, but if you're going to take this approach you may want to consider creating a symlink (e.g. `ln -s /usr/share/zoneinfo/America/Chicago "/usr/share/zoneinfo/Central Standard Time"`) instead, just in case the source gets updated. – Tobias J Oct 28 '21 at 00:26