defmodule Timex.Timezone.Local do @moduledoc """ Contains the logic and parser for extracting local timezone configuration. """ alias Timex.DateTime, as: DateTime alias Timex.Date, as: Date # Map of Windows time zone names to Olson time zone names @lookup_olson [ { "AUS Central Standard Time", "Australia/Darwin" }, { "AUS Eastern Standard Time", "Australia/Sydney" }, { "Afghanistan Standard Time", "Asia/Kabul" }, { "Alaskan Standard Time", "America/Anchorage" }, { "Arab Standard Time", "Asia/Riyadh" }, { "Arabian Standard Time", "Asia/Dubai" }, { "Arabic Standard Time", "Asia/Baghdad" }, { "Argentina Standard Time", "America/Buenos_Aires" }, { "Atlantic Standard Time", "America/Halifax" }, { "Azerbaijan Standard Time", "Asia/Baku" }, { "Azores Standard Time", "Atlantic/Azores" }, { "Bahia Standard Time", "America/Bahia" }, { "Bangladesh Standard Time", "Asia/Dhaka" }, { "Canada Central Standard Time", "America/Regina" }, { "Cape Verde Standard Time", "Atlantic/Cape_Verde" }, { "Caucasus Standard Time", "Asia/Yerevan" }, { "Cen. Australia Standard Time", "Australia/Adelaide" }, { "Central America Standard Time", "America/Guatemala" }, { "Central Asia Standard Time", "Asia/Almaty" }, { "Central Brazilian Standard Time", "America/Cuiaba" }, { "Central Europe Standard Time", "Europe/Budapest" }, { "Central European Standard Time", "Europe/Warsaw" }, { "Central Pacific Standard Time", "Pacific/Guadalcanal" }, { "Central Standard Time", "America/Chicago" }, { "Central Standard Time (Mexico)", "America/Mexico_City" }, { "China Standard Time", "Asia/Shanghai" }, { "Dateline Standard Time", "Etc/GMT+12" }, { "E. Africa Standard Time", "Africa/Nairobi" }, { "E. Australia Standard Time", "Australia/Brisbane" }, { "E. Europe Standard Time", "Asia/Nicosia" }, { "E. South America Standard Time", "America/Sao_Paulo" }, { "Eastern Standard Time", "America/New_York" }, { "Egypt Standard Time", "Africa/Cairo" }, { "Ekaterinburg Standard Time", "Asia/Yekaterinburg" }, { "FLE Standard Time", "Europe/Kiev" }, { "Fiji Standard Time", "Pacific/Fiji" }, { "GMT Standard Time", "Europe/London" }, { "GTB Standard Time", "Europe/Bucharest" }, { "Georgian Standard Time", "Asia/Tbilisi" }, { "Greenland Standard Time", "America/Godthab" }, { "Greenwich Standard Time", "Atlantic/Reykjavik" }, { "Hawaiian Standard Time", "Pacific/Honolulu" }, { "India Standard Time", "Asia/Calcutta" }, { "Iran Standard Time", "Asia/Tehran" }, { "Israel Standard Time", "Asia/Jerusalem" }, { "Jordan Standard Time", "Asia/Amman" }, { "Kaliningrad Standard Time", "Europe/Kaliningrad" }, { "Korea Standard Time", "Asia/Seoul" }, { "Libya Standard Time", "Africa/Tripoli" }, { "Magadan Standard Time", "Asia/Magadan" }, { "Mauritius Standard Time", "Indian/Mauritius" }, { "Middle East Standard Time", "Asia/Beirut" }, { "Montevideo Standard Time", "America/Montevideo" }, { "Morocco Standard Time", "Africa/Casablanca" }, { "Mountain Standard Time", "America/Denver" }, { "Mountain Standard Time (Mexico)", "America/Chihuahua" }, { "Myanmar Standard Time", "Asia/Rangoon" }, { "N. Central Asia Standard Time", "Asia/Novosibirsk" }, { "Namibia Standard Time", "Africa/Windhoek" }, { "Nepal Standard Time", "Asia/Katmandu" }, { "New Zealand Standard Time", "Pacific/Auckland" }, { "Newfoundland Standard Time", "America/St_Johns" }, { "North Asia East Standard Time", "Asia/Irkutsk" }, { "North Asia Standard Time", "Asia/Krasnoyarsk" }, { "Pacific SA Standard Time", "America/Santiago" }, { "Pacific Standard Time", "America/Los_Angeles" }, { "Pacific Standard Time (Mexico)", "America/Santa_Isabel" }, { "Pakistan Standard Time", "Asia/Karachi" }, { "Paraguay Standard Time", "America/Asuncion" }, { "Romance Standard Time", "Europe/Paris" }, { "Russian Standard Time", "Europe/Moscow" }, { "SA Eastern Standard Time", "America/Cayenne" }, { "SA Pacific Standard Time", "America/Bogota" }, { "SA Western Standard Time", "America/La_Paz" }, { "SE Asia Standard Time", "Asia/Bangkok" }, { "Samoa Standard Time", "Pacific/Apia" }, { "Singapore Standard Time", "Asia/Singapore" }, { "South Africa Standard Time", "Africa/Johannesburg" }, { "Sri Lanka Standard Time", "Asia/Colombo" }, { "Syria Standard Time", "Asia/Damascus" }, { "Taipei Standard Time", "Asia/Taipei" }, { "Tasmania Standard Time", "Australia/Hobart" }, { "Tokyo Standard Time", "Asia/Tokyo" }, { "Tonga Standard Time", "Pacific/Tongatapu" }, { "Turkey Standard Time", "Europe/Istanbul" }, { "US Eastern Standard Time", "America/Indianapolis" }, { "US Mountain Standard Time", "America/Phoenix" }, { "UTC", "Etc/GMT" }, { "UTC+12", "Etc/GMT-12" }, { "UTC-02", "Etc/GMT+2" }, { "UTC-11", "Etc/GMT+11" }, { "Ulaanbaatar Standard Time", "Asia/Ulaanbaatar" }, { "Venezuela Standard Time", "America/Caracas" }, { "Vladivostok Standard Time", "Asia/Vladivostok" }, { "W. Australia Standard Time", "Australia/Perth" }, { "W. Central Africa Standard Time", "Africa/Lagos" }, { "W. Europe Standard Time", "Europe/Berlin" }, { "West Asia Standard Time", "Asia/Tashkent" }, { "West Pacific Standard Time", "Pacific/Port_Moresby" }, { "Yakutsk Standard Time", "Asia/Yakutsk" } ] # Reverse lookup from Olson time zone names to Windows time zone names @lookup_win [ { "Africa/Abidjan", "Greenwich Standard Time" }, { "Africa/Accra", "Greenwich Standard Time" }, { "Africa/Addis_Ababa", "E. Africa Standard Time" }, { "Africa/Algiers", "W. Central Africa Standard Time" }, { "Africa/Asmera", "E. Africa Standard Time" }, { "Africa/Bamako", "Greenwich Standard Time" }, { "Africa/Bangui", "W. Central Africa Standard Time" }, { "Africa/Banjul", "Greenwich Standard Time" }, { "Africa/Bissau", "Greenwich Standard Time" }, { "Africa/Blantyre", "South Africa Standard Time" }, { "Africa/Brazzaville", "W. Central Africa Standard Time" }, { "Africa/Bujumbura", "South Africa Standard Time" }, { "Africa/Cairo", "Egypt Standard Time" }, { "Africa/Casablanca", "Morocco Standard Time" }, { "Africa/Ceuta", "Romance Standard Time" }, { "Africa/Conakry", "Greenwich Standard Time" }, { "Africa/Dakar", "Greenwich Standard Time" }, { "Africa/Dar_es_Salaam", "E. Africa Standard Time" }, { "Africa/Djibouti", "E. Africa Standard Time" }, { "Africa/Douala", "W. Central Africa Standard Time" }, { "Africa/El_Aaiun", "Morocco Standard Time" }, { "Africa/Freetown", "Greenwich Standard Time" }, { "Africa/Gaborone", "South Africa Standard Time" }, { "Africa/Harare", "South Africa Standard Time" }, { "Africa/Johannesburg", "South Africa Standard Time" }, { "Africa/Juba", "E. Africa Standard Time" }, { "Africa/Kampala", "E. Africa Standard Time" }, { "Africa/Khartoum", "E. Africa Standard Time" }, { "Africa/Kigali", "South Africa Standard Time" }, { "Africa/Kinshasa", "W. Central Africa Standard Time" }, { "Africa/Lagos", "W. Central Africa Standard Time" }, { "Africa/Libreville", "W. Central Africa Standard Time" }, { "Africa/Lome", "Greenwich Standard Time" }, { "Africa/Luanda", "W. Central Africa Standard Time" }, { "Africa/Lubumbashi", "South Africa Standard Time" }, { "Africa/Lusaka", "South Africa Standard Time" }, { "Africa/Malabo", "W. Central Africa Standard Time" }, { "Africa/Maputo", "South Africa Standard Time" }, { "Africa/Maseru", "South Africa Standard Time" }, { "Africa/Mbabane", "South Africa Standard Time" }, { "Africa/Mogadishu", "E. Africa Standard Time" }, { "Africa/Monrovia", "Greenwich Standard Time" }, { "Africa/Nairobi", "E. Africa Standard Time" }, { "Africa/Ndjamena", "W. Central Africa Standard Time" }, { "Africa/Niamey", "W. Central Africa Standard Time" }, { "Africa/Nouakchott", "Greenwich Standard Time" }, { "Africa/Ouagadougou", "Greenwich Standard Time" }, { "Africa/Porto-Novo", "W. Central Africa Standard Time" }, { "Africa/Sao_Tome", "Greenwich Standard Time" }, { "Africa/Tripoli", "Libya Standard Time" }, { "Africa/Tunis", "W. Central Africa Standard Time" }, { "Africa/Windhoek", "Namibia Standard Time" }, { "America/Anchorage", "Alaskan Standard Time" }, { "America/Anguilla", "SA Western Standard Time" }, { "America/Antigua", "SA Western Standard Time" }, { "America/Araguaina", "SA Eastern Standard Time" }, { "America/Argentina/La_Rioja", "Argentina Standard Time" }, { "America/Argentina/Rio_Gallegos", "Argentina Standard Time" }, { "America/Argentina/Salta", "Argentina Standard Time" }, { "America/Argentina/San_Juan", "Argentina Standard Time" }, { "America/Argentina/San_Luis", "Argentina Standard Time" }, { "America/Argentina/Tucuman", "Argentina Standard Time" }, { "America/Argentina/Ushuaia", "Argentina Standard Time" }, { "America/Aruba", "SA Western Standard Time" }, { "America/Asuncion", "Paraguay Standard Time" }, { "America/Bahia", "Bahia Standard Time" }, { "America/Bahia_Banderas", "Central Standard Time (Mexico)" }, { "America/Barbados", "SA Western Standard Time" }, { "America/Belem", "SA Eastern Standard Time" }, { "America/Belize", "Central America Standard Time" }, { "America/Blanc-Sablon", "SA Western Standard Time" }, { "America/Boa_Vista", "SA Western Standard Time" }, { "America/Bogota", "SA Pacific Standard Time" }, { "America/Boise", "Mountain Standard Time" }, { "America/Buenos_Aires", "Argentina Standard Time" }, { "America/Cambridge_Bay", "Mountain Standard Time" }, { "America/Campo_Grande", "Central Brazilian Standard Time" }, { "America/Cancun", "Central Standard Time (Mexico)" }, { "America/Caracas", "Venezuela Standard Time" }, { "America/Catamarca", "Argentina Standard Time" }, { "America/Cayenne", "SA Eastern Standard Time" }, { "America/Cayman", "SA Pacific Standard Time" }, { "America/Chicago", "Central Standard Time" }, { "America/Chihuahua", "Mountain Standard Time (Mexico)" }, { "America/Coral_Harbour", "SA Pacific Standard Time" }, { "America/Cordoba", "Argentina Standard Time" }, { "America/Costa_Rica", "Central America Standard Time" }, { "America/Creston", "US Mountain Standard Time" }, { "America/Cuiaba", "Central Brazilian Standard Time" }, { "America/Curacao", "SA Western Standard Time" }, { "America/Danmarkshavn", "UTC" }, { "America/Dawson", "Pacific Standard Time" }, { "America/Dawson_Creek", "US Mountain Standard Time" }, { "America/Denver", "Mountain Standard Time" }, { "America/Detroit", "Eastern Standard Time" }, { "America/Dominica", "SA Western Standard Time" }, { "America/Edmonton", "Mountain Standard Time" }, { "America/Eirunepe", "SA Pacific Standard Time" }, { "America/El_Salvador", "Central America Standard Time" }, { "America/Fortaleza", "SA Eastern Standard Time" }, { "America/Glace_Bay", "Atlantic Standard Time" }, { "America/Godthab", "Greenland Standard Time" }, { "America/Goose_Bay", "Atlantic Standard Time" }, { "America/Grand_Turk", "Eastern Standard Time" }, { "America/Grenada", "SA Western Standard Time" }, { "America/Guadeloupe", "SA Western Standard Time" }, { "America/Guatemala", "Central America Standard Time" }, { "America/Guayaquil", "SA Pacific Standard Time" }, { "America/Guyana", "SA Western Standard Time" }, { "America/Halifax", "Atlantic Standard Time" }, { "America/Havana", "Eastern Standard Time" }, { "America/Hermosillo", "US Mountain Standard Time" }, { "America/Indiana/Knox", "Central Standard Time" }, { "America/Indiana/Marengo", "US Eastern Standard Time" }, { "America/Indiana/Petersburg", "Eastern Standard Time" }, { "America/Indiana/Tell_City", "Central Standard Time" }, { "America/Indiana/Vevay", "US Eastern Standard Time" }, { "America/Indiana/Vincennes", "Eastern Standard Time" }, { "America/Indiana/Winamac", "Eastern Standard Time" }, { "America/Indianapolis", "US Eastern Standard Time" }, { "America/Inuvik", "Mountain Standard Time" }, { "America/Iqaluit", "Eastern Standard Time" }, { "America/Jamaica", "SA Pacific Standard Time" }, { "America/Jujuy", "Argentina Standard Time" }, { "America/Juneau", "Alaskan Standard Time" }, { "America/Kentucky/Monticello", "Eastern Standard Time" }, { "America/Kralendijk", "SA Western Standard Time" }, { "America/La_Paz", "SA Western Standard Time" }, { "America/Lima", "SA Pacific Standard Time" }, { "America/Los_Angeles", "Pacific Standard Time" }, { "America/Louisville", "Eastern Standard Time" }, { "America/Lower_Princes", "SA Western Standard Time" }, { "America/Maceio", "SA Eastern Standard Time" }, { "America/Managua", "Central America Standard Time" }, { "America/Manaus", "SA Western Standard Time" }, { "America/Marigot", "SA Western Standard Time" }, { "America/Martinique", "SA Western Standard Time" }, { "America/Matamoros", "Central Standard Time" }, { "America/Mazatlan", "Mountain Standard Time (Mexico)" }, { "America/Mendoza", "Argentina Standard Time" }, { "America/Menominee", "Central Standard Time" }, { "America/Merida", "Central Standard Time (Mexico)" }, { "America/Mexico_City", "Central Standard Time (Mexico)" }, { "America/Moncton", "Atlantic Standard Time" }, { "America/Monterrey", "Central Standard Time (Mexico)" }, { "America/Montevideo", "Montevideo Standard Time" }, { "America/Montreal", "Eastern Standard Time" }, { "America/Montserrat", "SA Western Standard Time" }, { "America/Nassau", "Eastern Standard Time" }, { "America/New_York", "Eastern Standard Time" }, { "America/Nipigon", "Eastern Standard Time" }, { "America/Nome", "Alaskan Standard Time" }, { "America/Noronha", "UTC-02" }, { "America/North_Dakota/Beulah", "Central Standard Time" }, { "America/North_Dakota/Center", "Central Standard Time" }, { "America/North_Dakota/New_Salem", "Central Standard Time" }, { "America/Ojinaga", "Mountain Standard Time" }, { "America/Panama", "SA Pacific Standard Time" }, { "America/Pangnirtung", "Eastern Standard Time" }, { "America/Paramaribo", "SA Eastern Standard Time" }, { "America/Phoenix", "US Mountain Standard Time" }, { "America/Port-au-Prince", "Eastern Standard Time" }, { "America/Port_of_Spain", "SA Western Standard Time" }, { "America/Porto_Velho", "SA Western Standard Time" }, { "America/Puerto_Rico", "SA Western Standard Time" }, { "America/Rainy_River", "Central Standard Time" }, { "America/Rankin_Inlet", "Central Standard Time" }, { "America/Recife", "SA Eastern Standard Time" }, { "America/Regina", "Canada Central Standard Time" }, { "America/Resolute", "Central Standard Time" }, { "America/Rio_Branco", "SA Pacific Standard Time" }, { "America/Santa_Isabel", "Pacific Standard Time (Mexico)" }, { "America/Santarem", "SA Eastern Standard Time" }, { "America/Santiago", "Pacific SA Standard Time" }, { "America/Santo_Domingo", "SA Western Standard Time" }, { "America/Sao_Paulo", "E. South America Standard Time" }, { "America/Scoresbysund", "Azores Standard Time" }, { "America/Shiprock", "Mountain Standard Time" }, { "America/Sitka", "Alaskan Standard Time" }, { "America/St_Barthelemy", "SA Western Standard Time" }, { "America/St_Johns", "Newfoundland Standard Time" }, { "America/St_Kitts", "SA Western Standard Time" }, { "America/St_Lucia", "SA Western Standard Time" }, { "America/St_Thomas", "SA Western Standard Time" }, { "America/St_Vincent", "SA Western Standard Time" }, { "America/Swift_Current", "Canada Central Standard Time" }, { "America/Tegucigalpa", "Central America Standard Time" }, { "America/Thule", "Atlantic Standard Time" }, { "America/Thunder_Bay", "Eastern Standard Time" }, { "America/Tijuana", "Pacific Standard Time" }, { "America/Toronto", "Eastern Standard Time" }, { "America/Tortola", "SA Western Standard Time" }, { "America/Vancouver", "Pacific Standard Time" }, { "America/Whitehorse", "Pacific Standard Time" }, { "America/Winnipeg", "Central Standard Time" }, { "America/Yakutat", "Alaskan Standard Time" }, { "America/Yellowknife", "Mountain Standard Time" }, { "Antarctica/Casey", "W. Australia Standard Time" }, { "Antarctica/Davis", "SE Asia Standard Time" }, { "Antarctica/DumontDUrville", "West Pacific Standard Time" }, { "Antarctica/Macquarie", "Central Pacific Standard Time" }, { "Antarctica/Mawson", "West Asia Standard Time" }, { "Antarctica/McMurdo", "New Zealand Standard Time" }, { "Antarctica/Palmer", "Pacific SA Standard Time" }, { "Antarctica/Rothera", "SA Eastern Standard Time" }, { "Antarctica/South_Pole", "New Zealand Standard Time" }, { "Antarctica/Syowa", "E. Africa Standard Time" }, { "Antarctica/Vostok", "Central Asia Standard Time" }, { "Arctic/Longyearbyen", "W. Europe Standard Time" }, { "Asia/Aden", "Arab Standard Time" }, { "Asia/Almaty", "Central Asia Standard Time" }, { "Asia/Amman", "Jordan Standard Time" }, { "Asia/Anadyr", "Magadan Standard Time" }, { "Asia/Aqtau", "West Asia Standard Time" }, { "Asia/Aqtobe", "West Asia Standard Time" }, { "Asia/Ashgabat", "West Asia Standard Time" }, { "Asia/Baghdad", "Arabic Standard Time" }, { "Asia/Bahrain", "Arab Standard Time" }, { "Asia/Baku", "Azerbaijan Standard Time" }, { "Asia/Bangkok", "SE Asia Standard Time" }, { "Asia/Beirut", "Middle East Standard Time" }, { "Asia/Bishkek", "Central Asia Standard Time" }, { "Asia/Brunei", "Singapore Standard Time" }, { "Asia/Calcutta", "India Standard Time" }, { "Asia/Choibalsan", "Ulaanbaatar Standard Time" }, { "Asia/Chongqing", "China Standard Time" }, { "Asia/Colombo", "Sri Lanka Standard Time" }, { "Asia/Damascus", "Syria Standard Time" }, { "Asia/Dhaka", "Bangladesh Standard Time" }, { "Asia/Dili", "Tokyo Standard Time" }, { "Asia/Dubai", "Arabian Standard Time" }, { "Asia/Dushanbe", "West Asia Standard Time" }, { "Asia/Harbin", "China Standard Time" }, { "Asia/Hong_Kong", "China Standard Time" }, { "Asia/Hovd", "SE Asia Standard Time" }, { "Asia/Irkutsk", "North Asia East Standard Time" }, { "Asia/Jakarta", "SE Asia Standard Time" }, { "Asia/Jayapura", "Tokyo Standard Time" }, { "Asia/Jerusalem", "Israel Standard Time" }, { "Asia/Kabul", "Afghanistan Standard Time" }, { "Asia/Kamchatka", "Magadan Standard Time" }, { "Asia/Karachi", "Pakistan Standard Time" }, { "Asia/Kashgar", "China Standard Time" }, { "Asia/Katmandu", "Nepal Standard Time" }, { "Asia/Khandyga", "Yakutsk Standard Time" }, { "Asia/Krasnoyarsk", "North Asia Standard Time" }, { "Asia/Kuala_Lumpur", "Singapore Standard Time" }, { "Asia/Kuching", "Singapore Standard Time" }, { "Asia/Kuwait", "Arab Standard Time" }, { "Asia/Macau", "China Standard Time" }, { "Asia/Magadan", "Magadan Standard Time" }, { "Asia/Makassar", "Singapore Standard Time" }, { "Asia/Manila", "Singapore Standard Time" }, { "Asia/Muscat", "Arabian Standard Time" }, { "Asia/Nicosia", "E. Europe Standard Time" }, { "Asia/Novokuznetsk", "N. Central Asia Standard Time" }, { "Asia/Novosibirsk", "N. Central Asia Standard Time" }, { "Asia/Omsk", "N. Central Asia Standard Time" }, { "Asia/Oral", "West Asia Standard Time" }, { "Asia/Phnom_Penh", "SE Asia Standard Time" }, { "Asia/Pontianak", "SE Asia Standard Time" }, { "Asia/Pyongyang", "Korea Standard Time" }, { "Asia/Qatar", "Arab Standard Time" }, { "Asia/Qyzylorda", "Central Asia Standard Time" }, { "Asia/Rangoon", "Myanmar Standard Time" }, { "Asia/Riyadh", "Arab Standard Time" }, { "Asia/Saigon", "SE Asia Standard Time" }, { "Asia/Sakhalin", "Vladivostok Standard Time" }, { "Asia/Samarkand", "West Asia 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/Thimphu", "Bangladesh Standard Time" }, { "Asia/Tokyo", "Tokyo Standard Time" }, { "Asia/Ulaanbaatar", "Ulaanbaatar Standard Time" }, { "Asia/Urumqi", "China Standard Time" }, { "Asia/Ust-Nera", "Vladivostok Standard Time" }, { "Asia/Vientiane", "SE Asia Standard Time" }, { "Asia/Vladivostok", "Vladivostok Standard Time" }, { "Asia/Yakutsk", "Yakutsk Standard Time" }, { "Asia/Yekaterinburg", "Ekaterinburg Standard Time" }, { "Asia/Yerevan", "Caucasus Standard Time" }, { "Atlantic/Azores", "Azores Standard Time" }, { "Atlantic/Bermuda", "Atlantic Standard Time" }, { "Atlantic/Canary", "GMT Standard Time" }, { "Atlantic/Cape_Verde", "Cape Verde Standard Time" }, { "Atlantic/Faeroe", "GMT Standard Time" }, { "Atlantic/Madeira", "GMT Standard Time" }, { "Atlantic/Reykjavik", "Greenwich Standard Time" }, { "Atlantic/South_Georgia", "UTC-02" }, { "Atlantic/St_Helena", "Greenwich Standard Time" }, { "Atlantic/Stanley", "SA Eastern Standard Time" }, { "Australia/Adelaide", "Cen. Australia Standard Time" }, { "Australia/Brisbane", "E. Australia Standard Time" }, { "Australia/Broken_Hill", "Cen. Australia Standard Time" }, { "Australia/Currie", "Tasmania Standard Time" }, { "Australia/Darwin", "AUS Central Standard Time" }, { "Australia/Hobart", "Tasmania Standard Time" }, { "Australia/Lindeman", "E. Australia Standard Time" }, { "Australia/Melbourne", "AUS Eastern Standard Time" }, { "Australia/Perth", "W. Australia Standard Time" }, { "Australia/Sydney", "AUS Eastern Standard Time" }, { "CST6CDT", "Central Standard Time" }, { "EST5EDT", "Eastern Standard Time" }, { "Etc/GMT", "UTC" }, { "Etc/GMT+1", "Cape Verde Standard Time" }, { "Etc/GMT+10", "Hawaiian Standard Time" }, { "Etc/GMT+11", "UTC+11" }, { "Etc/GMT+12", "Dateline Standard Time" }, { "Etc/GMT+2", "UTC+02" }, { "Etc/GMT+3", "SA Eastern Standard Time" }, { "Etc/GMT+4", "SA Western Standard Time" }, { "Etc/GMT+5", "SA Pacific Standard Time" }, { "Etc/GMT+6", "Central America Standard Time" }, { "Etc/GMT+7", "US Mountain Standard Time" }, { "Etc/GMT-1", "W. Central Africa Standard Time" }, { "Etc/GMT-10", "West Pacific Standard Time" }, { "Etc/GMT-11", "Central Pacific Standard Time" }, { "Etc/GMT-12", "GMT+12" }, { "Etc/GMT-13", "Tonga Standard Time" }, { "Etc/GMT-2", "South Africa Standard Time" }, { "Etc/GMT-3", "E. Africa Standard Time" }, { "Etc/GMT-4", "Arabian Standard Time" }, { "Etc/GMT-5", "West Asia Standard Time" }, { "Etc/GMT-6", "Central Asia Standard Time" }, { "Etc/GMT-7", "SE Asia Standard Time" }, { "Etc/GMT-8", "Singapore Standard Time" }, { "Etc/GMT-9", "Tokyo Standard Time" }, { "Etc/UTC", "UTC" }, { "Europe/Amsterdam", "W. Europe Standard Time" }, { "Europe/Andorra", "W. Europe Standard Time" }, { "Europe/Athens", "GTB Standard Time" }, { "Europe/Belgrade", "Central Europe Standard Time" }, { "Europe/Berlin", "W. Europe Standard Time" }, { "Europe/Bratislava", "Central Europe Standard Time" }, { "Europe/Brussels", "Romance Standard Time" }, { "Europe/Bucharest", "GTB Standard Time" }, { "Europe/Budapest", "Central Europe Standard Time" }, { "Europe/Busingen", "W. Europe Standard Time" }, { "Europe/Chisinau", "GTB Standard Time" }, { "Europe/Copenhagen", "Romance Standard Time" }, { "Europe/Dublin", "GMT Standard Time" }, { "Europe/Gibraltar", "W. Europe Standard Time" }, { "Europe/Guernsey", "GMT Standard Time" }, { "Europe/Helsinki", "FLE Standard Time" }, { "Europe/Isle_of_Man", "GMT Standard Time" }, { "Europe/Istanbul", "Turkey Standard Time" }, { "Europe/Jersey", "GMT Standard Time" }, { "Europe/Kaliningrad", "Kaliningrad Standard Time" }, { "Europe/Kiev", "FLE Standard Time" }, { "Europe/Lisbon", "GMT Standard Time" }, { "Europe/Ljubljana", "Central Europe Standard Time" }, { "Europe/London", "GMT Standard Time" }, { "Europe/Luxembourg", "W. Europe Standard Time" }, { "Europe/Madrid", "Romance Standard Time" }, { "Europe/Malta", "W. Europe Standard Time" }, { "Europe/Mariehamn", "FLE Standard Time" }, { "Europe/Minsk", "Kaliningrad Standard Time" }, { "Europe/Monaco", "W. Europe Standard Time" }, { "Europe/Moscow", "Russian Standard Time" }, { "Europe/Oslo", "W. Europe Standard Time" }, { "Europe/Paris", "Romance Standard Time" }, { "Europe/Podgorica", "Central Europe Standard Time" }, { "Europe/Prague", "Central Europe Standard Time" }, { "Europe/Riga", "FLE Standard Time" }, { "Europe/Rome", "W. Europe Standard Time" }, { "Europe/Samara", "Russian Standard Time" }, { "Europe/San_Marino", "W. Europe Standard Time" }, { "Europe/Sarajevo", "Central European Standard Time" }, { "Europe/Simferopol", "FLE Standard Time" }, { "Europe/Skopje", "Central European Standard Time" }, { "Europe/Sofia", "FLE Standard Time" }, { "Europe/Stockholm", "W. Europe Standard Time" }, { "Europe/Tallinn", "FLE Standard Time" }, { "Europe/Tirane", "Central Europe Standard Time" }, { "Europe/Uzhgorod", "FLE Standard Time" }, { "Europe/Vaduz", "W. Europe Standard Time" }, { "Europe/Vatican", "W. Europe Standard Time" }, { "Europe/Vienna", "W. Europe Standard Time" }, { "Europe/Vilnius", "FLE Standard Time" }, { "Europe/Volgograd", "Russian Standard Time" }, { "Europe/Warsaw", "Central European Standard Time" }, { "Europe/Zagreb", "Central European Standard Time" }, { "Europe/Zaporozhye", "FLE Standard Time" }, { "Europe/Zurich", "W. Europe Standard Time" }, { "Indian/Antananarivo", "E. Africa Standard Time" }, { "Indian/Chagos", "Central Asia Standard Time" }, { "Indian/Christmas", "SE Asia Standard Time" }, { "Indian/Cocos", "Myanmar Standard Time" }, { "Indian/Comoro", "E. Africa Standard Time" }, { "Indian/Kerguelen", "West Asia Standard Time" }, { "Indian/Mahe", "Mauritius Standard Time" }, { "Indian/Maldives", "West Asia Standard Time" }, { "Indian/Mauritius", "Mauritius Standard Time" }, { "Indian/Mayotte", "E. Africa Standard Time" }, { "Indian/Reunion", "Mauritius Standard Time" }, { "MST7MDT", "Mountain Standard Time" }, { "PST8PDT", "Pacific Standard Time" }, { "Pacific/Apia", "Samoa Standard Time" }, { "Pacific/Auckland", "New Zealand Standard Time" }, { "Pacific/Efate", "Central Pacific Standard Time" }, { "Pacific/Enderbury", "Tonga Standard Time" }, { "Pacific/Fakaofo", "Tonga Standard Time" }, { "Pacific/Fiji", "Fiji Standard Time" }, { "Pacific/Funafuti", "UTC+12" }, { "Pacific/Galapagos", "Central America Standard Time" }, { "Pacific/Guadalcanal", "Central Pacific Standard Time" }, { "Pacific/Guam", "West Pacific Standard Time" }, { "Pacific/Honolulu", "Hawaiian Standard Time" }, { "Pacific/Johnston", "Hawaiian Standard Time" }, { "Pacific/Kosrae", "Central Pacific Standard Time" }, { "Pacific/Kwajalein", "UTC+12" }, { "Pacific/Majuro", "UTC+12" }, { "Pacific/Midway", "UTC-11" }, { "Pacific/Nauru", "UTC+12" }, { "Pacific/Niue", "UTC-11" }, { "Pacific/Noumea", "Central Pacific Standard Time" }, { "Pacific/Pago_Pago", "UTC-11" }, { "Pacific/Palau", "Tokyo Standard Time" }, { "Pacific/Ponape", "Central Pacific Standard Time" }, { "Pacific/Port_Moresby", "West Pacific Standard Time" }, { "Pacific/Rarotonga", "Hawaiian Standard Time" }, { "Pacific/Saipan", "West Pacific Standard Time" }, { "Pacific/Tahiti", "Hawaiian Standard Time" }, { "Pacific/Tarawa", "UTC+12" }, { "Pacific/Tongatapu", "Tonga Standard Time" }, { "Pacific/Truk", "West Pacific Standard Time" }, { "Pacific/Wake", "UTC+12" }, { "Pacific/Wallis", "UTC+12" } ] @_ETC_TIMEZONE "/etc/timezone" @_ETC_SYS_CLOCK "/etc/sysconfig/clock" @_ETC_CONF_CLOCK "/etc/conf.d/clock" @_ETC_LOCALTIME "/etc/localtime" @_USR_ETC_LOCALTIME "/usr/local/etc/localtime" @doc """ Looks up the local timezone configuration. Returns the name of a timezone in the Olson database. """ @spec lookup(DateTime.t | nil) :: String.t def lookup(), do: Date.universal |> lookup def lookup(date) do case :os.type() do {:unix, :darwin} -> localtz(:osx, date) {:unix, _} -> localtz(:unix, date) {:nt} -> localtz(:win, date) _ -> raise "Unsupported operating system!" end end # Get the locally configured timezone on OSX systems defp localtz(:osx, date) do # Allow TZ environment variable to override lookup case System.get_env("TZ") do nil -> # Most accurate local timezone will come from /etc/localtime, # since we can lookup proper timezones for arbitrary dates case read_timezone_data(nil, @_ETC_LOCALTIME, date) do {:ok, tz} -> tz _ -> # Fallback and ask systemsetup tz = System.cmd("systemsetup -gettimezone") |> iodata_to_binary |> String.strip(?\n) |> String.replace("Time Zone: ", "") if String.length(tz) > 0 do tz else raise("Unable to find local timezone.") end end tz -> tz end end # Get the locally configured timezone on *NIX systems defp localtz(:unix, date) do case System.get_env("TZ") do # Not found nil -> # Since that failed, check distro specific config files # containing the timezone name. To clean up the code here # we're using pipes, even though we may find the value we # are looking for on the first try. The way the function # defs are set up, if we find a value, it's just passed # along through the pipe until we're done. If we don't, # this will try each fallback location in order. {:ok, tz} = read_timezone_data(@_ETC_TIMEZONE, date) |> read_timezone_data(@_ETC_SYS_CLOCK, date) |> read_timezone_data(@_ETC_CONF_CLOCK, date) |> read_timezone_data(@_ETC_LOCALTIME, date) |> read_timezone_data(@_USR_ETC_LOCALTIME, date) tz tz -> tz end end # Get the locally configured timezone on Windows systems @local_tz_key 'SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation' @sys_tz_key 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones' # We ignore the reference date here, since there is no way to lookup # transition times for historical/future dates defp localtz(:win, _date) do # Windows has many of it's own unique time zone names, which can # also be translated to the OS's language. {:ok, handle} = :win32reg.open(:local_machine) :ok = :win32reg.change_key(handle, @local_tz_key) {:ok, values} = :win32reg.values(handle) if List.keymember?(values, 'TimeZoneKeyName', 0) do # Windows 7/Vista # On some systems the string value might be padded with excessive \0 bytes, trim them List.keyfind(values, 'TimeZoneKeyName', 0) |> iodata_to_binary |> String.strip ?\0 else # Windows 2000 or XP # This is the localized name: localized = List.keyfind(values, 'StandardName', 0) # Open the list of timezones to look up the real name: :ok = :win32reg.change_key(handle, @sys_tz_key) {:ok, subkeys} = :win32reg.sub_keys(handle) # Iterate over each subkey (timezone), and match against the localized name tzone = Enum.find subkeys, fn subkey -> :ok = :win32reg.change_key(handle, subkey) {:ok, values} = :win32reg.values(handle) case List.keyfind(values, 'Std', 0) do {_, zone} when zone == localized -> zone _ -> nil end end # If we don't have a timezone yet, we've failed, # Otherwise, we need to lookup the final timezone name # in the dictionary of unique Windows timezone names cond do tzone == nil -> raise "Could not find Windows time zone configuration!" tzone -> timezone = tzone |> iodata_to_binary case List.keyfind(@lookup_olson, timezone, 0) do nil -> # Try appending "Standard Time" case List.keyfind(@lookup_olson, "#{timezone} Standard Time", 0) do nil -> raise "Could not find Windows time zone configuration!" final -> final end final -> final end end end end # Attempt to read timezone data from /etc/timezone defp read_timezone_data(@_ETC_TIMEZONE, date) do case File.exists?(@_ETC_TIMEZONE) do true -> etctz = File.read!(@_ETC_TIMEZONE) case etctz |> String.starts_with?("TZif2") do true -> case etctz |> File.read! |> parse_tzfile(date) do {:ok, tz} -> {:ok, tz} {:error, m} -> raise m end false -> [no_hostdefs | _] = etctz |> String.split " ", [global: false, trim: true] [no_comments | _] = no_hostdefs |> String.split "#", [global: false, trim: true] {:ok, no_comments |> String.replace(" ", "_") |> String.strip(?\n)} end _ -> nil end end # If we've found a timezone, just keep on piping it through defp read_timezone_data({:ok, _} = result, _, _date), do: result # Otherwise, read the next fallback location defp read_timezone_data(_, file, _date) when file == @_ETC_SYS_CLOCK or file == @_ETC_CONF_CLOCK do case File.exists?(file) do true -> match = file |> File.stream! |> Stream.filter fn line -> Regex.match?(~r/(^ZONE=)|(^TIMEZONE=)/, line) end |> Enum.to_list |> List.first case match do m when m != nil -> [_, tz, _] = m |> String.split "\"" {:ok, tz |> String.replace " ", "_"} _ -> nil end _ -> nil end end defp read_timezone_data(_, file, date) when file == @_ETC_LOCALTIME or file == @_USR_ETC_LOCALTIME do case File.exists?(file) do true -> case file |> File.read! |> parse_tzfile(date) do {:ok, tz} -> {:ok, tz} {:error, m} -> raise m end _ -> nil end end # See http://linux.about.com/library/cmd/blcmdl5_tzfile.htm or # https://github.com/eggert/tz/blob/master/tzfile.h for details on the tzfile format # NOTE: These are defined as structs, but I would've preferred to use `defrecordp` here to # keep them private. The problem is that it is not possible to do the kind of manipulation # of records I'm doing in `parse_long`, etc. This is because unlike structs, `defrecordp`'s # functionality is based around macros and compile time knowledge. To reflect my desire to keep # these private, I've given them names in the `defrecordp` format, but they are still exposed # publically. defmodule TzFile do # six big-endian 32-bit integers: # number of UTC/local indicators defstruct utc_local_num: 0, # number of standard/wall indicators std_wall_num: 0, # number of leap seconds leap_num: 0, # number of transition times time_num: 0, # number of local time zones zone_num: 0, # number of characters of time zone abbrev strings char_num: 0, # Transition times transitions: [], # Zone data, zones: [], # Leap second adjustments leaps: [] end defmodule Zone do defstruct offset: 0, is_dst?: false, abbrev_index: 0, name: "", is_std?: false, is_utc?: false end defmodule Transition do defstruct when?: 0, zone: nil end defmodule Leap do defstruct when?: 0, adjust: 0 end @doc """ Given a binary representing the data from a tzfile (not the source version), parses out the timezone for the provided reference date, or current UTC time if one wasn't provided. """ @spec parse_tzfile(binary, DateTime.t | nil) :: {:ok, String.t} | {:error, term} def parse_tzfile(tzdata), do: parse_tzfile(tzdata, Date.universal()) def parse_tzfile(tzdata, %DateTime{} = reference_date) do case tzdata do << ?T,?Z,?i,?f, rest :: binary >> -> # Trim reserved space << _ :: [bytes, size(16)], data :: binary >> = rest # Num of UTC/Local indicators {record, remaining} = {%TzFile{}, data} |> parse_long(:utc_local_num) |> parse_long(:std_wall_num) |> parse_long(:leap_num) |> parse_long(:time_num) |> parse_long(:zone_num) |> parse_long(:char_num) # Extract transition times num_times = Range.new(1, record.time_num) {record, remaining} = Enum.reduce num_times, {record, remaining}, fn _, {r, d} -> {transition, rem} = {%Transition{}, d} |> parse_long(:when?) {%{r | :transitions => r.transitions ++ [transition]}, rem} end # Extract transition zone indices {record, remaining} = Enum.reduce num_times, {record, remaining}, fn i, {r, d} -> {txzone, rem} = d |> parse_uchar transition = r.transitions |> Enum.at(i - 1) updated = %{transition | :zone => txzone} transitions = r.transitions |> List.replace_at(i - 1, updated) {%{r | :transitions => transitions}, rem} end # Extract zone data num_zones = Range.new(1, record.zone_num) {record, remaining} = Enum.reduce num_zones, {record, remaining}, fn _, {r, d} -> {zone, rem} = {%Zone{}, d} |> parse_long(:offset) |> parse_bool(:is_dst?) |> parse_uchar(:abbrev_index) {%{r | :zones => r.zones ++ [zone]}, rem} end # Extract zone abbreviations {record, remaining} = Enum.reduce num_zones, {record, remaining}, fn i, {r, d} -> {str, rem} = d |> parse_string(4) # Each abbreviation is a null terminated string, so trim the terminator <> = str # Update the zone with it's extracted abbreviation zone = r.zones |> Enum.at(i - 1) updated = %{zone | :name => abbrev} zones = r.zones |> List.replace_at(i - 1, updated) {%{r | :zones => zones}, rem} end # Extract leap adjustment pairs leap_pairs = Range.new(1, record.leap_num) # We may not have any valid pairs to look for, so check our range to # see if this extraction should just be a no-op noop? = leap_pairs.last == 0 {record, remaining} = Enum.reduce leap_pairs, {record, remaining}, fn _, {r, d} -> if noop? do {r, d} else {leap, rem} = {%Leap{}, d} |> parse_long(:when?) |> parse_long(:adjust) {%{r | :leaps => r.leaps ++ [leap]}, rem} end end # Extract standard/wall indicators num_stdwall = Range.new(1, record.std_wall_num) {record, remaining} = Enum.reduce num_stdwall, {record, remaining}, fn i, {r, d} -> {is_std?, rem} = d |> parse_bool # Update the zone with the extracted info zone = r.zones |> Enum.at(i - 1) updated = %{zone | :is_std? => is_std?} zones = r.zones |> List.replace_at(i - 1, updated) {%{r | :zones => zones}, rem} end # Extract UTC/local indicators num_utclocal = Range.new(1, record.utc_local_num) {record, _} = Enum.reduce num_utclocal, {record, remaining}, fn i, {r, d} -> {is_utc?, rem} = d |> parse_bool # Update the zone with the extracted info zone = r.zones |> Enum.at(i - 1) updated = %{zone | :is_utc? => is_utc?} zones = r.zones |> List.replace_at(i - 1, updated) {%{r | :zones => zones}, rem} end # Get the zone for the current time timestamp = reference_date |> Date.to_secs current_tt = record.transitions |> Enum.sort(fn %Transition{when?: utime1}, %Transition{when?: utime2} -> utime1 > utime2 end) |> Enum.reject(fn %Transition{when?: unix_time} -> unix_time > timestamp end) |> List.first # We'll need these handy zones_available = record.zones # Attempt to get the proper timezone for the current transition we're in result = case current_tt do %Transition{zone: zone_index} -> if zone_index <= Enum.count(record.zones) - 1 do # Sweet, we have a matching zone, we have our result! %Zone{name: name} = zones_available |> Enum.fetch!(current_tt.zone) {:ok, name} else nil # Fallback to first standard-time zone end _ -> # Fallback to first standard-time zone end cond do # Success result != nil -> result # Damn, let's fallback to the first standard-time zone available true -> fallback = zones_available |> Enum.filter(fn zone -> zone.is_std? end) |> List.first case fallback do # Well, there are no standard-time zones then, just take the first zone available nil -> last_transition = record.transitions |> List.last %Zone{name: name} = zones_available |> Enum.fetch!(last_transition.zone) {:ok, name} # Found a reasonable fallback zone, success? %Zone{name: name} -> {:ok, name} end end false -> {:error, "Malformed time zone info!"} end end defp parse_long({record, data}, prop) do {val, rest} = data |> parse_long { Map.put(record, prop, val), rest } end defp parse_uchar({record, data}, prop) do {val, rest} = data |> parse_uchar { Map.put(record, prop, val), rest } end defp parse_bool({record, data}, prop) do {val, rest} = data |> parse_bool { Map.put(record, prop, val), rest } end defp parse_long(<>), do: { val, rest } defp parse_uchar(<>), do: { val, rest } defp parse_bool(<>), do: { val == 1, rest } defp parse_string(data, length) do <> = data {str, rest} end end