diff --git a/TimeZoneMappings.csv b/TimeZoneMappings.csv new file mode 100644 index 0000000..961a4fa --- /dev/null +++ b/TimeZoneMappings.csv @@ -0,0 +1,449 @@ +"AUS Central Standard Time","001","Australia/Darwin" +"AUS Central Standard Time","AU","Australia/Darwin" +"AUS Eastern Standard Time","001","Australia/Sydney" +"AUS Eastern Standard Time","AU","Australia/Sydney Australia/Melbourne" +"Afghanistan Standard Time","001","Asia/Kabul" +"Afghanistan Standard Time","AF","Asia/Kabul" +"Alaskan Standard Time","001","America/Anchorage" +"Alaskan Standard Time","US","America/Anchorage America/Juneau America/Nome America/Sitka America/Yakutat" +"Arab Standard Time","001","Asia/Riyadh" +"Arab Standard Time","BH","Asia/Bahrain" +"Arab Standard Time","KW","Asia/Kuwait" +"Arab Standard Time","QA","Asia/Qatar" +"Arab Standard Time","SA","Asia/Riyadh" +"Arab Standard Time","YE","Asia/Aden" +"Arabian Standard Time","001","Asia/Dubai" +"Arabian Standard Time","AE","Asia/Dubai" +"Arabian Standard Time","OM","Asia/Muscat" +"Arabian Standard Time","ZZ","Etc/GMT-4" +"Arabic Standard Time","001","Asia/Baghdad" +"Arabic Standard Time","IQ","Asia/Baghdad" +"Argentina Standard Time","001","America/Buenos_Aires" +"Argentina Standard Time","AR","America/Buenos_Aires America/Argentina/La_Rioja America/Argentina/Rio_Gallegos America/Argentina/Salta America/Argentina/San_Juan America/Argentina/San_Luis America/Argentina/Tucuman America/Argentina/Ushuaia America/Catamarca America/Cordoba America/Jujuy America/Mendoza" +"Atlantic Standard Time","001","America/Halifax" +"Atlantic Standard Time","BM","Atlantic/Bermuda" +"Atlantic Standard Time","CA","America/Halifax America/Glace_Bay America/Goose_Bay America/Moncton" +"Atlantic Standard Time","GL","America/Thule" +"Azerbaijan Standard Time","001","Asia/Baku" +"Azerbaijan Standard Time","AZ","Asia/Baku" +"Azores Standard Time","001","Atlantic/Azores" +"Azores Standard Time","GL","America/Scoresbysund" +"Azores Standard Time","PT","Atlantic/Azores" +"Bahia Standard Time","001","America/Bahia" +"Bahia Standard Time","BR","America/Bahia" +"Bangladesh Standard Time","001","Asia/Dhaka" +"Bangladesh Standard Time","BD","Asia/Dhaka" +"Bangladesh Standard Time","BT","Asia/Thimphu" +"Belarus Standard Time","001","Europe/Minsk" +"Belarus Standard Time","BY","Europe/Minsk" +"Canada Central Standard Time","001","America/Regina" +"Canada Central Standard Time","CA","America/Regina America/Swift_Current" +"Cape Verde Standard Time","001","Atlantic/Cape_Verde" +"Cape Verde Standard Time","CV","Atlantic/Cape_Verde" +"Cape Verde Standard Time","ZZ","Etc/GMT+1" +"Caucasus Standard Time","001","Asia/Yerevan" +"Caucasus Standard Time","AM","Asia/Yerevan" +"Cen. Australia Standard Time","001","Australia/Adelaide" +"Cen. Australia Standard Time","AU","Australia/Adelaide Australia/Broken_Hill" +"Central America Standard Time","001","America/Guatemala" +"Central America Standard Time","BZ","America/Belize" +"Central America Standard Time","CR","America/Costa_Rica" +"Central America Standard Time","EC","Pacific/Galapagos" +"Central America Standard Time","GT","America/Guatemala" +"Central America Standard Time","HN","America/Tegucigalpa" +"Central America Standard Time","NI","America/Managua" +"Central America Standard Time","SV","America/El_Salvador" +"Central America Standard Time","ZZ","Etc/GMT+6" +"Central Asia Standard Time","001","Asia/Almaty" +"Central Asia Standard Time","AQ","Antarctica/Vostok" +"Central Asia Standard Time","CN","Asia/Urumqi" +"Central Asia Standard Time","IO","Indian/Chagos" +"Central Asia Standard Time","KG","Asia/Bishkek" +"Central Asia Standard Time","KZ","Asia/Almaty Asia/Qyzylorda" +"Central Asia Standard Time","ZZ","Etc/GMT-6" +"Central Brazilian Standard Time","001","America/Cuiaba" +"Central Brazilian Standard Time","BR","America/Cuiaba America/Campo_Grande" +"Central Europe Standard Time","001","Europe/Budapest" +"Central Europe Standard Time","AL","Europe/Tirane" +"Central Europe Standard Time","CZ","Europe/Prague" +"Central Europe Standard Time","HU","Europe/Budapest" +"Central Europe Standard Time","ME","Europe/Podgorica" +"Central Europe Standard Time","RS","Europe/Belgrade" +"Central Europe Standard Time","SI","Europe/Ljubljana" +"Central Europe Standard Time","SK","Europe/Bratislava" +"Central European Standard Time","001","Europe/Warsaw" +"Central European Standard Time","BA","Europe/Sarajevo" +"Central European Standard Time","HR","Europe/Zagreb" +"Central European Standard Time","MK","Europe/Skopje" +"Central European Standard Time","PL","Europe/Warsaw" +"Central Pacific Standard Time","001","Pacific/Guadalcanal" +"Central Pacific Standard Time","AU","Antarctica/Macquarie" +"Central Pacific Standard Time","FM","Pacific/Ponape Pacific/Kosrae" +"Central Pacific Standard Time","NC","Pacific/Noumea" +"Central Pacific Standard Time","PG","Pacific/Bougainville" +"Central Pacific Standard Time","SB","Pacific/Guadalcanal" +"Central Pacific Standard Time","VU","Pacific/Efate" +"Central Pacific Standard Time","ZZ","Etc/GMT-11" +"Central Standard Time","001","America/Chicago" +"Central Standard Time","CA","America/Winnipeg America/Rainy_River America/Rankin_Inlet America/Resolute" +"Central Standard Time","MX","America/Matamoros" +"Central Standard Time","US","America/Chicago America/Indiana/Knox America/Indiana/Tell_City America/Menominee America/North_Dakota/Beulah America/North_Dakota/Center America/North_Dakota/New_Salem" +"Central Standard Time","ZZ","CST6CDT" +"Central Standard Time (Mexico)","001","America/Mexico_City" +"Central Standard Time (Mexico)","MX","America/Mexico_City America/Bahia_Banderas America/Merida America/Monterrey" +"China Standard Time","001","Asia/Shanghai" +"China Standard Time","CN","Asia/Shanghai" +"China Standard Time","HK","Asia/Hong_Kong" +"China Standard Time","MO","Asia/Macau" +"Dateline Standard Time","001","Etc/GMT+12" +"Dateline Standard Time","ZZ","Etc/GMT+12" +"E. Africa Standard Time","001","Africa/Nairobi" +"E. Africa Standard Time","AQ","Antarctica/Syowa" +"E. Africa Standard Time","DJ","Africa/Djibouti" +"E. Africa Standard Time","ER","Africa/Asmera" +"E. Africa Standard Time","ET","Africa/Addis_Ababa" +"E. Africa Standard Time","KE","Africa/Nairobi" +"E. Africa Standard Time","KM","Indian/Comoro" +"E. Africa Standard Time","MG","Indian/Antananarivo" +"E. Africa Standard Time","SD","Africa/Khartoum" +"E. Africa Standard Time","SO","Africa/Mogadishu" +"E. Africa Standard Time","SS","Africa/Juba" +"E. Africa Standard Time","TZ","Africa/Dar_es_Salaam" +"E. Africa Standard Time","UG","Africa/Kampala" +"E. Africa Standard Time","YT","Indian/Mayotte" +"E. Africa Standard Time","ZZ","Etc/GMT-3" +"E. Australia Standard Time","001","Australia/Brisbane" +"E. Australia Standard Time","AU","Australia/Brisbane Australia/Lindeman" +"E. South America Standard Time","001","America/Sao_Paulo" +"E. South America Standard Time","BR","America/Sao_Paulo" +"Eastern Standard Time","001","America/New_York" +"Eastern Standard Time","BS","America/Nassau" +"Eastern Standard Time","CA","America/Toronto America/Iqaluit America/Montreal America/Nipigon America/Pangnirtung America/Thunder_Bay" +"Eastern Standard Time","CU","America/Havana" +"Eastern Standard Time","HT","America/Port-au-Prince" +"Eastern Standard Time","US","America/New_York America/Detroit America/Indiana/Petersburg America/Indiana/Vincennes America/Indiana/Winamac America/Kentucky/Monticello America/Louisville" +"Eastern Standard Time","ZZ","EST5EDT" +"Eastern Standard Time (Mexico)","001","America/Cancun" +"Eastern Standard Time (Mexico)","MX","America/Cancun" +"Egypt Standard Time","001","Africa/Cairo" +"Egypt Standard Time","EG","Africa/Cairo" +"Ekaterinburg Standard Time","001","Asia/Yekaterinburg" +"Ekaterinburg Standard Time","RU","Asia/Yekaterinburg" +"FLE Standard Time","001","Europe/Kiev" +"FLE Standard Time","AX","Europe/Mariehamn" +"FLE Standard Time","BG","Europe/Sofia" +"FLE Standard Time","EE","Europe/Tallinn" +"FLE Standard Time","FI","Europe/Helsinki" +"FLE Standard Time","LT","Europe/Vilnius" +"FLE Standard Time","LV","Europe/Riga" +"FLE Standard Time","UA","Europe/Kiev Europe/Uzhgorod Europe/Zaporozhye" +"Fiji Standard Time","001","Pacific/Fiji" +"Fiji Standard Time","FJ","Pacific/Fiji" +"GMT Standard Time","001","Europe/London" +"GMT Standard Time","ES","Atlantic/Canary" +"GMT Standard Time","FO","Atlantic/Faeroe" +"GMT Standard Time","GB","Europe/London" +"GMT Standard Time","GG","Europe/Guernsey" +"GMT Standard Time","IE","Europe/Dublin" +"GMT Standard Time","IM","Europe/Isle_of_Man" +"GMT Standard Time","JE","Europe/Jersey" +"GMT Standard Time","PT","Europe/Lisbon Atlantic/Madeira" +"GTB Standard Time","001","Europe/Bucharest" +"GTB Standard Time","CY","Asia/Nicosia" +"GTB Standard Time","GR","Europe/Athens" +"GTB Standard Time","MD","Europe/Chisinau" +"GTB Standard Time","RO","Europe/Bucharest" +"Georgian Standard Time","001","Asia/Tbilisi" +"Georgian Standard Time","GE","Asia/Tbilisi" +"Greenland Standard Time","001","America/Godthab" +"Greenland Standard Time","GL","America/Godthab" +"Greenwich Standard Time","001","Atlantic/Reykjavik" +"Greenwich Standard Time","BF","Africa/Ouagadougou" +"Greenwich Standard Time","CI","Africa/Abidjan" +"Greenwich Standard Time","GH","Africa/Accra" +"Greenwich Standard Time","GM","Africa/Banjul" +"Greenwich Standard Time","GN","Africa/Conakry" +"Greenwich Standard Time","GW","Africa/Bissau" +"Greenwich Standard Time","IS","Atlantic/Reykjavik" +"Greenwich Standard Time","LR","Africa/Monrovia" +"Greenwich Standard Time","ML","Africa/Bamako" +"Greenwich Standard Time","MR","Africa/Nouakchott" +"Greenwich Standard Time","SH","Atlantic/St_Helena" +"Greenwich Standard Time","SL","Africa/Freetown" +"Greenwich Standard Time","SN","Africa/Dakar" +"Greenwich Standard Time","ST","Africa/Sao_Tome" +"Greenwich Standard Time","TG","Africa/Lome" +"Hawaiian Standard Time","001","Pacific/Honolulu" +"Hawaiian Standard Time","CK","Pacific/Rarotonga" +"Hawaiian Standard Time","PF","Pacific/Tahiti" +"Hawaiian Standard Time","UM","Pacific/Johnston" +"Hawaiian Standard Time","US","Pacific/Honolulu" +"Hawaiian Standard Time","ZZ","Etc/GMT+10" +"India Standard Time","001","Asia/Calcutta" +"India Standard Time","IN","Asia/Calcutta" +"Iran Standard Time","001","Asia/Tehran" +"Iran Standard Time","IR","Asia/Tehran" +"Israel Standard Time","001","Asia/Jerusalem" +"Israel Standard Time","IL","Asia/Jerusalem" +"Jordan Standard Time","001","Asia/Amman" +"Jordan Standard Time","JO","Asia/Amman" +"Kaliningrad Standard Time","001","Europe/Kaliningrad" +"Kaliningrad Standard Time","RU","Europe/Kaliningrad" +"Korea Standard Time","001","Asia/Seoul" +"Korea Standard Time","KP","Asia/Pyongyang" +"Korea Standard Time","KR","Asia/Seoul" +"Libya Standard Time","001","Africa/Tripoli" +"Libya Standard Time","LY","Africa/Tripoli" +"Line Islands Standard Time","001","Pacific/Kiritimati" +"Line Islands Standard Time","KI","Pacific/Kiritimati" +"Line Islands Standard Time","ZZ","Etc/GMT-14" +"Magadan Standard Time","001","Asia/Magadan" +"Magadan Standard Time","RU","Asia/Magadan" +"Mauritius Standard Time","001","Indian/Mauritius" +"Mauritius Standard Time","MU","Indian/Mauritius" +"Mauritius Standard Time","RE","Indian/Reunion" +"Mauritius Standard Time","SC","Indian/Mahe" +"Middle East Standard Time","001","Asia/Beirut" +"Middle East Standard Time","LB","Asia/Beirut" +"Montevideo Standard Time","001","America/Montevideo" +"Montevideo Standard Time","UY","America/Montevideo" +"Morocco Standard Time","001","Africa/Casablanca" +"Morocco Standard Time","EH","Africa/El_Aaiun" +"Morocco Standard Time","MA","Africa/Casablanca" +"Mountain Standard Time","001","America/Denver" +"Mountain Standard Time","CA","America/Edmonton America/Cambridge_Bay America/Inuvik America/Yellowknife" +"Mountain Standard Time","MX","America/Ojinaga" +"Mountain Standard Time","US","America/Denver America/Boise" +"Mountain Standard Time","ZZ","MST7MDT" +"Mountain Standard Time (Mexico)","001","America/Chihuahua" +"Mountain Standard Time (Mexico)","MX","America/Chihuahua America/Mazatlan" +"Myanmar Standard Time","001","Asia/Rangoon" +"Myanmar Standard Time","CC","Indian/Cocos" +"Myanmar Standard Time","MM","Asia/Rangoon" +"N. Central Asia Standard Time","001","Asia/Novosibirsk" +"N. Central Asia Standard Time","RU","Asia/Novosibirsk Asia/Omsk" +"Namibia Standard Time","001","Africa/Windhoek" +"Namibia Standard Time","NA","Africa/Windhoek" +"Nepal Standard Time","001","Asia/Katmandu" +"Nepal Standard Time","NP","Asia/Katmandu" +"New Zealand Standard Time","001","Pacific/Auckland" +"New Zealand Standard Time","AQ","Antarctica/McMurdo" +"New Zealand Standard Time","NZ","Pacific/Auckland" +"Newfoundland Standard Time","001","America/St_Johns" +"Newfoundland Standard Time","CA","America/St_Johns" +"North Asia East Standard Time","001","Asia/Irkutsk" +"North Asia East Standard Time","RU","Asia/Irkutsk Asia/Chita" +"North Asia Standard Time","001","Asia/Krasnoyarsk" +"North Asia Standard Time","RU","Asia/Krasnoyarsk Asia/Novokuznetsk" +"Pacific SA Standard Time","001","America/Santiago" +"Pacific SA Standard Time","AQ","Antarctica/Palmer" +"Pacific SA Standard Time","CL","America/Santiago" +"Pacific Standard Time","001","America/Los_Angeles" +"Pacific Standard Time","CA","America/Vancouver America/Dawson America/Whitehorse" +"Pacific Standard Time","MX","America/Tijuana" +"Pacific Standard Time","US","America/Los_Angeles" +"Pacific Standard Time","ZZ","PST8PDT" +"Pacific Standard Time (Mexico)","001","America/Santa_Isabel" +"Pacific Standard Time (Mexico)","MX","America/Santa_Isabel" +"Pakistan Standard Time","001","Asia/Karachi" +"Pakistan Standard Time","PK","Asia/Karachi" +"Paraguay Standard Time","001","America/Asuncion" +"Paraguay Standard Time","PY","America/Asuncion" +"Romance Standard Time","001","Europe/Paris" +"Romance Standard Time","BE","Europe/Brussels" +"Romance Standard Time","DK","Europe/Copenhagen" +"Romance Standard Time","ES","Europe/Madrid Africa/Ceuta" +"Romance Standard Time","FR","Europe/Paris" +"Russia Time Zone 10","001","Asia/Srednekolymsk" +"Russia Time Zone 10","RU","Asia/Srednekolymsk" +"Russia Time Zone 11","001","Asia/Kamchatka" +"Russia Time Zone 11","RU","Asia/Kamchatka Asia/Anadyr" +"Russia Time Zone 3","001","Europe/Samara" +"Russia Time Zone 3","RU","Europe/Samara" +"Russian Standard Time","001","Europe/Moscow" +"Russian Standard Time","RU","Europe/Moscow Europe/Simferopol Europe/Volgograd" +"SA Eastern Standard Time","001","America/Cayenne" +"SA Eastern Standard Time","AQ","Antarctica/Rothera" +"SA Eastern Standard Time","BR","America/Fortaleza America/Araguaina America/Belem America/Maceio America/Recife America/Santarem" +"SA Eastern Standard Time","FK","Atlantic/Stanley" +"SA Eastern Standard Time","GF","America/Cayenne" +"SA Eastern Standard Time","SR","America/Paramaribo" +"SA Eastern Standard Time","ZZ","Etc/GMT+3" +"SA Pacific Standard Time","001","America/Bogota" +"SA Pacific Standard Time","BR","America/Rio_Branco America/Eirunepe" +"SA Pacific Standard Time","CA","America/Coral_Harbour" +"SA Pacific Standard Time","CO","America/Bogota" +"SA Pacific Standard Time","EC","America/Guayaquil" +"SA Pacific Standard Time","JM","America/Jamaica" +"SA Pacific Standard Time","KY","America/Cayman" +"SA Pacific Standard Time","PA","America/Panama" +"SA Pacific Standard Time","PE","America/Lima" +"SA Pacific Standard Time","ZZ","Etc/GMT+5" +"SA Western Standard Time","001","America/La_Paz" +"SA Western Standard Time","AG","America/Antigua" +"SA Western Standard Time","AI","America/Anguilla" +"SA Western Standard Time","AW","America/Aruba" +"SA Western Standard Time","BB","America/Barbados" +"SA Western Standard Time","BL","America/St_Barthelemy" +"SA Western Standard Time","BO","America/La_Paz" +"SA Western Standard Time","BQ","America/Kralendijk" +"SA Western Standard Time","BR","America/Manaus America/Boa_Vista America/Porto_Velho" +"SA Western Standard Time","CA","America/Blanc-Sablon" +"SA Western Standard Time","CW","America/Curacao" +"SA Western Standard Time","DM","America/Dominica" +"SA Western Standard Time","DO","America/Santo_Domingo" +"SA Western Standard Time","GD","America/Grenada" +"SA Western Standard Time","GP","America/Guadeloupe" +"SA Western Standard Time","GY","America/Guyana" +"SA Western Standard Time","KN","America/St_Kitts" +"SA Western Standard Time","LC","America/St_Lucia" +"SA Western Standard Time","MF","America/Marigot" +"SA Western Standard Time","MQ","America/Martinique" +"SA Western Standard Time","MS","America/Montserrat" +"SA Western Standard Time","PR","America/Puerto_Rico" +"SA Western Standard Time","SX","America/Lower_Princes" +"SA Western Standard Time","TC","America/Grand_Turk" +"SA Western Standard Time","TT","America/Port_of_Spain" +"SA Western Standard Time","VC","America/St_Vincent" +"SA Western Standard Time","VG","America/Tortola" +"SA Western Standard Time","VI","America/St_Thomas" +"SA Western Standard Time","ZZ","Etc/GMT+4" +"SE Asia Standard Time","001","Asia/Bangkok" +"SE Asia Standard Time","AQ","Antarctica/Davis" +"SE Asia Standard Time","CX","Indian/Christmas" +"SE Asia Standard Time","ID","Asia/Jakarta Asia/Pontianak" +"SE Asia Standard Time","KH","Asia/Phnom_Penh" +"SE Asia Standard Time","LA","Asia/Vientiane" +"SE Asia Standard Time","MN","Asia/Hovd" +"SE Asia Standard Time","TH","Asia/Bangkok" +"SE Asia Standard Time","VN","Asia/Saigon" +"SE Asia Standard Time","ZZ","Etc/GMT-7" +"Samoa Standard Time","001","Pacific/Apia" +"Samoa Standard Time","WS","Pacific/Apia" +"Singapore Standard Time","001","Asia/Singapore" +"Singapore Standard Time","BN","Asia/Brunei" +"Singapore Standard Time","ID","Asia/Makassar" +"Singapore Standard Time","MY","Asia/Kuala_Lumpur Asia/Kuching" +"Singapore Standard Time","PH","Asia/Manila" +"Singapore Standard Time","SG","Asia/Singapore" +"Singapore Standard Time","ZZ","Etc/GMT-8" +"South Africa Standard Time","001","Africa/Johannesburg" +"South Africa Standard Time","BI","Africa/Bujumbura" +"South Africa Standard Time","BW","Africa/Gaborone" +"South Africa Standard Time","CD","Africa/Lubumbashi" +"South Africa Standard Time","LS","Africa/Maseru" +"South Africa Standard Time","MW","Africa/Blantyre" +"South Africa Standard Time","MZ","Africa/Maputo" +"South Africa Standard Time","RW","Africa/Kigali" +"South Africa Standard Time","SZ","Africa/Mbabane" +"South Africa Standard Time","ZA","Africa/Johannesburg" +"South Africa Standard Time","ZM","Africa/Lusaka" +"South Africa Standard Time","ZW","Africa/Harare" +"South Africa Standard Time","ZZ","Etc/GMT-2" +"Sri Lanka Standard Time","001","Asia/Colombo" +"Sri Lanka Standard Time","LK","Asia/Colombo" +"Syria Standard Time","001","Asia/Damascus" +"Syria Standard Time","SY","Asia/Damascus" +"Taipei Standard Time","001","Asia/Taipei" +"Taipei Standard Time","TW","Asia/Taipei" +"Tasmania Standard Time","001","Australia/Hobart" +"Tasmania Standard Time","AU","Australia/Hobart Australia/Currie" +"Tokyo Standard Time","001","Asia/Tokyo" +"Tokyo Standard Time","ID","Asia/Jayapura" +"Tokyo Standard Time","JP","Asia/Tokyo" +"Tokyo Standard Time","PW","Pacific/Palau" +"Tokyo Standard Time","TL","Asia/Dili" +"Tokyo Standard Time","ZZ","Etc/GMT-9" +"Tonga Standard Time","001","Pacific/Tongatapu" +"Tonga Standard Time","KI","Pacific/Enderbury" +"Tonga Standard Time","TK","Pacific/Fakaofo" +"Tonga Standard Time","TO","Pacific/Tongatapu" +"Tonga Standard Time","ZZ","Etc/GMT-13" +"Turkey Standard Time","001","Europe/Istanbul" +"Turkey Standard Time","TR","Europe/Istanbul" +"US Eastern Standard Time","001","America/Indianapolis" +"US Eastern Standard Time","US","America/Indianapolis America/Indiana/Marengo America/Indiana/Vevay" +"US Mountain Standard Time","001","America/Phoenix" +"US Mountain Standard Time","CA","America/Dawson_Creek America/Creston" +"US Mountain Standard Time","MX","America/Hermosillo" +"US Mountain Standard Time","US","America/Phoenix" +"US Mountain Standard Time","ZZ","Etc/GMT+7" +"UTC","001","Etc/GMT" +"UTC","GL","America/Danmarkshavn" +"UTC","ZZ","Etc/GMT" +"UTC+12","001","Etc/GMT-12" +"UTC+12","KI","Pacific/Tarawa" +"UTC+12","MH","Pacific/Majuro Pacific/Kwajalein" +"UTC+12","NR","Pacific/Nauru" +"UTC+12","TV","Pacific/Funafuti" +"UTC+12","UM","Pacific/Wake" +"UTC+12","WF","Pacific/Wallis" +"UTC+12","ZZ","Etc/GMT-12" +"UTC-02","001","Etc/GMT+2" +"UTC-02","BR","America/Noronha" +"UTC-02","GS","Atlantic/South_Georgia" +"UTC-02","ZZ","Etc/GMT+2" +"UTC-11","001","Etc/GMT+11" +"UTC-11","AS","Pacific/Pago_Pago" +"UTC-11","NU","Pacific/Niue" +"UTC-11","UM","Pacific/Midway" +"UTC-11","ZZ","Etc/GMT+11" +"Ulaanbaatar Standard Time","001","Asia/Ulaanbaatar" +"Ulaanbaatar Standard Time","MN","Asia/Ulaanbaatar Asia/Choibalsan" +"Venezuela Standard Time","001","America/Caracas" +"Venezuela Standard Time","VE","America/Caracas" +"Vladivostok Standard Time","001","Asia/Vladivostok" +"Vladivostok Standard Time","RU","Asia/Vladivostok Asia/Sakhalin Asia/Ust-Nera" +"W. Australia Standard Time","001","Australia/Perth" +"W. Australia Standard Time","AQ","Antarctica/Casey" +"W. Australia Standard Time","AU","Australia/Perth" +"W. Central Africa Standard Time","001","Africa/Lagos" +"W. Central Africa Standard Time","AO","Africa/Luanda" +"W. Central Africa Standard Time","BJ","Africa/Porto-Novo" +"W. Central Africa Standard Time","CD","Africa/Kinshasa" +"W. Central Africa Standard Time","CF","Africa/Bangui" +"W. Central Africa Standard Time","CG","Africa/Brazzaville" +"W. Central Africa Standard Time","CM","Africa/Douala" +"W. Central Africa Standard Time","DZ","Africa/Algiers" +"W. Central Africa Standard Time","GA","Africa/Libreville" +"W. Central Africa Standard Time","GQ","Africa/Malabo" +"W. Central Africa Standard Time","NE","Africa/Niamey" +"W. Central Africa Standard Time","NG","Africa/Lagos" +"W. Central Africa Standard Time","TD","Africa/Ndjamena" +"W. Central Africa Standard Time","TN","Africa/Tunis" +"W. Central Africa Standard Time","ZZ","Etc/GMT-1" +"W. Europe Standard Time","001","Europe/Berlin" +"W. Europe Standard Time","AD","Europe/Andorra" +"W. Europe Standard Time","AT","Europe/Vienna" +"W. Europe Standard Time","CH","Europe/Zurich" +"W. Europe Standard Time","DE","Europe/Berlin Europe/Busingen" +"W. Europe Standard Time","GI","Europe/Gibraltar" +"W. Europe Standard Time","IT","Europe/Rome" +"W. Europe Standard Time","LI","Europe/Vaduz" +"W. Europe Standard Time","LU","Europe/Luxembourg" +"W. Europe Standard Time","MC","Europe/Monaco" +"W. Europe Standard Time","MT","Europe/Malta" +"W. Europe Standard Time","NL","Europe/Amsterdam" +"W. Europe Standard Time","NO","Europe/Oslo" +"W. Europe Standard Time","SE","Europe/Stockholm" +"W. Europe Standard Time","SJ","Arctic/Longyearbyen" +"W. Europe Standard Time","SM","Europe/San_Marino" +"W. Europe Standard Time","VA","Europe/Vatican" +"West Asia Standard Time","001","Asia/Tashkent" +"West Asia Standard Time","AQ","Antarctica/Mawson" +"West Asia Standard Time","KZ","Asia/Oral Asia/Aqtau Asia/Aqtobe" +"West Asia Standard Time","MV","Indian/Maldives" +"West Asia Standard Time","TF","Indian/Kerguelen" +"West Asia Standard Time","TJ","Asia/Dushanbe" +"West Asia Standard Time","TM","Asia/Ashgabat" +"West Asia Standard Time","UZ","Asia/Tashkent Asia/Samarkand" +"West Asia Standard Time","ZZ","Etc/GMT-5" +"West Pacific Standard Time","001","Pacific/Port_Moresby" +"West Pacific Standard Time","AQ","Antarctica/DumontDUrville" +"West Pacific Standard Time","FM","Pacific/Truk" +"West Pacific Standard Time","GU","Pacific/Guam" +"West Pacific Standard Time","MP","Pacific/Saipan" +"West Pacific Standard Time","PG","Pacific/Port_Moresby" +"West Pacific Standard Time","ZZ","Etc/GMT-10" +"Yakutsk Standard Time","001","Asia/Yakutsk" +"Yakutsk Standard Time","RU","Asia/Yakutsk Asia/Khandyga" diff --git a/date.h b/date.h index 073abee..61364ba 100644 --- a/date.h +++ b/date.h @@ -22,7 +22,14 @@ namespace date // Configuration | //---------------+ -#if __cplusplus >= 201402 +// MSVC's constexpr support is still a WIP, even in VS2015. +// Fall back to a lesser mode to support it. +// TODO: Remove this or retest later once MSVC's constexpr improves. +#if defined(_MSC_VER) && ! defined(__clang__) && ! defined( __GNUG__) +# define CONSTDATA const +# define CONSTCD11 +# define CONSTCD14 +#elif __cplusplus >= 201402 # define CONSTDATA constexpr # define CONSTCD11 constexpr # define CONSTCD14 constexpr diff --git a/tz.cpp b/tz.cpp index cd289f4..2a03de1 100644 --- a/tz.cpp +++ b/tz.cpp @@ -10,21 +10,92 @@ #include #include #include +#include #include #include #include #include #include +#ifdef WIN32 +#include +#include +#endif + +#if TIMEZONE_MAPPING +// Timezone mapping is mapping native timezone names to "Standard" ones. +// Mapping reades a CSV file for the data and currently uses +// std::quoted to do that which is a C++14 feature found in iomanip. +// VS2015 supports std::quoted but MSVC has a mixed rather +// than strict standard support so there is no -std=c++14 flag for MSVC. +// MingW is a Windows based platform so requires mapping and thefore C++14. +// Linux/Mac currently do not require mapping so C++14 isn't needed for this +// so C++11 should work. +#include +#endif + +// unistd.h is used on some platforms as part of the the means to get +// the current time zone. However unistd.h only somtimes exists on Win32. +// gcc/mingw support unistd.h on Win32 but MSVC does not. +// However on Win32 we don't need unistd.h anyway to get the current timezone +// as Windows.h provides a means to do it + +#ifdef _WIN32 +#include +#else #include +#endif + +// Until filesystem arrives. +static const char folder_delimiter = +#ifdef _WIN32 +'\\'; +#else +'/'; +#endif + +#ifdef _WIN32 +// Win32 support requires calling OS functions. +// This routine maps OS error codes to readable text strngs. +static std::string get_win32_message(DWORD error_code) +{ + struct free_message { + void operator()(char buf[]) { + if (buf != nullptr) + { + auto result = HeapFree(GetProcessHeap(), 0, buf); + assert(result != 0); + } + } + }; + char* msg = nullptr; + auto result = FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&msg), 0, NULL ); + std::unique_ptr message_buffer(msg); + if (result == 0) // If there is no error message, still give the code. + { + std::string err = "Error getting message for error number "; + err += std::to_string(error_code); + return err; + } + assert(message_buffer.get() != nullptr); + return std::string(message_buffer.get()); +} +#endif namespace date { - // +---------------------+ // | Begin Configuration | // +---------------------+ -static std::string install{"/Users/howardhinnant/Downloads/tzdata2015e"}; +#if _WIN32 // TODO: sensible default for all platforms. +static std::string install{ "c:\\tzdata" }; +#else +static std::string install{ "/Users/howardhinnant/Downloads/tzdata2015e" }; +#endif static const std::vector files = { @@ -44,11 +115,294 @@ CONSTDATA auto boring_day = date::aug/18; // | End Configuration | // +-------------------+ +#if _MSC_VER && ! defined(__clang__) && ! defined( __GNUG__) +// We can't use static_assert here for MSVC (yet) because +// the expression isn't constexpr in MSVC yet. +// FIXME! Remove this when MSVC's constexpr support improves. +#else static_assert(min_year <= max_year, "Configuration error"); +#endif #if __cplusplus >= 201402 static_assert(boring_day.ok(), "Configuration error"); #endif +#if TIMEZONE_MAPPING + +namespace // Put types in an aonymous name space. +{ + // A simple type to manage RAII for key handles and to + // implement the trivial registry interface we need. + // Not itended to be general purpose. + class reg_key + { + private: + // Note there is no value documented to be an invalid handle value. + // Not NULL nor INVALID_HANDLE_VALUE. We must rely on is_open. + HKEY m_key = NULL; + bool m_is_open = false; + public: + HKEY handle() + { + return m_key; + } + bool is_open() const + { + return m_is_open; + } + LONG open(const wchar_t* key_name) + { + LONG result; + result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, key_name, 0, KEY_READ, &m_key); + if (result == ERROR_SUCCESS) + m_is_open = true; + return result; + } + LONG close() + { + if (m_is_open) + { + auto result = RegCloseKey(m_key); + assert(result == ERROR_SUCCESS); + if (result == ERROR_SUCCESS) + { + m_is_open = false; + m_key = NULL; + } + return result; + } + return ERROR_SUCCESS; + } + + // WARNING: this function has a hard code value size limit. + // It is not a general purpose function. + // It should be sufficient for our use cases. + // The function could be made workable for any size string + // but we don't need the complexity of implementing that + // for our meagre purposes right now. + bool get_string(const wchar_t* key_name, std::string& value) + { + value.clear(); + wchar_t value_buffer[256]; + // in/out parameter. Documentation say that size is a count of bytes not chars. + DWORD size = sizeof(value_buffer); + DWORD tzi_type = REG_SZ; + if (RegQueryValueExW(handle(), key_name, nullptr, &tzi_type, + reinterpret_cast(value_buffer), &size) == ERROR_SUCCESS) + { + // Function does not guarantee to null terminate. + value_buffer[size] = L'\0'; + std::wstring_convert> converter; + value = converter.to_bytes(value_buffer); + return true; + } + return false; + } + + bool get_binary(const wchar_t* key_name, void* value, int value_size) + { + DWORD size = value_size; + DWORD type = REG_BINARY; + if (RegQueryValueExW(handle(), key_name, nullptr, &type, + reinterpret_cast(value), &size) == ERROR_SUCCESS + && (int) size == value_size) + return true; + return false; + } + + ~reg_key() + { + close(); + } + }; +} // anonymous namespace + +template < typename T, size_t N > +static inline size_t countof(T(&arr)[N]) +{ + return std::extent< T[N] >::value; +} + +// This function return an exhaustive list of time zone information +// from the Windows registry. +// The routine tries to to obtain as much information as possible despite errors. +// If there is an error with any key, it is silently ignored to move on to the next. +// We don't have a logger to log such errors and it might disruptive to log anyway. +// We don't want the whole database of information disrupted just because +// one record of in it can't be read. +// The expectation is that the errors will eventually manifest to the +// caller as a missing time zone which they will need to investigate. + +static void get_windows_timezone_info(std::vector& tz_list) +{ + tz_list.clear(); + LONG result; + + // Open the parent time zone key that has the list of timzeones in. + reg_key zones_key; + static const wchar_t zones_key_name[] = { L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones" }; + result = zones_key.open(zones_key_name); + // TODO! Review if this should happen here or be signalled later. + // We don't want process to fail on startup because of this or something. + if (result != ERROR_SUCCESS) + throw std::runtime_error("Time Zone registry key could not be opened: " + get_win32_message(result)); + + DWORD size; + wchar_t zone_key_name[256]; + std::wstring value; + + // Iterate through the list of keys of the parent time zones key to get + // each key that identifies each individual timezone. + std::wstring full_zone_key_name; + for (DWORD zone_index = 0; ; ++zone_index) + { + timezone_info tz; + + size = (DWORD) sizeof(zone_key_name); + auto status = RegEnumKeyExW(zones_key.handle(), zone_index, zone_key_name, &size, + nullptr, nullptr, nullptr, nullptr); + if (status != ERROR_SUCCESS && status != ERROR_NO_MORE_ITEMS) + throw std::runtime_error("Can't enumerate time zone registry key" + get_win32_message(status)); + if (status == ERROR_NO_MORE_ITEMS) + break; + zone_key_name[size] = L'\0'; + std::wstring_convert> converter; + tz.timezone_id = converter.to_bytes(zone_key_name); + + full_zone_key_name = zones_key_name; + full_zone_key_name += L'\\'; + full_zone_key_name += zone_key_name; + + // If any field fails to be found consider the whole time zone + // entry corrupt and move onto the next. See comments + // at top of function. + + reg_key zone_key; + if (zone_key.open(full_zone_key_name.c_str()) != ERROR_SUCCESS) + continue; + + if (!zone_key.get_string(L"Std", tz.standard_name)) + continue; + +#if 0 + // TBD if these fields are not required yet. + // The might be useful for test cases though. + if (!zone_key.get_string("Display", tz.display_name)) + continue; + + if (!zone_key.get_binary("TZI", &tz.tzi, sizeof(TZI))) + continue; +#endif + auto result = zone_key.close(); + + tz_list.push_back(std::move(tz)); + } + result = zones_key.close(); +} + +// standard_name is the StandardName field from the Windows +// TIME_ZONE_INFORMATION structure. +// See the Windows API function GetTimeZoneInformation. +// The standard_name is also the value from STD field of +// under the windows registry key Time Zones. +// To be clear standard_name does NOT represent a windows timezone id +// or an IANA tzid +static const timezone_info* find_native_timezone_by_standard_name( + const std::string& standard_name) +{ + // TODO! we can improve on linear search. + const auto& native_zones = get_tzdb().native_zones; + for (const auto& tz : native_zones) + { + if (tz.standard_name == standard_name) + return &tz; + } + + return nullptr; +} + +// Read CSV file of "other","territory","type". +// See timezone_mapping structure for more info. +// This function should be kept in sync the code/ that writes this file. +static std::vector +load_timezone_mappings_from_csv_file(const std::string& input_path) +{ + size_t line = 1; + std::vector mappings; + std::ifstream is(input_path, std::ios_base::in | std::ios_base::binary); + if (!is.is_open()) + { + // We don't emit file exceptions because that's an implementation detail. + std::string msg = "Error opening time zone mapping file: "; + msg += input_path; + throw std::runtime_error(msg); + } + auto error = [&](const char* info) + { + std::string msg = "Error reading zone mapping file at line "; + msg += std::to_string(line); + msg += ": "; + msg += info; + throw std::runtime_error(msg); + }; + + auto read_field_delim = [&]() + { + char field_delim; + is.read(&field_delim, 1); + if (is.gcount() != 1 || field_delim != ',') + error("delimiter ',' expected"); + }; + + for (;;) + { + timezone_mapping zm{}; + is >> std::quoted(zm.other); + if (is.eof()) + break; + + read_field_delim(); + is >> std::quoted(zm.territory); + read_field_delim(); + is >> std::quoted(zm.type); + + char record_delim; + is.read(&record_delim, 1); + if (is.gcount() != 1 || record_delim != '\n') + error("record delimiter LF expected"); + + if (is.fail() || is.eof()) + error("unexpected end of file, file read error or formatting error."); + ++line; + mappings.push_back(std::move(zm)); + } + is.close(); + return mappings; +} + +static bool +native_to_standard_timezone_name(const std::string& native_tz_name, std::string& standard_tz_name) +{ + // TOOD! Need be a case insensitive compare? + if (native_tz_name == "UTC") + { + standard_tz_name = "Etc/UTC"; + return true; + } + standard_tz_name.clear(); + // TODO! we can improve on linear search. + const auto& mappings = date::get_tzdb().mappings; + for (const auto& tzm : mappings) + { + if (tzm.other == native_tz_name) + { + standard_tz_name = tzm.type; + return true; + } + } + return false; +} +#endif + // Parsing helpers static @@ -67,7 +421,7 @@ static unsigned parse_dow(std::istream& in) { - CONSTDATA const char* dow_names[] = + const char*const dow_names[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; auto s = parse3(in); auto dow = std::find(std::begin(dow_names), std::end(dow_names), s) - dow_names; @@ -80,7 +434,7 @@ static unsigned parse_month(std::istream& in) { - CONSTDATA const char* month_names[] = + const char*const month_names[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; auto s = parse3(in); @@ -1032,6 +1386,9 @@ find_rule_for_zone(const std::pair& eqr, const date::year& y, const std::chrono::seconds& offset, const MonthDayTime& mdt) { + assert(eqr.first != nullptr); + assert(eqr.second != nullptr); + using namespace std::chrono; using namespace date; auto r = eqr.first; @@ -1437,7 +1794,7 @@ TZ_DB init_tzdb() { using namespace date; - const std::string path = install + "/"; + const std::string path = install + folder_delimiter; std::string line; bool continue_zone = false; TZ_DB db; @@ -1493,6 +1850,13 @@ init_tzdb() db.links.shrink_to_fit(); std::sort(db.leaps.begin(), db.leaps.end()); db.leaps.shrink_to_fit(); + +#if TIMEZONE_MAPPING + std::string mapping_file = path + "TimeZoneMappings.csv"; + db.mappings = load_timezone_mappings_from_csv_file(mapping_file); + get_windows_timezone_info(db.native_zones); +#endif + return db; } @@ -1555,6 +1919,25 @@ locate_zone(const std::string& tz_name) return &*zi; } +#ifdef TZ_TEST +#ifdef _WIN32 +const Zone* +locate_native_zone(const std::string& native_tz_name) +{ + std::string standard_tz_name; + if (!native_to_standard_timezone_name(native_tz_name, standard_tz_name)) + { + std::string msg; + msg = "locate_native_zone() failed: A mapping from the Windows Time Zone id \""; + msg += native_tz_name; + msg += "\" was not found in the time zone mapping database."; + throw std::runtime_error(msg); + } + return locate_zone(standard_tz_name); +} +#endif +#endif + std::ostream& operator<<(std::ostream& os, const TZ_DB& db) { @@ -1625,6 +2008,48 @@ operator<<(std::ostream& os, const Info& r) return os; } +#ifdef _WIN32 + +const Zone* +current_timezone() +{ +#if TIMEZONE_MAPPING + TIME_ZONE_INFORMATION tzi{}; + DWORD tz_result = ::GetTimeZoneInformation(&tzi); + if (tz_result == TIME_ZONE_ID_INVALID) + { + auto error_code = ::GetLastError(); // Store this quick before it gets overwritten. + throw std::runtime_error("GetTimeZoneInformation failed: " + get_win32_message(error_code)); + } + std::wstring_convert> converter; + std::string standard_name(converter.to_bytes(tzi.StandardName)); + auto tz = find_native_timezone_by_standard_name(standard_name); + if (!tz) + { + std::string msg; + msg = "current_timezone() failed: "; + msg += standard_name; + msg += " was not found in the Windows Time Zone registry"; + throw std::runtime_error( msg ); + } + std::string standard_tzid; + if (!native_to_standard_timezone_name(tz->timezone_id, standard_tzid)) + { + std::string msg; + msg = "current_timezone() failed: A mapping from the Windows Time Zone id \""; + msg += tz->timezone_id; + msg += "\" was not found in the time zone mapping database."; + throw std::runtime_error(msg); + } + return date::locate_zone(standard_tzid); +#else + // Currently Win32 requires mapping for this function to work. + throw std::runtime_error("current_timezone not implemented."); +#endif +} + +#else // ! WIN32 + const Zone* current_timezone() { @@ -1677,5 +2102,6 @@ current_timezone() result.erase(0, zonepath_len+pos); return locate_zone(result); } +#endif } // namespace date diff --git a/tz.h b/tz.h index 936cc38..e03a551 100644 --- a/tz.h +++ b/tz.h @@ -11,6 +11,30 @@ // 1. Reload database. // 4. Is the utc to sys renaming complete? Was it done correctly? +/* +The notion of "current timezone" is something the operating system is expected +to "just know". How it knows this is system specific. It's often a value +set by the user at OS intallation time and recorded by the OS somewhere. +On Linux and Mac systems. the current timezone name is obtained by looking at +the name or contents of a particular file on disk. +On Windows the current timzeone name comes from the registry. +But however the name is obtained there is no guarante +that the "native" current timezone name obtained in this way +will match any of the "Standard" names in this library's "database". +On Linux, the names usually do seem to match so mapping functions to map from +native to "Standard" are typically NOT required. +On Windows, the names are never Standard mapping is always required. +One should not equate the mapping process with Windows. +Windows is just currently the only client of them. +Technically any OS may required mapping. +*/ + +#ifdef _WIN32 +#ifndef TIMEZONE_MAPPING +#define TIMEZONE_MAPPING 1 +#endif +#endif + #include "date.h" #include @@ -462,12 +486,77 @@ operator>=(const std::chrono::time_point& x return !(x < y); } +#if TIMEZONE_MAPPING + +// TODO! Ensure all these types aren't exposed. + +// The time zone mapping is modelled after this data file: +// http://unicode.org/repos/cldr/trunk/common/supplemental/windowsZones.xml +// and the field names match the element names from the mapZone element +// of windowsZones.xml. +// The website displays this file here: +// http://www.unicode.org/cldr/charts/latest/supplemental/zone_tzid.html +// The html view is sorted before being displayed but is otherwise the same +// There is a mapping between the os centric view (in this case windows) +// the html displays uses and the generic view the xml file. +// That mapping is this: +// display column "windows" -> xml field "other". +// display column "region" -> xml field "territory". +// display column "tzid" -> xml field "type". +// This structure uses the generic terminology because it could be +// used to to support other os/native name conversions, not just windows, +// and using the same generic names helps retain the connection to the +// origin of the data that we are using. +struct timezone_mapping +{ + timezone_mapping(const char* other, const char* territory, const char* type) + : other(other), territory(territory), type(type) + { + } + timezone_mapping() = default; + std::string other; + std::string territory; + std::string type; +}; + +#if 0 +// This represents the type for the tzi field in the windows registry. +// It's TBD if we need this yet. +struct TZI +{ + TZI() = default; + int Bias; + int StandardBias; + int DaylightBias; + SYSTEMTIME StandardDate; + SYSTEMTIME DaylightDate; +}; +#endif + +struct timezone_info +{ + timezone_info() = default; + std::string timezone_id; + std::string standard_name; +#if 0 // TBD + std::string display_name; + TZI tzi; +#endif +}; + +#endif + struct TZ_DB { std::vector zones; std::vector links; std::vector leaps; std::vector rules; +#if TIMEZONE_MAPPING + // TODO! These need some protection. + std::vector mappings; + std::vector native_zones; +#endif TZ_DB() = default; TZ_DB(TZ_DB&&) = default; @@ -481,6 +570,11 @@ const TZ_DB& reload_tzdb(); const TZ_DB& reload_tzdb(const std::string& new_install); const Zone* locate_zone(const std::string& tz_name); +#ifdef TZ_TEST +#ifdef _WIN32 +const Zone* locate_native_zone(const std::string& native_tz_name); +#endif +#endif const Zone* current_timezone(); class utc_clock