From 82e99282792a29ed5864baa70ffed8107bda8ee4 Mon Sep 17 00:00:00 2001 From: Frank Brehm Date: Wed, 27 Mar 2024 11:56:55 +0100 Subject: [PATCH] Adding lib/pp_admintools/tzlocal.py --- lib/pp_admintools/tzlocal.py | 1130 ++++++++++++++++++++++++++++++++++ 1 file changed, 1130 insertions(+) create mode 100644 lib/pp_admintools/tzlocal.py diff --git a/lib/pp_admintools/tzlocal.py b/lib/pp_admintools/tzlocal.py new file mode 100644 index 0000000..df903c5 --- /dev/null +++ b/lib/pp_admintools/tzlocal.py @@ -0,0 +1,1130 @@ +# -*- coding: utf-8 -*- +""" +@summary: A class for getting the local timezone as name or as a timezone object. + +@author: Frank Brehm +@contact: frank.brehm@pixelpark.com +@copyright: © 2024 by Frank Brehm, Berlin +""" +from __future__ import absolute_import + +# Standard modules +import calendar +import datetime +import logging +import os +import re +import time +import warnings + +try: + import zoneinfo # pragma: no cover +except ImportError: + from backports import zoneinfo # pragma: no cover + +# Own modules +from .xlate import XLATOR + +_ = XLATOR.gettext +ngettext = XLATOR.ngettext + +__version__ = '0.6.0' + + +LOG = logging.getLogger(__name__) + +# ============================================================================= +TZ_NAMES_REVERSE = { + 'AUS Central Standard Time': 'Australia/Darwin', + 'AUS Eastern Standard Time': 'Australia/Sydney', + 'Afghanistan Standard Time': 'Asia/Kabul', + 'Alaskan Standard Time': 'America/Anchorage', + 'Aleutian Standard Time': 'America/Adak', + 'Altai Standard Time': 'Asia/Barnaul', + 'Arab Standard Time': 'Asia/Riyadh', + 'Arabian Standard Time': 'Asia/Dubai', + 'Arabic Standard Time': 'Asia/Baghdad', + 'Argentina Standard Time': 'America/Buenos_Aires', + 'Astrakhan Standard Time': 'Europe/Astrakhan', + 'Atlantic Standard Time': 'America/Halifax', + 'Aus Central W. Standard Time': 'Australia/Eucla', + 'Azerbaijan Standard Time': 'Asia/Baku', + 'Azores Standard Time': 'Atlantic/Azores', + 'Bahia Standard Time': 'America/Bahia', + 'Bangladesh Standard Time': 'Asia/Dhaka', + 'Belarus Standard Time': 'Europe/Minsk', + 'Bougainville Standard Time': 'Pacific/Bougainville', + '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', + 'Chatham Islands Standard Time': 'Pacific/Chatham', + 'China Standard Time': 'Asia/Shanghai', + 'Cuba Standard Time': 'America/Havana', + 'Dateline Standard Time': 'Etc/GMT+12', + 'E. Africa Standard Time': 'Africa/Nairobi', + 'E. Australia Standard Time': 'Australia/Brisbane', + 'E. Europe Standard Time': 'Europe/Chisinau', + 'E. South America Standard Time': 'America/Sao_Paulo', + 'Easter Island Standard Time': 'Pacific/Easter', + 'Eastern Standard Time': 'America/New_York', + 'Eastern Standard Time (Mexico)': 'America/Cancun', + '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', + 'Haiti Standard Time': 'America/Port-au-Prince', + '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', + 'Line Islands Standard Time': 'Pacific/Kiritimati', + 'Lord Howe Standard Time': 'Australia/Lord_Howe', + 'Magadan Standard Time': 'Asia/Magadan', + 'Magallanes Standard Time': 'America/Punta_Arenas', + 'Marquesas Standard Time': 'Pacific/Marquesas', + '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/Mazatlan', + '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', + 'Norfolk Standard Time': 'Pacific/Norfolk', + 'North Asia East Standard Time': 'Asia/Irkutsk', + 'North Asia Standard Time': 'Asia/Krasnoyarsk', + 'North Korea Standard Time': 'Asia/Pyongyang', + 'Omsk Standard Time': 'Asia/Omsk', + 'Pacific SA Standard Time': 'America/Santiago', + 'Pacific Standard Time': 'America/Los_Angeles', + 'Pacific Standard Time (Mexico)': 'America/Tijuana', + 'Pakistan Standard Time': 'Asia/Karachi', + 'Paraguay Standard Time': 'America/Asuncion', + 'Qyzylorda Standard Time': 'Asia/Qyzylorda', + 'Romance Standard Time': 'Europe/Paris', + 'Russia Time Zone 10': 'Asia/Srednekolymsk', + 'Russia Time Zone 11': 'Asia/Kamchatka', + 'Russia Time Zone 3': 'Europe/Samara', + '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', + 'Saint Pierre Standard Time': 'America/Miquelon', + 'Sakhalin Standard Time': 'Asia/Sakhalin', + 'Samoa Standard Time': 'Pacific/Apia', + 'Sao Tome Standard Time': 'Africa/Sao_Tome', + 'Saratov Standard Time': 'Europe/Saratov', + 'Singapore Standard Time': 'Asia/Singapore', + 'South Africa Standard Time': 'Africa/Johannesburg', + 'South Sudan Standard Time': 'Africa/Juba', + 'Sri Lanka Standard Time': 'Asia/Colombo', + 'Sudan Standard Time': 'Africa/Khartoum', + 'Syria Standard Time': 'Asia/Damascus', + 'Taipei Standard Time': 'Asia/Taipei', + 'Tasmania Standard Time': 'Australia/Hobart', + 'Tocantins Standard Time': 'America/Araguaina', + 'Tokyo Standard Time': 'Asia/Tokyo', + 'Tomsk Standard Time': 'Asia/Tomsk', + 'Tonga Standard Time': 'Pacific/Tongatapu', + 'Transbaikal Standard Time': 'Asia/Chita', + 'Turkey Standard Time': 'Europe/Istanbul', + 'Turks And Caicos Standard Time': 'America/Grand_Turk', + 'US Eastern Standard Time': 'America/Indianapolis', + 'US Mountain Standard Time': 'America/Phoenix', + 'UTC': 'Etc/UTC', + 'UTC+12': 'Etc/GMT-12', + 'UTC+13': 'Etc/GMT-13', + 'UTC-02': 'Etc/GMT+2', + 'UTC-08': 'Etc/GMT+8', + 'UTC-09': 'Etc/GMT+9', + 'UTC-11': 'Etc/GMT+11', + 'Ulaanbaatar Standard Time': 'Asia/Ulaanbaatar', + 'Venezuela Standard Time': 'America/Caracas', + 'Vladivostok Standard Time': 'Asia/Vladivostok', + 'Volgograd Standard Time': 'Europe/Volgograd', + 'W. Australia Standard Time': 'Australia/Perth', + 'W. Central Africa Standard Time': 'Africa/Lagos', + 'W. Europe Standard Time': 'Europe/Berlin', + 'W. Mongolia Standard Time': 'Asia/Hovd', + 'West Asia Standard Time': 'Asia/Tashkent', + 'West Bank Standard Time': 'Asia/Hebron', + 'West Pacific Standard Time': 'Pacific/Port_Moresby', + 'Yakutsk Standard Time': 'Asia/Yakutsk', + 'Yukon Standard Time': 'America/Whitehorse', +} + +TZ_NAMES = { + '': 'Central Standard Time (Mexico)', + '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/Asmara': 'E. 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': 'South Sudan Standard Time', + 'Africa/Kampala': 'E. Africa Standard Time', + 'Africa/Khartoum': 'Sudan 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': 'Sao Tome Standard Time', + 'Africa/Timbuktu': 'Greenwich Standard Time', + 'Africa/Tripoli': 'Libya Standard Time', + 'Africa/Tunis': 'W. Central Africa Standard Time', + 'Africa/Windhoek': 'Namibia Standard Time', + 'America/Adak': 'Aleutian Standard Time', + 'America/Anchorage': 'Alaskan Standard Time', + 'America/Anguilla': 'SA Western Standard Time', + 'America/Antigua': 'SA Western Standard Time', + 'America/Araguaina': 'Tocantins Standard Time', + 'America/Argentina/Buenos_Aires': 'Argentina Standard Time', + 'America/Argentina/Catamarca': 'Argentina Standard Time', + 'America/Argentina/ComodRivadavia': 'Argentina Standard Time', + 'America/Argentina/Cordoba': 'Argentina Standard Time', + 'America/Argentina/Jujuy': 'Argentina Standard Time', + 'America/Argentina/La_Rioja': 'Argentina Standard Time', + 'America/Argentina/Mendoza': '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/Atikokan': 'SA Pacific Standard Time', + 'America/Atka': 'Aleutian 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': 'Eastern 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': 'Central Standard Time (Mexico)', + 'America/Ciudad_Juarez': 'Mountain Standard Time', + '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': 'Greenwich Standard Time', + 'America/Dawson': 'Yukon 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/Ensenada': 'Pacific Standard Time (Mexico)', + 'America/Fort_Nelson': 'US Mountain Standard Time', + 'America/Fort_Wayne': 'US Eastern 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': 'Turks And Caicos 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': 'Cuba Standard Time', + 'America/Hermosillo': 'US Mountain Standard Time', + 'America/Indiana/Indianapolis': 'US Eastern 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/Louisville': 'Eastern Standard Time', + 'America/Kentucky/Monticello': 'Eastern Standard Time', + 'America/Knox_IN': 'Central 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/Metlakatla': 'Alaskan Standard Time', + 'America/Mexico_City': 'Central Standard Time (Mexico)', + 'America/Miquelon': 'Saint Pierre Standard Time', + '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/Nuuk': 'Greenland Standard Time', + 'America/Ojinaga': 'Central 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': 'Haiti Standard Time', + 'America/Port_of_Spain': 'SA Western Standard Time', + 'America/Porto_Acre': 'SA Pacific Standard Time', + 'America/Porto_Velho': 'SA Western Standard Time', + 'America/Puerto_Rico': 'SA Western Standard Time', + 'America/Punta_Arenas': 'Magallanes 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/Rosario': 'Argentina 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 (Mexico)', + 'America/Toronto': 'Eastern Standard Time', + 'America/Tortola': 'SA Western Standard Time', + 'America/Vancouver': 'Pacific Standard Time', + 'America/Virgin': 'SA Western Standard Time', + 'America/Whitehorse': 'Yukon Standard Time', + 'America/Winnipeg': 'Central Standard Time', + 'America/Yakutat': 'Alaskan Standard Time', + 'America/Yellowknife': 'Mountain Standard Time', + 'Antarctica/Casey': 'Central Pacific Standard Time', + 'Antarctica/Davis': 'SE Asia Standard Time', + 'Antarctica/DumontDUrville': 'West Pacific Standard Time', + 'Antarctica/Macquarie': 'Tasmania Standard Time', + 'Antarctica/Mawson': 'West Asia Standard Time', + 'Antarctica/McMurdo': 'New Zealand Standard Time', + 'Antarctica/Palmer': 'SA Eastern 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': 'Russia Time Zone 11', + 'Asia/Aqtau': 'West Asia Standard Time', + 'Asia/Aqtobe': 'West Asia Standard Time', + 'Asia/Ashgabat': 'West Asia Standard Time', + 'Asia/Ashkhabad': 'West Asia Standard Time', + 'Asia/Atyrau': '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/Barnaul': 'Altai 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/Chita': 'Transbaikal Standard Time', + 'Asia/Choibalsan': 'Ulaanbaatar Standard Time', + 'Asia/Chongqing': 'China Standard Time', + 'Asia/Chungking': 'China Standard Time', + 'Asia/Colombo': 'Sri Lanka Standard Time', + 'Asia/Dacca': 'Bangladesh 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/Famagusta': 'GTB Standard Time', + 'Asia/Gaza': 'West Bank Standard Time', + 'Asia/Harbin': 'China Standard Time', + 'Asia/Hebron': 'West Bank Standard Time', + 'Asia/Ho_Chi_Minh': 'SE Asia Standard Time', + 'Asia/Hong_Kong': 'China Standard Time', + 'Asia/Hovd': 'W. Mongolia Standard Time', + 'Asia/Irkutsk': 'North Asia East Standard Time', + 'Asia/Istanbul': 'Turkey 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': 'Russia Time Zone 11', + 'Asia/Karachi': 'Pakistan Standard Time', + 'Asia/Kashgar': 'Central Asia Standard Time', + 'Asia/Kathmandu': 'Nepal Standard Time', + 'Asia/Katmandu': 'Nepal Standard Time', + 'Asia/Khandyga': 'Yakutsk Standard Time', + 'Asia/Kolkata': 'India 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/Macao': 'China 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': 'GTB Standard Time', + 'Asia/Novokuznetsk': 'North Asia Standard Time', + 'Asia/Novosibirsk': 'N. Central Asia Standard Time', + 'Asia/Omsk': 'Omsk Standard Time', + 'Asia/Oral': 'West Asia Standard Time', + 'Asia/Phnom_Penh': 'SE Asia Standard Time', + 'Asia/Pontianak': 'SE Asia Standard Time', + 'Asia/Pyongyang': 'North Korea Standard Time', + 'Asia/Qatar': 'Arab Standard Time', + 'Asia/Qostanay': 'Central Asia Standard Time', + 'Asia/Qyzylorda': 'Qyzylorda Standard Time', + 'Asia/Rangoon': 'Myanmar Standard Time', + 'Asia/Riyadh': 'Arab Standard Time', + 'Asia/Saigon': 'SE Asia Standard Time', + 'Asia/Sakhalin': 'Sakhalin Standard Time', + 'Asia/Samarkand': 'West Asia Standard Time', + 'Asia/Seoul': 'Korea Standard Time', + 'Asia/Shanghai': 'China Standard Time', + 'Asia/Singapore': 'Singapore Standard Time', + 'Asia/Srednekolymsk': 'Russia Time Zone 10', + 'Asia/Taipei': 'Taipei Standard Time', + 'Asia/Tashkent': 'West Asia Standard Time', + 'Asia/Tbilisi': 'Georgian Standard Time', + 'Asia/Tehran': 'Iran Standard Time', + 'Asia/Tel_Aviv': 'Israel Standard Time', + 'Asia/Thimbu': 'Bangladesh Standard Time', + 'Asia/Thimphu': 'Bangladesh Standard Time', + 'Asia/Tokyo': 'Tokyo Standard Time', + 'Asia/Tomsk': 'Tomsk Standard Time', + 'Asia/Ujung_Pandang': 'Singapore Standard Time', + 'Asia/Ulaanbaatar': 'Ulaanbaatar Standard Time', + 'Asia/Ulan_Bator': 'Ulaanbaatar Standard Time', + 'Asia/Urumqi': 'Central Asia 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/Yangon': 'Myanmar 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/Faroe': 'GMT Standard Time', + 'Atlantic/Jan_Mayen': 'W. Europe 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/ACT': 'AUS Eastern Standard Time', + 'Australia/Adelaide': 'Cen. Australia Standard Time', + 'Australia/Brisbane': 'E. Australia Standard Time', + 'Australia/Broken_Hill': 'Cen. Australia Standard Time', + 'Australia/Canberra': 'AUS Eastern Standard Time', + 'Australia/Currie': 'Tasmania Standard Time', + 'Australia/Darwin': 'AUS Central Standard Time', + 'Australia/Eucla': 'Aus Central W. Standard Time', + 'Australia/Hobart': 'Tasmania Standard Time', + 'Australia/LHI': 'Lord Howe Standard Time', + 'Australia/Lindeman': 'E. Australia Standard Time', + 'Australia/Lord_Howe': 'Lord Howe Standard Time', + 'Australia/Melbourne': 'AUS Eastern Standard Time', + 'Australia/NSW': 'AUS Eastern Standard Time', + 'Australia/North': 'AUS Central Standard Time', + 'Australia/Perth': 'W. Australia Standard Time', + 'Australia/Queensland': 'E. Australia Standard Time', + 'Australia/South': 'Cen. Australia Standard Time', + 'Australia/Sydney': 'AUS Eastern Standard Time', + 'Australia/Tasmania': 'Tasmania Standard Time', + 'Australia/Victoria': 'AUS Eastern Standard Time', + 'Australia/West': 'W. Australia Standard Time', + 'Australia/Yancowinna': 'Cen. Australia Standard Time', + 'Brazil/Acre': 'SA Pacific Standard Time', + 'Brazil/DeNoronha': 'UTC-02', + 'Brazil/East': 'E. South America Standard Time', + 'Brazil/West': 'SA Western Standard Time', + 'CST6CDT': 'Central Standard Time', + 'Canada/Atlantic': 'Atlantic Standard Time', + 'Canada/Central': 'Central Standard Time', + 'Canada/Eastern': 'Eastern Standard Time', + 'Canada/Mountain': 'Mountain Standard Time', + 'Canada/Newfoundland': 'Newfoundland Standard Time', + 'Canada/Pacific': 'Pacific Standard Time', + 'Canada/Saskatchewan': 'Canada Central Standard Time', + 'Canada/Yukon': 'Yukon Standard Time', + 'Chile/Continental': 'Pacific SA Standard Time', + 'Chile/EasterIsland': 'Easter Island Standard Time', + 'Cuba': 'Cuba Standard Time', + 'EST5EDT': 'Eastern Standard Time', + 'Egypt': 'Egypt Standard Time', + 'Eire': 'GMT Standard Time', + 'Etc/GMT': 'UTC', + 'Etc/GMT+0': '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+8': 'UTC-08', + 'Etc/GMT+9': 'UTC-09', + 'Etc/GMT-0': 'UTC', + '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': 'UTC+12', + 'Etc/GMT-13': 'UTC+13', + 'Etc/GMT-14': 'Line Islands 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/GMT0': 'UTC', + 'Etc/Greenwich': 'UTC', + 'Etc/UCT': 'UTC', + 'Etc/UTC': 'UTC', + 'Etc/Universal': 'UTC', + 'Etc/Zulu': 'UTC', + 'Europe/Amsterdam': 'W. Europe Standard Time', + 'Europe/Andorra': 'W. Europe Standard Time', + 'Europe/Astrakhan': 'Astrakhan Standard Time', + 'Europe/Athens': 'GTB Standard Time', + 'Europe/Belfast': 'GMT 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': 'E. Europe 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/Kirov': 'Russian Standard Time', + 'Europe/Kyiv': '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': 'Belarus Standard Time', + 'Europe/Monaco': 'W. Europe Standard Time', + 'Europe/Moscow': 'Russian Standard Time', + 'Europe/Nicosia': 'GTB 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': 'Russia Time Zone 3', + 'Europe/San_Marino': 'W. Europe Standard Time', + 'Europe/Sarajevo': 'Central European Standard Time', + 'Europe/Saratov': 'Saratov Standard Time', + 'Europe/Simferopol': 'Russian 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/Tiraspol': 'E. Europe Standard Time', + 'Europe/Ulyanovsk': 'Astrakhan 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': 'Volgograd 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', + 'GB': 'GMT Standard Time', + 'GB-Eire': 'GMT Standard Time', + 'GMT+0': 'UTC', + 'GMT-0': 'UTC', + 'GMT0': 'UTC', + 'Greenwich': 'UTC', + 'Hongkong': 'China Standard Time', + 'Iceland': 'Greenwich 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', + 'Iran': 'Iran Standard Time', + 'Israel': 'Israel Standard Time', + 'Jamaica': 'SA Pacific Standard Time', + 'Japan': 'Tokyo Standard Time', + 'Kwajalein': 'UTC+12', + 'Libya': 'Libya Standard Time', + 'MST7MDT': 'Mountain Standard Time', + 'Mexico/BajaNorte': 'Pacific Standard Time (Mexico)', + 'Mexico/BajaSur': 'Mountain Standard Time (Mexico)', + 'Mexico/General': 'Central Standard Time (Mexico)', + 'NZ': 'New Zealand Standard Time', + 'NZ-CHAT': 'Chatham Islands Standard Time', + 'Navajo': 'Mountain Standard Time', + 'PRC': 'China Standard Time', + 'PST8PDT': 'Pacific Standard Time', + 'Pacific/Apia': 'Samoa Standard Time', + 'Pacific/Auckland': 'New Zealand Standard Time', + 'Pacific/Bougainville': 'Bougainville Standard Time', + 'Pacific/Chatham': 'Chatham Islands Standard Time', + 'Pacific/Chuuk': 'West Pacific Standard Time', + 'Pacific/Easter': 'Easter Island Standard Time', + 'Pacific/Efate': 'Central Pacific Standard Time', + 'Pacific/Enderbury': 'UTC+13', + 'Pacific/Fakaofo': 'UTC+13', + 'Pacific/Fiji': 'Fiji Standard Time', + 'Pacific/Funafuti': 'UTC+12', + 'Pacific/Galapagos': 'Central America Standard Time', + 'Pacific/Gambier': 'UTC-09', + 'Pacific/Guadalcanal': 'Central Pacific Standard Time', + 'Pacific/Guam': 'West Pacific Standard Time', + 'Pacific/Honolulu': 'Hawaiian Standard Time', + 'Pacific/Johnston': 'Hawaiian Standard Time', + 'Pacific/Kanton': 'UTC+13', + 'Pacific/Kiritimati': 'Line Islands Standard Time', + 'Pacific/Kosrae': 'Central Pacific Standard Time', + 'Pacific/Kwajalein': 'UTC+12', + 'Pacific/Majuro': 'UTC+12', + 'Pacific/Marquesas': 'Marquesas Standard Time', + 'Pacific/Midway': 'UTC-11', + 'Pacific/Nauru': 'UTC+12', + 'Pacific/Niue': 'UTC-11', + 'Pacific/Norfolk': 'Norfolk Standard Time', + 'Pacific/Noumea': 'Central Pacific Standard Time', + 'Pacific/Pago_Pago': 'UTC-11', + 'Pacific/Palau': 'Tokyo Standard Time', + 'Pacific/Pitcairn': 'UTC-08', + 'Pacific/Pohnpei': 'Central Pacific 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/Samoa': 'UTC-11', + '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', + 'Pacific/Yap': 'West Pacific Standard Time', + 'Poland': 'Central European Standard Time', + 'Portugal': 'GMT Standard Time', + 'ROC': 'Taipei Standard Time', + 'ROK': 'Korea Standard Time', + 'Singapore': 'Singapore Standard Time', + 'Turkey': 'Turkey Standard Time', + 'UCT': 'UTC', + 'US/Alaska': 'Alaskan Standard Time', + 'US/Aleutian': 'Aleutian Standard Time', + 'US/Arizona': 'US Mountain Standard Time', + 'US/Central': 'Central Standard Time', + 'US/Eastern': 'Eastern Standard Time', + 'US/Hawaii': 'Hawaiian Standard Time', + 'US/Indiana-Starke': 'Central Standard Time', + 'US/Michigan': 'Eastern Standard Time', + 'US/Mountain': 'Mountain Standard Time', + 'US/Pacific': 'Pacific Standard Time', + 'US/Samoa': 'UTC-11', + 'UTC': 'UTC', + 'Universal': 'UTC', + 'W-SU': 'Russian Standard Time', + 'Zulu': 'UTC', +} + +CACHE_TZ = None +CACHE_TZ_NAME = None + +RE_ZONE = re.compile(r'\s*ZONE\s*=\s*"') +RE_TIMEZONE = re.compile(r'\s*TIMEZONE\s*=\s*"') +RE_END = re.compile(r'"') + +# ============================================================================= +def get_tz_offset(tz): + """Get timezone's offset using built-in function datetime.utcoffset().""" + return int(datetime.datetime.now(tz).utcoffset().total_seconds()) + + +# ============================================================================= +def assert_tz_offset(tz, error=True): + """ + Assert that system's timezone offset equals to the timezone offset found. + + If they don't match, we probably have a misconfiguration, for example, an + incorrect timezone set in /etc/timezone file in systemd distributions. + + If error is True, this method will raise a ValueError, otherwise it will + emit a warning. + """ + tz_offset = get_tz_offset(tz) + system_offset = calendar.timegm(time.localtime()) - calendar.timegm(time.gmtime()) + # No one has timezone offsets less than a minute, so this should be close enough: + if abs(tz_offset - system_offset) > 60: + msg = _( + 'Timezone offset does not match system offset: {tz_offset} != {system_offset}. ' + 'Please, check your config files.' + ).format(tz_offset=tz_offset, system_offset=system_offset) + if error: + raise ValueError(msg) + warnings.warn(msg) + + +# ============================================================================= +def _tz_name_from_env(tzenv=None): + if tzenv is None: + tzenv = os.environ.get('TZ') + + if not tzenv: + return None + + LOG.debug(_('Found a TZ environment: {}').format(tzenv)) + + if tzenv[0] == ':': + tzenv = tzenv[1:] + + if tzenv in TZ_NAMES: + # Yup, it's a timezone + return tzenv + + if os.path.isabs(tzenv) and os.path.exists(tzenv): + # It's a file specification, expand it, if possible + parts = os.path.realpath(tzenv).split(os.sep) + + # Is it a zone info zone? + possible_tz = '/'.join(parts[-2:]) + if possible_tz in TZ_NAMES: + # Yup, it is + return possible_tz + + # Maybe it's a short one, like UTC? + if parts[-1] in TZ_NAMES: + # Indeed + return parts[-1] + + LOG.debug(_('Environment variable TZ does not contain a time zone name.')) + return None + + +# ============================================================================= +def _tz_from_env(tzenv=None): + if tzenv is None: + tzenv = os.environ.get('TZ') + + if not tzenv: + return None + + # Some weird format that exists: + if tzenv[0] == ':': + tzenv = tzenv[1:] + + # TZ specifies a file + if os.path.isabs(tzenv) and os.path.exists(tzenv): + # Try to see if we can figure out the name + tzname = _tz_name_from_env(tzenv) + if not tzname: + # Nope, not a standard timezone name, just take the filename + tzname = tzenv.split(os.sep)[-1] + with open(tzenv, 'rb') as tzfile: + return zoneinfo.ZoneInfo.from_file(tzfile, key=tzname) + + # TZ must specify a zoneinfo zone. + try: + tz = zoneinfo.ZoneInfo(tzenv) + # That worked, so we return this: + return tz + except zoneinfo.ZoneInfoNotFoundError: + # Nope, it's something like "PST4DST" etc, we can't handle that. + msg = _('tzlocal() does not support non-zoneinfo timezones like {!r}.').format(tzenv) + msg += '\n' + _('Please use a timezone in the form of Continent/City.') + raise zoneinfo.ZoneInfoNotFoundError(msg) from None + + +# ============================================================================== +def _read_android_tz(): + LOG.debug(_('This looks like {}.').format('Termux')) + + import subprocess + + try: + androidtz = ( + subprocess.check_output(['getprop', 'persist.sys.timezone']) + .strip() + .decode() + ) + return androidtz + except (OSError, subprocess.CalledProcessError): + # proot environment or failed to getprop + LOG.debug(_("It's not {}?").format('Termux')) + + return None + + +# ============================================================================== +def _read_etc_timezone(tzpath): + try: + with open(tzpath) as tzfile: + data = tzfile.read() + LOG.debug(_('{!r} found, contents:').format(tzpath) + '\n' + data) + + etctz = data.strip('/ \t\r\n') + if not etctz: + # Empty file, skip + return None + + for etctz in etctz.splitlines(): + # Get rid of host definitions and comments: + if ' ' in etctz: + etctz, dummy = etctz.split(' ', 1) + if '#' in etctz: + etctz, dummy = etctz.split('#', 1) + if not etctz: + continue + + return etctz.replace(' ', '_') + + except (OSError, UnicodeDecodeError): + # File doesn't exist or is a directory, or it's a binary file. + return None + + return None + + +# ============================================================================= +def _read_clock_timezone(tzpath): + try: + with open(tzpath, 'rt') as tzfile: + data = tzfile.readlines() + LOG.debug(_('{!r} found, contents:').format(tzpath) + '\n' + data) + + for line in data: + # Look for the ZONE= setting. + match = RE_ZONE.match(line) + if match is None: + # No ZONE= setting. Look for the TIMEZONE= setting. + match = RE_TIMEZONE.match(line) + if match is not None: + # Some setting existed + line = line[match.end():] + etctz = line[: RE_END.search(line).start()] + + # We found a timezone + return etctz.replace(' ', '_') + + except (OSError, UnicodeDecodeError): + # UnicodeDecode handles when clock is symlink to /etc/localtime + return None + + return None + + +# ============================================================================= +def _get_localzone_name(_root='/'): + """ + Try to find the local timezone configuration. + + This method finds the timezone name, if it can, or it returns None. + + The parameter _root makes the function look for files like /etc/localtime + beneath the _root directory. This is primarily used by the tests. + In normal usage you call the function without parameters. + """ + # First try the ENV setting. + tzenv = _tz_name_from_env() + if tzenv: + return tzenv + + # Are we under Termux on Android? + if os.path.exists(os.path.join(_root, 'system/bin/getprop')): + return _read_android_tz() + + # Now look for distribution specific configuration files + # that contain the timezone name. + + # Stick all of them in a dict, to compare later. + found_configs = {} + + for configfile in ('etc/timezone', 'var/db/zoneinfo'): + tzpath = os.path.join(_root, configfile) + tzconfig = _read_etc_timezone(tzpath) + if tzconfig is not None: + found_configs[tzpath] = tzconfig + + # CentOS has a ZONE setting in /etc/sysconfig/clock, + # OpenSUSE has a TIMEZONE setting in /etc/sysconfig/clock and + # Gentoo has a TIMEZONE setting in /etc/conf.d/clock + # We look through these files for a timezone: + + for filename in ('etc/sysconfig/clock', 'etc/conf.d/clock'): + tzpath = os.path.join(_root, filename) + tzconfig = _read_clock_timezone(tzpath) + if tzconfig is not None: + found_configs[tzpath] = tzconfig + + # systemd distributions use symlinks that include the zone name, + # see manpage of localtime(5) and timedatectl(1) + tzpath = os.path.join(_root, 'etc/localtime') + if os.path.exists(tzpath) and os.path.islink(tzpath): + LOG.debug(_('{!r} found.').format(tzpath)) + etctz = os.path.realpath(tzpath) + start = etctz.find('/') + 1 + while start != 0: + etctz = etctz[start:] + try: + zoneinfo.ZoneInfo(etctz) + tzinfo = f'{tzpath} is a symlink to' + found_configs[tzinfo] = etctz.replace(' ', '_') + # Only need first valid relative path in simlink. + break + except zoneinfo.ZoneInfoNotFoundError: + pass + start = etctz.find('/') + 1 + + if len(found_configs) > 0: + msg = _('{} found:').format(len(found_configs)) + '\n' + found_configs + LOG.debug(msg) + # We found some explicit config of some sort! + if len(found_configs) > 1: + # Uh-oh, multiple configs. See if they match: + unique_tzs = set() + zoneinfopath = os.path.join(_root, 'usr', 'share', 'zoneinfo') + directory_depth = len(zoneinfopath.split(os.path.sep)) + + for tzname in found_configs.values(): + # Look them up in /usr/share/zoneinfo, and find what they + # really point to: + path = os.path.realpath(os.path.join(zoneinfopath, *tzname.split('/'))) + real_zone_name = '/'.join(path.split(os.path.sep)[directory_depth:]) + unique_tzs.add(real_zone_name) + + if len(unique_tzs) != 1: + message = _('Multiple conflicting time zone configurations found:') + '\n' + for key, value in found_configs.items(): + message += f'{key}: {value}\n' + message += _( + 'Fix the configuration, or set the time zone in a TZ environment ' + 'variable.') + '\n' + raise zoneinfo.ZoneInfoNotFoundError(message) + + # We found exactly one config! Use it. + return list(found_configs.values())[0] + + +# ============================================================================= +def _get_localzone(_root='/'): + """ + Create a timezone object from the timezone name. + + If there is no timezone config, it will try to create a file from the + localtime timezone, and if there isn't one, it will default to UTC. + + The parameter _root makes the function look for files like /etc/localtime + beneath the _root directory. This is primarily used by the tests. + In normal usage you call the function without parameters. + """ + # First try the ENV setting. + tzenv = _tz_from_env() + if tzenv: + return tzenv + + tzname = _get_localzone_name(_root) + if tzname is None: + # No explicit setting existed. Use localtime + LOG.debug(_('No explicit setting existed. Use localtime".')) + for filename in ('etc/localtime', 'usr/local/etc/localtime'): + tzpath = os.path.join(_root, filename) + + if not os.path.exists(tzpath): + continue + with open(tzpath, 'rb') as tzfile: + tz = zoneinfo.ZoneInfo.from_file(tzfile, key='local') + break + else: + warnings.warn(_('Can not find any timezone configuration, defaulting to UTC.')) + tz = datetime.timezone.utc + else: + tz = zoneinfo.ZoneInfo(tzname) + + if _root == '/': + # We are using a file in etc to name the timezone. + # Verify that the timezone specified there is actually used: + assert_tz_offset(tz, error=False) + return tz + + +# ============================================================================= +def get_localzone_name(): + """Get the computers configured local timezone name, if any.""" + global CACHE_TZ_NAME + + if CACHE_TZ_NAME is None: + CACHE_TZ_NAME = _get_localzone_name() + + return CACHE_TZ_NAME + + +# ============================================================================= +def get_localzone(): + """Get the computers configured local timezone, if any.""" + global CACHE_TZ + + if CACHE_TZ is None: + CACHE_TZ = _get_localzone() + + return CACHE_TZ + + +# ============================================================================= +def reload_localzone(): + """Reload the cached localzone. You need to call this if the timezone has changed.""" + global CACHE_TZ_NAME + global CACHE_TZ + + CACHE_TZ_NAME = _get_localzone_name() + CACHE_TZ = _get_localzone() + + return CACHE_TZ + + +# ============================================================================= + +if __name__ == '__main__': + + pass + +# ============================================================================= + +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 list -- 2.39.5