Differences in Time Zone Formats in .NET Core on Windows and Linux host

Overcoming TimeZoneInfo load problem on different operating systems in .NET Core

Recently I was working on converting DateTime value to a different time zone because the servers are running in a different time zone than the database where data is stored. Storing DateTime values in UTC time zone is the best practice, so simple DateTime.ToUniversalTime method call solves the problem.

But what in case you need to save in the specific timezone and your different server where you application is running on. One of the things you can do is to set the time zone on the host operating system itself and let the environment take care of the timezone for you. That is fine if you have full control over the host operating system, but that is not always the case. Even if you do have full control over the host, It is not that easy to change timezone since it is OS specific and other components on the host might be affected by this change as well.

So the best thing to do is to do it in the code. For this purpose, you can siply use TimeZoneInfo.ConvertTime method passing your DateTime value and the actual time zone.

            var targetTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Easter Island Standard Time");
            var targetDAteTime = TimeZoneInfo.ConvertTime(DateTime.Now, targetTimeZone);
    

To get the values of the time zones you can simply run the following code to list them

using System;

namespace TimeZonesApp
{
    class Program
    {
        static void Main(string[] args)
        {
            foreach (TimeZoneInfo timeZone in TimeZoneInfo.GetSystemTimeZones()){
                Console.WriteLine(timeZone.Id);
			}
        }
    }
}
    

The output of this code executed on Windows machine will be the following list.

Dateline Standard Time
UTC-11
Aleutian Standard Time
Hawaiian Standard Time
Marquesas Standard Time
Alaskan Standard Time
UTC-09
Pacific Standard Time (Mexico)
UTC-08
Pacific Standard Time
US Mountain Standard Time
Mountain Standard Time (Mexico)
Mountain Standard Time
Central America Standard Time
Central Standard Time
Easter Island Standard Time
Central Standard Time (Mexico)
Canada Central Standard Time
SA Pacific Standard Time
Eastern Standard Time (Mexico)
Eastern Standard Time
Haiti Standard Time
Cuba Standard Time
US Eastern Standard Time
Turks And Caicos Standard Time
Paraguay Standard Time
Atlantic Standard Time
Venezuela Standard Time
Central Brazilian Standard Time
SA Western Standard Time
Pacific SA Standard Time
Newfoundland Standard Time
Tocantins Standard Time
E. South America Standard Time
SA Eastern Standard Time
Argentina Standard Time
Greenland Standard Time
Montevideo Standard Time
Magallanes Standard Time
Saint Pierre Standard Time
Bahia Standard Time
UTC-02
Mid-Atlantic Standard Time
Azores Standard Time
Cape Verde Standard Time
UTC
Morocco Standard Time
GMT Standard Time
Greenwich Standard Time
W. Europe Standard Time
Central Europe Standard Time
Romance Standard Time
Sao Tome Standard Time
Central European Standard Time
W. Central Africa Standard Time
Jordan Standard Time
GTB Standard Time
Middle East Standard Time
Egypt Standard Time
E. Europe Standard Time
Syria Standard Time
West Bank Standard Time
South Africa Standard Time
FLE Standard Time
Israel Standard Time
Kaliningrad Standard Time
Sudan Standard Time
Libya Standard Time
Namibia Standard Time
Arabic Standard Time
Turkey Standard Time
Arab Standard Time
Belarus Standard Time
Russian Standard Time
E. Africa Standard Time
Iran Standard Time
Arabian Standard Time
Astrakhan Standard Time
Azerbaijan Standard Time
Russia Time Zone 3
Mauritius Standard Time
Saratov Standard Time
Georgian Standard Time
Caucasus Standard Time
Afghanistan Standard Time
West Asia Standard Time
Ekaterinburg Standard Time
Pakistan Standard Time
India Standard Time
Sri Lanka Standard Time
Nepal Standard Time
Central Asia Standard Time
Bangladesh Standard Time
Omsk Standard Time
Myanmar Standard Time
SE Asia Standard Time
Altai Standard Time
W. Mongolia Standard Time
North Asia Standard Time
N. Central Asia Standard Time
Tomsk Standard Time
China Standard Time
North Asia East Standard Time
Singapore Standard Time
W. Australia Standard Time
Taipei Standard Time
Ulaanbaatar Standard Time
North Korea Standard Time
Aus Central W. Standard Time
Transbaikal Standard Time
Tokyo Standard Time
Korea Standard Time
Yakutsk Standard Time
Cen. Australia Standard Time
AUS Central Standard Time
E. Australia Standard Time
AUS Eastern Standard Time
West Pacific Standard Time
Tasmania Standard Time
Vladivostok Standard Time
Lord Howe Standard Time
Bougainville Standard Time
Russia Time Zone 10
Magadan Standard Time
Norfolk Standard Time
Sakhalin Standard Time
Central Pacific Standard Time
Russia Time Zone 11
New Zealand Standard Time
UTC+12
Fiji Standard Time
Kamchatka Standard Time
Chatham Islands Standard Time
UTC+13
Tonga Standard Time
Samoa Standard Time
Line Islands Standard Time

But if you run the same code on Linux you will get the following list

Pacific/Niue
Pacific/Pago_Pago
Pacific/Midway
Pacific/Rarotonga
America/Adak
Pacific/Honolulu
Pacific/Tahiti
Pacific/Marquesas
America/Metlakatla
America/Sitka
America/Nome
America/Juneau
America/Yakutat
America/Anchorage
Pacific/Gambier
America/Los_Angeles
America/Vancouver
America/Whitehorse
America/Dawson
America/Tijuana
Pacific/Pitcairn
America/Hermosillo
America/Chihuahua
America/Mazatlan
America/Yellowknife
America/Phoenix
America/Boise
America/Denver
America/Edmonton
America/Cambridge_Bay
America/Fort_Nelson
America/Creston
America/Dawson_Creek
America/Ojinaga
America/Inuvik
America/Belize
America/Winnipeg
America/Rainy_River
America/Resolute
America/Regina
America/Swift_Current
America/Matamoros
America/Monterrey
America/Managua
America/Merida
America/Costa_Rica
America/Mexico_City
America/Guatemala
America/Tegucigalpa
America/El_Salvador
America/Rankin_Inlet
America/Bahia_Banderas
America/North_Dakota/New_Salem
America/Indiana/Knox
America/North_Dakota/Beulah
America/Indiana/Tell_City
America/North_Dakota/Center
America/Chicago
America/Menominee
Pacific/Easter
Pacific/Galapagos
America/Rio_Branco
America/Eirunepe
America/Bogota
America/Havana
America/Kentucky/Louisville
America/Atikokan
America/Indiana/Indianapolis
America/Nassau
America/Pangnirtung
America/Iqaluit
America/Thunder_Bay
America/Kentucky/Monticello
America/Indiana/Vevay
America/Nipigon
America/Port-au-Prince
America/Detroit
America/New_York
America/Panama
America/Cancun
America/Jamaica
America/Indiana/Vincennes
America/Cayman
America/Indiana/Winamac
America/Indiana/Marengo
America/Indiana/Petersburg
America/Toronto
America/Guayaquil
America/Lima
America/Manaus
America/Boa_Vista
America/Porto_Velho
America/Cuiaba
America/Campo_Grande
America/Goose_Bay
America/Kralendijk
America/Curacao
America/Grenada
America/Puerto_Rico
America/St_Lucia
America/Marigot
America/St_Vincent
America/Grand_Turk
America/Dominica
America/Santo_Domingo
Atlantic/Bermuda
America/Port_of_Spain
America/St_Barthelemy
America/Lower_Princes
America/Anguilla
America/St_Kitts
America/Antigua
America/Tortola
America/St_Thomas
America/Montserrat
America/Martinique
America/Blanc-Sablon
America/Thule
America/Barbados
America/Aruba
America/Halifax
America/Guadeloupe
America/Moncton
America/Glace_Bay
America/La_Paz
America/Santiago
America/Guyana
America/Asuncion
America/Caracas
America/St_Johns
America/Argentina/La_Rioja
America/Argentina/Ushuaia
America/Argentina/Rio_Gallegos
America/Argentina/Mendoza
America/Argentina/San_Juan
America/Argentina/Catamarca
America/Argentina/Jujuy
America/Argentina/Salta
America/Argentina/Buenos_Aires
America/Argentina/Tucuman
America/Argentina/Cordoba
America/Maceio
America/Araguaina
America/Bahia
America/Sao_Paulo
America/Santarem
America/Recife
America/Belem
America/Fortaleza
Antarctica/Palmer
Atlantic/Stanley
America/Cayenne
America/Punta_Arenas
Antarctica/Rothera
America/Miquelon
America/Paramaribo
America/Montevideo
America/Godthab
America/Argentina/San_Luis
America/Noronha
Atlantic/South_Georgia
Atlantic/Azores
Atlantic/Cape_Verde
America/Scoresbysund
Europe/Isle_of_Man
Antarctica/Troll
Africa/Dakar
Africa/Ouagadougou
Africa/Bissau
Africa/Nouakchott
Europe/Jersey
Africa/Abidjan
Atlantic/St_Helena
Africa/Banjul
Africa/Freetown
Europe/London
Africa/Lome
Africa/Conakry
Europe/Guernsey
Africa/Accra
Atlantic/Reykjavik
America/Danmarkshavn
Africa/Bamako
Africa/Monrovia
Africa/Casablanca
Atlantic/Canary
Africa/El_Aaiun
Atlantic/Faroe
Europe/Lisbon
Atlantic/Madeira
Europe/Vaduz
Europe/Malta
Europe/Podgorica
Europe/Monaco
Europe/Rome
Europe/Luxembourg
Europe/Amsterdam
Europe/Oslo
Europe/Andorra
Africa/Algiers
Europe/Tirane
Europe/Vatican
Europe/Vienna
Europe/Sarajevo
Europe/Brussels
Europe/Zurich
Africa/Tunis
Europe/Prague
Europe/Berlin
Europe/Busingen
Europe/Copenhagen
Europe/Warsaw
Europe/Madrid
Africa/Ceuta
Europe/San_Marino
Europe/Skopje
Europe/Belgrade
Arctic/Longyearbyen
Europe/Paris
Europe/Budapest
Europe/Ljubljana
Europe/Zagreb
Europe/Gibraltar
Europe/Stockholm
Europe/Bratislava
Europe/Dublin
Africa/Sao_Tome
Africa/Luanda
Africa/Porto-Novo
Africa/Kinshasa
Africa/Brazzaville
Africa/Bangui
Africa/Malabo
Africa/Douala
Africa/Ndjamena
Africa/Libreville
Africa/Lagos
Africa/Niamey
Africa/Kigali
Africa/Blantyre
Africa/Maputo
Africa/Harare
Africa/Lusaka
Africa/Lubumbashi
Africa/Gaborone
Africa/Bujumbura
Africa/Khartoum
Asia/Gaza
Asia/Hebron
Europe/Bucharest
Europe/Kiev
Europe/Mariehamn
Europe/Sofia
Europe/Athens
Europe/Helsinki
Africa/Cairo
Europe/Tallinn
Asia/Damascus
Asia/Nicosia
Europe/Zaporozhye
Asia/Amman
Europe/Kaliningrad
Europe/Uzhgorod
Asia/Beirut
Africa/Tripoli
Europe/Vilnius
Europe/Chisinau
Europe/Riga
Asia/Famagusta
Asia/Jerusalem
Africa/Maseru
Africa/Mbabane
Africa/Johannesburg
Africa/Windhoek
Asia/Aden
Asia/Qatar
Asia/Bahrain
Asia/Riyadh
Asia/Kuwait
Asia/Baghdad
Africa/Addis_Ababa
Africa/Djibouti
Africa/Mogadishu
Africa/Juba
Africa/Asmara
Indian/Mayotte
Africa/Dar_es_Salaam
Indian/Antananarivo
Africa/Kampala
Indian/Comoro
Africa/Nairobi
Europe/Istanbul
Europe/Minsk
Europe/Kirov
Europe/Moscow
Europe/Volgograd
Europe/Simferopol
Antarctica/Syowa
Asia/Tehran
Asia/Yerevan
Asia/Baku
Europe/Saratov
Europe/Astrakhan
Europe/Ulyanovsk
Asia/Tbilisi
Asia/Dubai
Asia/Muscat
Indian/Mauritius
Indian/Reunion
Europe/Samara
Indian/Mahe
Asia/Kabul
Indian/Kerguelen
Asia/Atyrau
Indian/Maldives
Antarctica/Mawson
Asia/Karachi
Asia/Dushanbe
Asia/Ashgabat
Asia/Tashkent
Asia/Samarkand
Asia/Aqtobe
Asia/Aqtau
Asia/Oral
Asia/Yekaterinburg
Asia/Colombo
Asia/Kolkata
Asia/Kathmandu
Asia/Dhaka
Asia/Thimphu
Asia/Almaty
Asia/Qyzylorda
Asia/Urumqi
Indian/Chagos
Asia/Bishkek
Asia/Omsk
Antarctica/Vostok
Indian/Cocos
Asia/Yangon
Indian/Christmas
Antarctica/Davis
Asia/Tomsk
Asia/Barnaul
Asia/Hovd
Asia/Ho_Chi_Minh
Asia/Vientiane
Asia/Phnom_Penh
Asia/Bangkok
Asia/Krasnoyarsk
Asia/Novokuznetsk
Asia/Novosibirsk
Asia/Jakarta
Asia/Pontianak
Antarctica/Casey
Australia/Perth
Asia/Brunei
Asia/Makassar
Asia/Macau
Asia/Shanghai
Asia/Choibalsan
Asia/Hong_Kong
Asia/Irkutsk
Asia/Kuching
Asia/Kuala_Lumpur
Asia/Manila
Asia/Singapore
Asia/Taipei
Asia/Ulaanbaatar
Australia/Eucla
Asia/Dili
Asia/Jayapura
Asia/Tokyo
Asia/Seoul
Pacific/Palau
Asia/Pyongyang
Asia/Chita
Asia/Yakutsk
Asia/Khandyga
Australia/Broken_Hill
Australia/Adelaide
Australia/Darwin
Australia/Currie
Australia/Hobart
Australia/Melbourne
Australia/Sydney
Australia/Brisbane
Australia/Lindeman
Pacific/Saipan
Pacific/Guam
Pacific/Chuuk
Antarctica/DumontDUrville
Pacific/Port_Moresby
Asia/Ust-Nera
Asia/Vladivostok
Australia/Lord_Howe
Pacific/Bougainville
Asia/Srednekolymsk
Pacific/Kosrae
Antarctica/Macquarie
Asia/Magadan
Pacific/Noumea
Pacific/Norfolk
Pacific/Pohnpei
Asia/Sakhalin
Pacific/Guadalcanal
Pacific/Efate
Asia/Anadyr
Pacific/Fiji
Pacific/Tarawa
Pacific/Kwajalein
Pacific/Majuro
Pacific/Nauru
Pacific/Auckland
Antarctica/McMurdo
Asia/Kamchatka
Pacific/Funafuti
Pacific/Wake
Pacific/Wallis
Pacific/Chatham
Pacific/Apia
Pacific/Enderbury
Pacific/Fakaofo
Pacific/Tongatapu
Pacific/Kiritimati

You can see that these two lists are not the same, so running the following time zone load on Linux and Windows will throw exceptions on different lines.

            //Works on Windows only
            var winTimezone = TimeZoneInfo.FindSystemTimeZoneById("Central Europe Standard Time");

            //Works on Linux only 
            var linTimezone = TimeZoneInfo.FindSystemTimeZoneById("Europe/Budapest");
    

Trying to load wrong time zone will result in the following exception of type System.TimeZoneNotFoundException

Unhandled Exception: System.TimeZoneNotFoundException: The time zone ID 'Central Europe Standard Time' was not found on the local computer. ---> System.IO.FileNotFoundException: Could not find file '/usr/share/zoneinfo/Central Europe Standard Time'.
   at Interop.ThrowExceptionForIoErrno(ErrorInfo errorInfo, String path, Boolean isDirectory, Func`2 errorRewriter)
   at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String path, OpenFlags flags, Int32 mode)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options)
   at Internal.IO.File.ReadAllBytes(String path)
   at System.TimeZoneInfo.TryGetTimeZoneFromLocalMachine(String id, TimeZoneInfo& value, Exception& e)
   --- End of inner exception stack trace ---
   at System.TimeZoneInfo.FindSystemTimeZoneById(String id)

So basically you need to load timezone from first list if you are running your application on Windows or the second if you are running on Linux. Now problem comes if you need to find the Windows timezone match for Linux.

Mapping Windows to Linux timezones

Luckily, there is a GitHub project https://github.com/mj1856/TimeZoneConverter that will do the binding for you. The following is the snippet of one of the unit tests of TimeZoneConverter project

var result = TZConvert.WindowsToIana("Central Europe Standard Time");

Assert.Equal("Europe/Budapest", result);
    

This poject relies on the following publicly available timezone lookups

Linuxhttp://unicode.org/repos/cldr/trunk/common/bcp47/timezone.xml

Windowshttp://unicode.org/repos/cldr/trunk/common/supplemental/windowsZones.xml

If you go and check the code of the project, you will see that it is actually downloading the XML files above, so in my case that was not acceptable, so I used it to generate mapping and save to JSON which I can easily use later. Little bit of Linq and here is the mapping generate code

            //Execute on Windows machine
            Dictionary<String, String> timeZonePairs = new Dictionary<String, String>();
            foreach (TimeZoneInfo timeZone in TimeZoneInfo.GetSystemTimeZones())
            {
                timeZonePairs.Add(timeZone.Id, TZConvert.WindowsToIana(timeZone.Id));
            }
            var result = JsonConvert.SerializeObject(
                timeZonePairs.Select(t => new { Windows = t.Key, Linux = t.Value })
              );
    

Since the output is a bit long, you can download JSON document from this link or from the download section of this article. 

Now simply deserialize this to a C# model collection and you are good to go to map you OS specific timezone format without any third party code.

Detecting the operating system from the code

Now when we have time zones binding for Windows and Linux, we need to know on which OS we are running to know which value to load to TimeZoneInfo class instance. For this purpose you can use System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform and pass OS enumeration value. Enumeration values are:

  • OSPlatform.Windows
  • OSPlatform.OSX
  • OSPlatform.Linux

Simply run the following line to detemine whether you are running you application on Windows or not

bool isWindows = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
    

This method worked for me on both Window and Linux Debian OS, but there are issues reported for some of the Linux distros, so alternatevly you can rely on your configuration for different environment. More on how to use different config for different environment you can find in Use different configuration based on environment value in ASP.NET Core and on more how to set environment for .NET Core check out Different ways to set environment variable for .NET Core application

References

Disclaimer

Purpose of the code contained in snippets or available for download in this article is solely for learning and demo purposes. Author will not be held responsible for any failure or damages caused due to any other usage.


About the author

DEJAN STOJANOVIC

Dejan is a passionate Software Architect/Developer. He is highly experienced in .NET programming platform including ASP.NET MVC and WebApi. He likes working on new technologies and exciting challenging projects

CONNECT WITH DEJAN  Loginlinkedin Logintwitter Logingoogleplus Logingoogleplus

JavaScript

read more

SQL/T-SQL

read more

Umbraco CMS

read more

PowerShell

read more

Comments for this article