.Net Timezoneinfo from Olson Time Zone

.NET TimeZoneInfo from Olson time zone

This Unicode.org page has a "Olson time zone to Win32 time zone" table. From there, I created a nice little C# helper function to map from Olson time zone string into a .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;
}

How to convert a time and olson timezone to the time in another olson timezone?

Observe:

var tzdb = DateTimeZoneProviders.Tzdb;

var zone1 = tzdb["America/New_York"];
var ldt1 = new LocalDateTime(2013, 3, 4, 8, 0); // March 4th, 2013 - 8:00 AM
var zdt1 = zone1.AtLeniently(ldt1);

var zone2 = tzdb["America/Los_Angeles"];
var zdt2 = zdt1.ToInstant().InZone(zone2);
var ldt2 = zdt2.LocalDateTime;

Notice the call to AtLeniently - that's because you don't have enough information to be absolutely certain of the moment in time you are talking about. For example, if you were talking about 1:30 AM on the day of a DST fall-back transition, you wouldn't know if you were talking about before or after the transition. AtLeniently will make the assumption you meant after. If you don't want that behavior, you have to provide an offset so you know which local time you were talking about.

The actual conversion is being done by ToInstant which is getting the UTC moment you're talking about, and then InZone which is applying it to the target zone.

TimeZoneInfo vs. Olson database

The unicode consortium keeps a mapping between Olson database and Windows TimeZone Ids, which can be accessed here.

Also see this SO question and answers for more daylight savings info (Daylight saving time - do and don’ts).

Convert .Net TimeZoneInfo to POSIX time zone

I've just added support for POSIX time zone strings as an add-on for my TimeZoneConverter project. It is in a separate package called TimeZoneConverter.Posix. Install that, and you can do the following:

string posix = PosixTimeZone.FromTimeZoneInfo(SomeTimeZoneInfoObject);

Also, you can simply provide either a Windows time zone ID or an IANA time zone name, and that will work also:

string posix = PosixTimeZone.FromWindowsTimeZoneId("Eastern Standard Time");
// Result: "EST5EDT,M3.2.0,M11.1.0"

string posix = PosixTimeZone.FromIanaTimeZoneName("Australia/Sydney");
// Result: "AEST-10AEDT,M10.1.0,M4.1.0/3"

Keep in mind that you are still subject to the limitations of POSIX time zones:

  • They can only support two transitions per year.
  • Not all time zone transitions are for DST. Some are for changes in standard time, and that isn't reflected here. They will look like DST transitions.
  • They don't provide any historical changes unto themselves.

On the last point, the samples shown above use the current year, but there are also overloads to supply a year to generate a string specific to that year.

Time Conversion with TimeZoneInfo for past years

Does the TimeZoneInfo.ConvertTimeFromUtc method read the daylight saving settings from the registry HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zone to calculate the local time?

Yes, except the last key name is Time Zones (you dropped an s). You can read about how this works in this MSDN blog article.

If so, does it work for only the current time and not for any past years or dates?

It will work for any past years that exist in the Windows registry data. You can examine each subkey in the registry to see how far back you can convert for each zone. However, there are various errors and omissions within the data. In general, the historical accuracy of this data is not near as good as other sources such as the IANA/Olson time zone database, which you can use via Noda Time. Read more in the timezone tag wiki.

I would like to know if I can convert a past date time in 'Eastern Standard Time' like '2-1-2010 3:00 PM' to GMT or UTC which follows the DLS (Day Light Saving) for the year 2010? Take another year like '2-1-2006 3:00 PM' as an example also. As you read here, the DST has changed in 2007 for the US.

  • The term is "daylight saving time" - no capital letters, one word for daylight, abbreviated DST

  • Yes, Windows time zone data, and TimeZoneInfo take DST into account.

  • To convert to UTC, you use ConvertTimeToUtc, not ConvertTimeFromUtc.

  • Yes, Windows knows about the 2007 DST change in the US. It does not however, know about earlier changes, such as values before the Uniform Time Act went into effect in 1987. For that, you need the IANA/Olson database.

May I ask, what is the best solution if I have to display an appointment time from a SQL database in two different locals like Malaysia and New York (USA Eastern Standard Time) for a visitor in the past 5 years?

  • Using TimeZoneInfo use the ID "Eastern Standard Time" ID for New York, and the "Singapore Standard Time" ID for Malaysia.

  • Using IANA/Olson time zones with Noda Time, use "America/New_York", and "Asia/Kuala_Lumpur".

  • Either option is fine for the last 5 years you asked about. Malaysia's last time zone change was in 1982.

  • For past events, store UTC times in your database, or store DateTimeOffset values with the correct time and offset for the time zone of the event (See DateTime vs DateTimeOffset). Convert from UTC to the viewer's time zone as needed for display.

  • For scheduling future events, store the local time of the event in your database, and follow the guidance I've written here.

You will also find extended guidance on this topic in my Pluralsight course, Date and Time Fundamentals. Specifically, you should consider watching the sections titled, "Time Zones", "Date and Time in the .NET Framework", and "Introducing Noda Time".

Daylight Saving Time not working in TimeZoneInfo, when converting from UTC to Local

A few things you should understand:

  1. A time zone is not the same as a time zone offset. One cannot just take the number -8 and assume that the time zone should be Pacific time.

  2. Offsets can change within a single time zone. For example, Pacific Time usually uses -8, but switches to -7 when daylight saving time is in effect.

  3. The offsets in the DisplayName property of a TimeZoneInfo are only the standard offset. They match with the BaseOffset property. They do not change to reflect the current offset.

  4. Time zone detection in JavaScript is imperfect. There are only three approaches:

    • Using the getTimezoneOffset function of the Date class, which should return you the offset of the date it was called on. For example new Date().getTimezoneOffset() gives you the current offset. With this approach, you should also be aware that there is a bug in the ES5 spec that can cause the wrong offset to sometimes be returned when called on older dates.

    • Using a library such as jsTimezoneDetect, which makes several calls to the getTimezoneOffset to attempt to guess at an IANA time zone identifier. The guess is suitable to set a default time zone when a list of time zones is presented to the user. It is just a guess, and could be wrong. If you want to use it on the back end with .NET, you'll need Noda Time, since TimeZoneInfo doesn't currently support IANA time zones. (You can optionally convert to Windows time zones if desired).

    • Some newer browsers support the ECMAScript Internationalization API, which has an optionally implemented function to return the time zone. It may work in some browsers, but is not guaranteed to return a valid result everywhere.

      Intl.DateTimeFormat().resolvedOptions().timeZone

      Again, you'll need Noda Time on the back end.

  5. You said:

    The problem is, if when I change timezone of client machine to (UTC -8:00), the client machine shows the timezone name as "(UTC-08:00) Pacific Time (US & Canada)" but in application the timezone differs from the client system it shows as "(UTC-08:00) Baja California".

    This is probably related to how you are choosing a time zone in your application code. It sounds to me like you're scanning the list of server time zones and choosing the first one that matches some criteria. Since both of these time zones have the same base offset, you're probably just picking the wrong one, and you shouldn't be doing that anyway. But since you didn't show that part of your code, I can't help you much there.

To answer your specific questions:

  • Whether the TimeZoneInfo class can work automatically work according to the adjusment rule, when we convert from UTC to Local?

    Yes, it can. There's nothing wrong with TimeZoneInfo, it's all about how you're using it. You probably are selecting the wrong time zone.

  • Do we have to detect the DST for particular datetime using the method TimeZoneInfoObject.IsDaylightSavingTime(DateTime) and do conversion?

    No, you should not have to do that just to convert from UTC to a specific time zone. The ConvertTimeFromUtc function will handle that for you.

  • Is there any other classes in .Net which can sync with windows timezones?

    TimeZoneInfo is the only one built in to the .NET Framework. Noda Time is a great alternative that can work with either Windows time zone or IANA time zones.

Lastly, I'll re-iterate what Jon said in comments. If all you're doing is displaying a particular instant in time to an end-user, then forget about time zone detection or working with local time on the server at all. Just send the UTC time to the client, and use either the UTC functions on the JavaScript Date object, or use a library like moment.js. Either can work in both UTC and local, and can convert between them. For example (using moment.js):

var valueFromServer = "2015-07-26T12:00:00Z";     // the Z means UTC
var localTime = moment(valueFromServer).format(); // "2015-07-26T05:00:00-07:00" (Pacific)

How to convert a time zone information in C# to a time zone information in Java?

You know that "time" is independent of "time zone", so I won't belabor that point :)

"TimeZone" is really more a function of your OS than a programming language.

Android "Time Zone IDs" correspond to the standard, IANA "Olson database":

  • http://developer.android.com/reference/java/util/TimeZone.html#getTimeZone%28java.lang.String%29

  • http://en.wikipedia.org/wiki/Tz_database

The other half of the equation is mapping .Net timezones to the "standard". This link should help:

  • .NET TimeZoneInfo from Olson time zone

How to translate between Windows and IANA time zones?

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.

Create DateTime in specific time zone then convert to utc

You'd basically need TimeZoneInfo.ConvertTimeToUtc method.

Just make sure the Kind property of the passed DateTime is Unspecified, otherwise the method has special expectations for the sourceTimeZone argument and will throw exception.

e.g.

var testTime = new DateTime(testDate.Year, testDate.Month, testDate.Day, 4, 0, 0);
var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("West Asia Standard Time");
var utcTime = TimeZoneInfo.ConvertTimeToUtc(testTime, timeZoneInfo);;


Related Topics



Leave a reply



Submit