diff --git a/lib/pytz_deprecation_shim/__init__.py b/lib/pytz_deprecation_shim/__init__.py new file mode 100644 index 00000000..8b451620 --- /dev/null +++ b/lib/pytz_deprecation_shim/__init__.py @@ -0,0 +1,34 @@ +__all__ = [ + "AmbiguousTimeError", + "NonExistentTimeError", + "InvalidTimeError", + "UnknownTimeZoneError", + "PytzUsageWarning", + "FixedOffset", + "UTC", + "utc", + "build_tzinfo", + "timezone", + "fixed_offset_timezone", + "wrap_zone", +] + +from . import helpers +from ._exceptions import ( + AmbiguousTimeError, + InvalidTimeError, + NonExistentTimeError, + PytzUsageWarning, + UnknownTimeZoneError, +) +from ._impl import ( + UTC, + build_tzinfo, + fixed_offset_timezone, + timezone, + wrap_zone, +) + +# Compatibility aliases +utc = UTC +FixedOffset = fixed_offset_timezone diff --git a/lib/pytz_deprecation_shim/_common.py b/lib/pytz_deprecation_shim/_common.py new file mode 100644 index 00000000..ace322e9 --- /dev/null +++ b/lib/pytz_deprecation_shim/_common.py @@ -0,0 +1,13 @@ +import sys + +_PYTZ_IMPORTED = False + + +def pytz_imported(): + """Detects whether or not pytz has been imported without importing pytz.""" + global _PYTZ_IMPORTED + + if not _PYTZ_IMPORTED and "pytz" in sys.modules: + _PYTZ_IMPORTED = True + + return _PYTZ_IMPORTED diff --git a/lib/pytz_deprecation_shim/_compat.py b/lib/pytz_deprecation_shim/_compat.py new file mode 100644 index 00000000..5b734592 --- /dev/null +++ b/lib/pytz_deprecation_shim/_compat.py @@ -0,0 +1,15 @@ +import sys + +if sys.version_info[0] == 2: + from . import _compat_py2 as _compat_impl +else: + from . import _compat_py3 as _compat_impl + +UTC = _compat_impl.UTC +get_timezone = _compat_impl.get_timezone +get_timezone_file = _compat_impl.get_timezone_file +get_fixed_offset_zone = _compat_impl.get_fixed_offset_zone +is_ambiguous = _compat_impl.is_ambiguous +is_imaginary = _compat_impl.is_imaginary +enfold = _compat_impl.enfold +get_fold = _compat_impl.get_fold diff --git a/lib/pytz_deprecation_shim/_compat_py2.py b/lib/pytz_deprecation_shim/_compat_py2.py new file mode 100644 index 00000000..f473d267 --- /dev/null +++ b/lib/pytz_deprecation_shim/_compat_py2.py @@ -0,0 +1,43 @@ +from datetime import timedelta + +from dateutil import tz + +UTC = tz.UTC + + +def get_timezone(key): + if not key: + raise KeyError("Unknown time zone: %s" % key) + + try: + rv = tz.gettz(key) + except Exception: + rv = None + + if rv is None or not isinstance(rv, (tz.tzutc, tz.tzfile)): + raise KeyError("Unknown time zone: %s" % key) + + return rv + + +def get_timezone_file(f, key=None): + return tz.tzfile(f) + + +def get_fixed_offset_zone(offset): + return tz.tzoffset(None, timedelta(minutes=offset)) + + +def is_ambiguous(dt): + return tz.datetime_ambiguous(dt) + + +def is_imaginary(dt): + return not tz.datetime_exists(dt) + + +enfold = tz.enfold + + +def get_fold(dt): + return getattr(dt, "fold", 0) diff --git a/lib/pytz_deprecation_shim/_compat_py3.py b/lib/pytz_deprecation_shim/_compat_py3.py new file mode 100644 index 00000000..8881abac --- /dev/null +++ b/lib/pytz_deprecation_shim/_compat_py3.py @@ -0,0 +1,58 @@ +# Note: This file could use Python 3-only syntax, but at the moment this breaks +# the coverage job on Python 2. Until we make it so that coverage can ignore +# this file only on Python 2, we'll have to stick to 2/3-compatible syntax. +try: + import zoneinfo +except ImportError: + from backports import zoneinfo + +import datetime + +UTC = datetime.timezone.utc + + +def get_timezone(key): + try: + return zoneinfo.ZoneInfo(key) + except (ValueError, OSError): + # TODO: Use `from e` when this file can use Python 3 syntax + raise KeyError(key) + + +def get_timezone_file(f, key=None): + return zoneinfo.ZoneInfo.from_file(f, key=key) + + +def get_fixed_offset_zone(offset): + return datetime.timezone(datetime.timedelta(minutes=offset)) + + +def is_imaginary(dt): + dt_rt = dt.astimezone(UTC).astimezone(dt.tzinfo) + + return not (dt == dt_rt) + + +def is_ambiguous(dt): + if is_imaginary(dt): + return False + + wall_0 = dt + wall_1 = dt.replace(fold=not dt.fold) + + # Ambiguous datetimes can only exist if the offset changes, so we don't + # actually have to check whether dst() or tzname() are different. + same_offset = wall_0.utcoffset() == wall_1.utcoffset() + + return not same_offset + + +def enfold(dt, fold=1): + if dt.fold != fold: + return dt.replace(fold=fold) + else: + return dt + + +def get_fold(dt): + return dt.fold diff --git a/lib/pytz_deprecation_shim/_exceptions.py b/lib/pytz_deprecation_shim/_exceptions.py new file mode 100644 index 00000000..58d7af0a --- /dev/null +++ b/lib/pytz_deprecation_shim/_exceptions.py @@ -0,0 +1,75 @@ +from ._common import pytz_imported + + +class PytzUsageWarning(RuntimeWarning): + """Warning raised when accessing features specific to ``pytz``'s interface. + + This warning is used to direct users of ``pytz``-specific features like the + ``localize`` and ``normalize`` methods towards using the standard + ``tzinfo`` interface, so that these shims can be replaced with one of the + underlying libraries they are wrapping. + """ + + +class UnknownTimeZoneError(KeyError): + """Raised when no time zone is found for a specified key.""" + + +class InvalidTimeError(Exception): + """The base class for exceptions related to folds and gaps.""" + + +class AmbiguousTimeError(InvalidTimeError): + """Exception raised when ``is_dst=None`` for an ambiguous time (fold).""" + + +class NonExistentTimeError(InvalidTimeError): + """Exception raised when ``is_dst=None`` for a non-existent time (gap).""" + + +PYTZ_BASE_ERROR_MAPPING = {} + + +def _make_pytz_derived_errors( + InvalidTimeError_=InvalidTimeError, + AmbiguousTimeError_=AmbiguousTimeError, + NonExistentTimeError_=NonExistentTimeError, + UnknownTimeZoneError_=UnknownTimeZoneError, +): + if PYTZ_BASE_ERROR_MAPPING or not pytz_imported(): + return + + import pytz + + class InvalidTimeError(InvalidTimeError_, pytz.InvalidTimeError): + pass + + class AmbiguousTimeError(AmbiguousTimeError_, pytz.AmbiguousTimeError): + pass + + class NonExistentTimeError( + NonExistentTimeError_, pytz.NonExistentTimeError + ): + pass + + class UnknownTimeZoneError( + UnknownTimeZoneError_, pytz.UnknownTimeZoneError + ): + pass + + PYTZ_BASE_ERROR_MAPPING.update( + { + InvalidTimeError_: InvalidTimeError, + AmbiguousTimeError_: AmbiguousTimeError, + NonExistentTimeError_: NonExistentTimeError, + UnknownTimeZoneError_: UnknownTimeZoneError, + } + ) + + +def get_exception(exc_type, msg): + _make_pytz_derived_errors() + + out_exc_type = PYTZ_BASE_ERROR_MAPPING.get(exc_type, exc_type) + + return out_exc_type(msg) diff --git a/lib/pytz_deprecation_shim/_impl.py b/lib/pytz_deprecation_shim/_impl.py new file mode 100644 index 00000000..54430479 --- /dev/null +++ b/lib/pytz_deprecation_shim/_impl.py @@ -0,0 +1,296 @@ +# -*- coding: utf-8 -*- +import warnings +from datetime import tzinfo + +from . import _compat +from ._exceptions import ( + AmbiguousTimeError, + NonExistentTimeError, + PytzUsageWarning, + UnknownTimeZoneError, + get_exception, +) + +IS_DST_SENTINEL = object() +KEY_SENTINEL = object() + + +def timezone(key, _cache={}): + """Builds an IANA database time zone shim. + + This is the equivalent of ``pytz.timezone``. + + :param key: + A valid key from the IANA time zone database. + + :raises UnknownTimeZoneError: + If an unknown value is passed, this will raise an exception that can be + caught by :exc:`pytz_deprecation_shim.UnknownTimeZoneError` or + ``pytz.UnknownTimeZoneError``. Like + :exc:`zoneinfo.ZoneInfoNotFoundError`, both of those are subclasses of + :exc:`KeyError`. + """ + instance = _cache.get(key, None) + if instance is None: + if len(key) == 3 and key.lower() == "utc": + instance = _cache.setdefault(key, UTC) + else: + try: + zone = _compat.get_timezone(key) + except KeyError: + raise get_exception(UnknownTimeZoneError, key) + instance = _cache.setdefault(key, wrap_zone(zone, key=key)) + + return instance + + +def fixed_offset_timezone(offset, _cache={}): + """Builds a fixed offset time zone shim. + + This is the equivalent of ``pytz.FixedOffset``. An alias is available as + ``pytz_deprecation_shim.FixedOffset`` as well. + + :param offset: + A fixed offset from UTC, in minutes. This must be in the range ``-1439 + <= offset <= 1439``. + + :raises ValueError: + For offsets whose absolute value is greater than or equal to 24 hours. + + :return: + A shim time zone. + """ + if not (-1440 < offset < 1440): + raise ValueError("absolute offset is too large", offset) + + instance = _cache.get(offset, None) + if instance is None: + if offset == 0: + instance = _cache.setdefault(offset, UTC) + else: + zone = _compat.get_fixed_offset_zone(offset) + instance = _cache.setdefault(offset, wrap_zone(zone, key=None)) + + return instance + + +def build_tzinfo(zone, fp): + """Builds a shim object from a TZif file. + + This is a shim for ``pytz.build_tzinfo``. Given a value to use as the zone + IANA key and a file-like object containing a valid TZif file (i.e. + conforming to :rfc:`8536`), this builds a time zone object and wraps it in + a shim class. + + The argument names are chosen to match those in ``pytz.build_tzinfo``. + + :param zone: + A string to be used as the time zone object's IANA key. + + :param fp: + A readable file-like object emitting bytes, pointing to a valid TZif + file. + + :return: + A shim time zone. + """ + zone_file = _compat.get_timezone_file(fp) + + return wrap_zone(zone_file, key=zone) + + +def wrap_zone(tz, key=KEY_SENTINEL, _cache={}): + """Wrap an existing time zone object in a shim class. + + This is likely to be useful if you would like to work internally with + non-``pytz`` zones, but you expose an interface to callers relying on + ``pytz``'s interface. It may also be useful for passing non-``pytz`` zones + to libraries expecting to use ``pytz``'s interface. + + :param tz: + A :pep:`495`-compatible time zone, such as those provided by + :mod:`dateutil.tz` or :mod:`zoneinfo`. + + :param key: + The value for the IANA time zone key. This is optional for ``zoneinfo`` + zones, but required for ``dateutil.tz`` zones. + + :return: + A shim time zone. + """ + if key is KEY_SENTINEL: + key = getattr(tz, "key", KEY_SENTINEL) + + if key is KEY_SENTINEL: + raise TypeError( + "The `key` argument is required when wrapping zones that do not " + + "have a `key` attribute." + ) + + instance = _cache.get((id(tz), key), None) + if instance is None: + instance = _cache.setdefault((id(tz), key), _PytzShimTimezone(tz, key)) + + return instance + + +class _PytzShimTimezone(tzinfo): + # Add instance variables for _zone and _key because this will make error + # reporting with partially-initialized _BasePytzShimTimezone objects + # work better. + _zone = None + _key = None + + def __init__(self, zone, key): + self._key = key + self._zone = zone + + def utcoffset(self, dt): + return self._zone.utcoffset(dt) + + def dst(self, dt): + return self._zone.dst(dt) + + def tzname(self, dt): + return self._zone.tzname(dt) + + def fromutc(self, dt): + # The default fromutc implementation only works if tzinfo is "self" + dt_base = dt.replace(tzinfo=self._zone) + dt_out = self._zone.fromutc(dt_base) + + return dt_out.replace(tzinfo=self) + + def __str__(self): + if self._key is not None: + return str(self._key) + else: + return repr(self) + + def __repr__(self): + return "%s(%s, %s)" % ( + self.__class__.__name__, + repr(self._zone), + repr(self._key), + ) + + def unwrap_shim(self): + """Returns the underlying class that the shim is a wrapper for. + + This is a shim-specific method equivalent to + :func:`pytz_deprecation_shim.helpers.upgrade_tzinfo`. It is provided as + a method to allow end-users to upgrade shim timezones without requiring + an explicit dependency on ``pytz_deprecation_shim``, e.g.: + + .. code-block:: python + + if getattr(tz, "unwrap_shim", None) is None: + tz = tz.unwrap_shim() + """ + return self._zone + + @property + def zone(self): + warnings.warn( + "The zone attribute is specific to pytz's interface; " + + "please migrate to a new time zone provider. " + + "For more details on how to do so, see %s" + % PYTZ_MIGRATION_GUIDE_URL, + PytzUsageWarning, + stacklevel=2, + ) + + return self._key + + def localize(self, dt, is_dst=IS_DST_SENTINEL): + warnings.warn( + "The localize method is no longer necessary, as this " + + "time zone supports the fold attribute (PEP 495). " + + "For more details on migrating to a PEP 495-compliant " + + "implementation, see %s" % PYTZ_MIGRATION_GUIDE_URL, + PytzUsageWarning, + stacklevel=2, + ) + + if dt.tzinfo is not None: + raise ValueError("Not naive datetime (tzinfo is already set)") + + dt_out = dt.replace(tzinfo=self) + + if is_dst is IS_DST_SENTINEL: + return dt_out + + dt_ambiguous = _compat.is_ambiguous(dt_out) + dt_imaginary = ( + _compat.is_imaginary(dt_out) if not dt_ambiguous else False + ) + + if is_dst is None: + if dt_imaginary: + raise get_exception( + NonExistentTimeError, dt.replace(tzinfo=None) + ) + + if dt_ambiguous: + raise get_exception(AmbiguousTimeError, dt.replace(tzinfo=None)) + + elif dt_ambiguous or dt_imaginary: + # Start by normalizing the folds; dt_out may have fold=0 or fold=1, + # but we need to know the DST offset on both sides anyway, so we + # will get one datetime representing each side of the fold, then + # decide which one we're going to return. + if _compat.get_fold(dt_out): + dt_enfolded = dt_out + dt_out = _compat.enfold(dt_out, fold=0) + else: + dt_enfolded = _compat.enfold(dt_out, fold=1) + + # Now we want to decide whether the fold=0 or fold=1 represents + # what pytz would return for `is_dst=True` + enfolded_dst = bool(dt_enfolded.dst()) + if bool(dt_out.dst()) == enfolded_dst: + # If this is not a transition between standard time and + # daylight saving time, pytz will consider the larger offset + # the DST offset. + enfolded_dst = dt_enfolded.utcoffset() > dt_out.utcoffset() + + # The default we've established is that dt_out is fold=0; swap it + # for the fold=1 datetime if is_dst == True and the enfolded side + # is DST or if is_dst == False and the enfolded side is *not* DST. + if is_dst == enfolded_dst: + dt_out = dt_enfolded + + return dt_out + + def normalize(self, dt): + warnings.warn( + "The normalize method is no longer necessary, as this " + + "time zone supports the fold attribute (PEP 495). " + + "For more details on migrating to a PEP 495-compliant " + + "implementation, see %s" % PYTZ_MIGRATION_GUIDE_URL, + PytzUsageWarning, + stacklevel=2, + ) + + if dt.tzinfo is None: + raise ValueError("Naive time - no tzinfo set") + + if dt.tzinfo is self: + return dt + + return dt.astimezone(self) + + def __copy__(self): + return self + + def __deepcopy__(self, memo=None): + return self + + def __reduce__(self): + return wrap_zone, (self._zone, self._key) + + +UTC = wrap_zone(_compat.UTC, "UTC") +PYTZ_MIGRATION_GUIDE_URL = ( + "https://pytz-deprecation-shim.readthedocs.io/en/latest/migration.html" +) diff --git a/lib/pytz_deprecation_shim/helpers.py b/lib/pytz_deprecation_shim/helpers.py new file mode 100644 index 00000000..6b05b130 --- /dev/null +++ b/lib/pytz_deprecation_shim/helpers.py @@ -0,0 +1,90 @@ +""" +This module contains helper functions to ease the transition from ``pytz`` to +another :pep:`495`-compatible library. +""" +from . import _common, _compat +from ._impl import _PytzShimTimezone + +_PYTZ_BASE_CLASSES = None + + +def is_pytz_zone(tz): + """Check if a time zone is a ``pytz`` time zone. + + This will only import ``pytz`` if it has already been imported, and does + not rely on the existence of the ``localize`` or ``normalize`` methods + (since the shim classes also have these methods, but are not ``pytz`` + zones). + """ + + # If pytz is not in sys.modules, then we will assume the time zone is not a + # pytz zone. It is possible that someone has manipulated sys.modules to + # remove pytz, but that's the kind of thing that causes all kinds of other + # problems anyway, so we'll call that an unsupported configuration. + if not _common.pytz_imported(): + return False + + if _PYTZ_BASE_CLASSES is None: + _populate_pytz_base_classes() + + return isinstance(tz, _PYTZ_BASE_CLASSES) + + +def upgrade_tzinfo(tz): + """Convert a ``pytz`` or shim timezone into its modern equivalent. + + The shim classes are thin wrappers around :mod:`zoneinfo` or + :mod:`dateutil.tz` implementations of the :class:`datetime.tzinfo` base + class. This function removes the shim and returns the underlying "upgraded" + time zone. + + When passed a ``pytz`` zone (not a shim), this returns the non-``pytz`` + equivalent. This may fail if ``pytz`` is using a data source incompatible + with the upgraded provider's data source, or if the ``pytz`` zone was built + from a file rather than an IANA key. + + When passed an object that is not a shim or a ``pytz`` zone, this returns + the original object. + + :param tz: + A :class:`datetime.tzinfo` object. + + :raises KeyError: + If a ``pytz`` zone is passed to the function with no equivalent in the + :pep:`495`-compatible library's version of the Olson database. + + :return: + A :pep:`495`-compatible equivalent of any ``pytz`` or shim + class, or the original object. + """ + if isinstance(tz, _PytzShimTimezone): + return tz._zone + + if is_pytz_zone(tz): + if tz.zone is None: + # This is a fixed offset zone + offset = tz.utcoffset(None) + offset_minutes = offset.total_seconds() / 60 + + return _compat.get_fixed_offset_zone(offset_minutes) + + if tz.zone == "UTC": + return _compat.UTC + + return _compat.get_timezone(tz.zone) + + return tz + + +def _populate_pytz_base_classes(): + import pytz + from pytz.tzinfo import BaseTzInfo + + base_classes = (BaseTzInfo, pytz._FixedOffset) + + # In releases prior to 2018.4, pytz.UTC was not a subclass of BaseTzInfo + if not isinstance(pytz.UTC, BaseTzInfo): # pragma: nocover + base_classes = base_classes + (type(pytz.UTC),) + + global _PYTZ_BASE_CLASSES + _PYTZ_BASE_CLASSES = base_classes diff --git a/lib/tzdata/__init__.py b/lib/tzdata/__init__.py index 4fd76c37..07c7b3a2 100644 --- a/lib/tzdata/__init__.py +++ b/lib/tzdata/__init__.py @@ -1,6 +1,6 @@ # IANA versions like 2020a are not valid PEP 440 identifiers; the recommended # way to translate the version is to use YYYY.n where `n` is a 0-based index. -__version__ = "2021.2.post0" +__version__ = "2021.5" # This exposes the original IANA version number. -IANA_VERSION = "2021b" +IANA_VERSION = "2021e" diff --git a/lib/tzdata/zoneinfo/Asia/Gaza b/lib/tzdata/zoneinfo/Asia/Gaza index 58e9fdf4..ccc57c9c 100644 Binary files a/lib/tzdata/zoneinfo/Asia/Gaza and b/lib/tzdata/zoneinfo/Asia/Gaza differ diff --git a/lib/tzdata/zoneinfo/Asia/Hebron b/lib/tzdata/zoneinfo/Asia/Hebron index aeda06b5..906d8d5c 100644 Binary files a/lib/tzdata/zoneinfo/Asia/Hebron and b/lib/tzdata/zoneinfo/Asia/Hebron differ diff --git a/lib/tzdata/zoneinfo/Atlantic/Jan_Mayen b/lib/tzdata/zoneinfo/Atlantic/Jan_Mayen index 465546bd..dfc50957 100644 Binary files a/lib/tzdata/zoneinfo/Atlantic/Jan_Mayen and b/lib/tzdata/zoneinfo/Atlantic/Jan_Mayen differ diff --git a/lib/tzdata/zoneinfo/Pacific/Fiji b/lib/tzdata/zoneinfo/Pacific/Fiji index e3934e42..8b2dd52b 100644 Binary files a/lib/tzdata/zoneinfo/Pacific/Fiji and b/lib/tzdata/zoneinfo/Pacific/Fiji differ diff --git a/lib/tzdata/zoneinfo/tzdata.zi b/lib/tzdata/zoneinfo/tzdata.zi index fa3ebc23..1948c725 100644 --- a/lib/tzdata/zoneinfo/tzdata.zi +++ b/lib/tzdata/zoneinfo/tzdata.zi @@ -1,4 +1,4 @@ -# version 2021b +# version 2021e # This zic input file is in the public domain. R d 1916 o - Jun 14 23s 1 S R d 1916 1919 - O Su>=1 23s 0 - @@ -43,6 +43,16 @@ Z Africa/Ndjamena 1:0:12 - LMT 1912 1 - WAT Z Africa/Abidjan -0:16:8 - LMT 1912 0 - GMT +L Africa/Abidjan Africa/Accra +L Africa/Abidjan Africa/Bamako +L Africa/Abidjan Africa/Banjul +L Africa/Abidjan Africa/Conakry +L Africa/Abidjan Africa/Dakar +L Africa/Abidjan Africa/Freetown +L Africa/Abidjan Africa/Lome +L Africa/Abidjan Africa/Nouakchott +L Africa/Abidjan Africa/Ouagadougou +L Africa/Abidjan Atlantic/St_Helena R K 1940 o - Jul 15 0 1 S R K 1940 o - O 1 0 0 - R K 1941 o - Ap 15 0 1 S @@ -86,6 +96,15 @@ Z Africa/Nairobi 2:27:16 - LMT 1908 May 2:30 - +0230 1936 D 31 24 2:45 - +0245 1942 Jul 31 24 3 - EAT +L Africa/Nairobi Africa/Addis_Ababa +L Africa/Nairobi Africa/Asmara +L Africa/Nairobi Africa/Dar_es_Salaam +L Africa/Nairobi Africa/Djibouti +L Africa/Nairobi Africa/Kampala +L Africa/Nairobi Africa/Mogadishu +L Africa/Nairobi Indian/Antananarivo +L Africa/Nairobi Indian/Comoro +L Africa/Nairobi Indian/Mayotte Z Africa/Monrovia -0:43:8 - LMT 1882 -0:43:8 - MMT 1919 Mar -0:44:30 - MMT 1972 Ja 7 @@ -316,6 +335,13 @@ Z Africa/El_Aaiun -0:52:48 - LMT 1934 1 M +01/+00 Z Africa/Maputo 2:10:20 - LMT 1903 Mar 2 - CAT +L Africa/Maputo Africa/Blantyre +L Africa/Maputo Africa/Bujumbura +L Africa/Maputo Africa/Gaborone +L Africa/Maputo Africa/Harare +L Africa/Maputo Africa/Kigali +L Africa/Maputo Africa/Lubumbashi +L Africa/Maputo Africa/Lusaka R NA 1994 o - Mar 21 0 -1 WAT R NA 1994 2017 - S Su>=1 2 0 CAT R NA 1995 2017 - Ap Su>=1 2 -1 WAT @@ -330,6 +356,15 @@ Z Africa/Lagos 0:13:35 - LMT 1905 Jul 0:13:35 - LMT 1914 0:30 - +0030 1919 S 1 - WAT +L Africa/Lagos Africa/Bangui +L Africa/Lagos Africa/Brazzaville +L Africa/Lagos Africa/Douala +L Africa/Lagos Africa/Kinshasa +L Africa/Lagos Africa/Libreville +L Africa/Lagos Africa/Luanda +L Africa/Lagos Africa/Malabo +L Africa/Lagos Africa/Niamey +L Africa/Lagos Africa/Porto-Novo Z Indian/Reunion 3:41:52 - LMT 1911 Jun 4 - +04 Z Africa/Sao_Tome 0:26:56 - LMT 1884 @@ -344,6 +379,8 @@ R SA 1943 1944 - Mar Su>=15 2 0 - Z Africa/Johannesburg 1:52 - LMT 1892 F 8 1:30 - SAST 1903 Mar 2 SA SAST +L Africa/Johannesburg Africa/Maseru +L Africa/Johannesburg Africa/Mbabane R SD 1970 o - May 1 0 1 S R SD 1970 1985 - O 15 0 0 - R SD 1971 o - Ap 30 0 1 S @@ -571,6 +608,7 @@ Z Asia/Famagusta 2:15:48 - LMT 1921 N 14 2 E EE%sT 2016 S 8 3 - +03 2017 O 29 1u 2 E EE%sT +L Asia/Nicosia Europe/Nicosia Z Asia/Tbilisi 2:59:11 - LMT 1880 2:59:11 - TBMT 1924 May 2 3 - +03 1957 Mar @@ -1074,7 +1112,8 @@ R P 2016 2018 - O Sa>=24 1 0 - R P 2019 o - Mar 29 0 1 S R P 2019 o - O Sa>=24 0 0 - R P 2020 ma - Mar Sa>=24 0 1 S -R P 2020 ma - O Sa>=24 1 0 - +R P 2020 o - O 24 1 0 - +R P 2021 ma - O lastF 1 0 - Z Asia/Gaza 2:17:52 - LMT 1900 O 2 Z EET/EEST 1948 May 15 2 K EE%sT 1967 Jun 5 @@ -1107,8 +1146,12 @@ Z Asia/Manila -15:56 - LMT 1844 D 31 Z Asia/Qatar 3:26:8 - LMT 1920 4 - +04 1972 Jun 3 - +03 +L Asia/Qatar Asia/Bahrain Z Asia/Riyadh 3:6:52 - LMT 1947 Mar 14 3 - +03 +L Asia/Riyadh Antarctica/Syowa +L Asia/Riyadh Asia/Aden +L Asia/Riyadh Asia/Kuwait Z Asia/Singapore 6:55:25 - LMT 1901 6:55:25 - SMT 1905 Jun 7 - +07 1933 @@ -1178,6 +1221,8 @@ Z Asia/Dushanbe 4:35:12 - LMT 1924 May 2 Z Asia/Bangkok 6:42:4 - LMT 1880 6:42:4 - BMT 1920 Ap 7 - +07 +L Asia/Bangkok Asia/Phnom_Penh +L Asia/Bangkok Asia/Vientiane Z Asia/Ashgabat 3:53:32 - LMT 1924 May 2 4 - +04 1930 Jun 21 5 R +05/+06 1991 Mar 31 2 @@ -1185,6 +1230,7 @@ Z Asia/Ashgabat 3:53:32 - LMT 1924 May 2 5 - +05 Z Asia/Dubai 3:41:12 - LMT 1920 4 - +04 +L Asia/Dubai Asia/Muscat Z Asia/Samarkand 4:27:53 - LMT 1924 May 2 4 - +04 1930 Jun 21 5 - +05 1981 Ap @@ -1373,10 +1419,11 @@ R FJ 2011 o - Mar Su>=1 3 0 - R FJ 2012 2013 - Ja Su>=18 3 0 - R FJ 2014 o - Ja Su>=18 2 0 - R FJ 2014 2018 - N Su>=1 2 1 - -R FJ 2015 ma - Ja Su>=12 3 0 - +R FJ 2015 2021 - Ja Su>=12 3 0 - R FJ 2019 o - N Su>=8 2 1 - R FJ 2020 o - D 20 2 1 - -R FJ 2021 ma - N Su>=8 2 1 - +R FJ 2022 ma - N Su>=8 2 1 - +R FJ 2023 ma - Ja Su>=12 3 0 - Z Pacific/Fiji 11:55:44 - LMT 1915 O 26 12 FJ +12/+13 Z Pacific/Gambier -8:59:48 - LMT 1912 O @@ -1405,6 +1452,7 @@ Z Pacific/Guam -14:21 - LMT 1844 D 31 9 - +09 1944 Jul 31 10 Gu G%sT 2000 D 23 10 - ChST +L Pacific/Guam Pacific/Saipan Z Pacific/Tarawa 11:32:4 - LMT 1901 12 - +12 Z Pacific/Kanton 0 - -00 1937 Au 31 @@ -1497,6 +1545,7 @@ Z Pacific/Auckland 11:39:4 - LMT 1868 N 2 Z Pacific/Chatham 12:13:48 - LMT 1868 N 2 12:15 - +1215 1946 12:45 k +1245/+1345 +L Pacific/Auckland Antarctica/McMurdo R CK 1978 o - N 12 0 0:30 - R CK 1979 1991 - Mar Su>=1 0 0 - R CK 1979 1990 - O lastSu 0 0:30 - @@ -1520,6 +1569,7 @@ Z Pacific/Palau -15:2:4 - LMT 1844 D 31 Z Pacific/Port_Moresby 9:48:40 - LMT 1880 9:48:32 - PMMT 1895 10 - +10 +L Pacific/Port_Moresby Antarctica/DumontDUrville Z Pacific/Bougainville 10:22:16 - LMT 1880 9:48:32 - PMMT 1895 10 - +10 1942 Jul @@ -1532,6 +1582,7 @@ Z Pacific/Pitcairn -8:40:20 - LMT 1901 Z Pacific/Pago_Pago 12:37:12 - LMT 1892 Jul 5 -11:22:48 - LMT 1911 -11 - SST +L Pacific/Pago_Pago Pacific/Midway R WS 2010 o - S lastSu 0 1 - R WS 2011 o - Ap Sa>=1 4 0 - R WS 2011 o - S lastSa 3 1 - @@ -1641,6 +1692,9 @@ Z Europe/London -0:1:15 - LMT 1847 D 1 0s 1 - BST 1971 O 31 2u 0 G %s 1996 0 E GMT/BST +L Europe/London Europe/Jersey +L Europe/London Europe/Guernsey +L Europe/London Europe/Isle_of_Man R IE 1971 o - O 31 2u -1 - R IE 1972 1980 - Mar Su>=16 2u 0 - R IE 1972 1980 - O Su>=23 2u -1 - @@ -1904,6 +1958,7 @@ Z Europe/Helsinki 1:39:49 - LMT 1878 May 31 1:39:49 - HMT 1921 May 2 FI EE%sT 1983 2 E EE%sT +L Europe/Helsinki Europe/Mariehamn R F 1916 o - Jun 14 23s 1 S R F 1916 1919 - O Su>=1 23s 0 - R F 1917 o - Mar 24 23s 1 S @@ -1969,6 +2024,7 @@ Z Europe/Berlin 0:53:28 - LMT 1893 Ap 1 So CE%sT 1946 1 DE CE%sT 1980 1 E CE%sT +L Europe/Zurich Europe/Busingen Z Europe/Gibraltar -0:21:24 - LMT 1880 Au 2 0s 0 G %s 1957 Ap 14 2 1 - CET 1982 @@ -2089,6 +2145,8 @@ Z Europe/Rome 0:49:56 - LMT 1866 D 12 1 c CE%sT 1944 Jun 4 1 I CE%sT 1980 1 E CE%sT +L Europe/Rome Europe/Vatican +L Europe/Rome Europe/San_Marino R LV 1989 1996 - Mar lastSu 2s 1 S R LV 1989 1996 - S lastSu 2s 0 - Z Europe/Riga 1:36:34 - LMT 1880 @@ -2106,6 +2164,7 @@ Z Europe/Riga 1:36:34 - LMT 1880 2 E EE%sT 2000 F 29 2 - EET 2001 Ja 2 2 E EE%sT +L Europe/Zurich Europe/Vaduz Z Europe/Vilnius 1:41:16 - LMT 1880 1:24 - WMT 1917 1:35:36 - KMT 1919 O 10 @@ -2217,6 +2276,7 @@ Z Europe/Oslo 0:43 - LMT 1895 1 c CE%sT 1945 Ap 2 2 1 NO CE%sT 1980 1 E CE%sT +L Europe/Oslo Arctic/Longyearbyen R O 1918 1919 - S 16 2s 0 - R O 1919 o - Ap 15 2s 1 S R O 1944 o - Ap 3 2s 1 S @@ -2586,6 +2646,12 @@ Z Europe/Belgrade 1:22 - LMT 1884 1 1 CEST 1945 S 16 2s 1 - CET 1982 N 27 1 E CE%sT +L Europe/Belgrade Europe/Ljubljana +L Europe/Belgrade Europe/Podgorica +L Europe/Belgrade Europe/Sarajevo +L Europe/Belgrade Europe/Skopje +L Europe/Belgrade Europe/Zagreb +L Europe/Prague Europe/Bratislava R s 1918 o - Ap 15 23 1 S R s 1918 1919 - O 6 24s 0 - R s 1919 o - Ap 6 23 1 S @@ -2713,6 +2779,7 @@ Z Europe/Istanbul 1:55:52 - LMT 1880 2 1 EEST 2015 N 8 1u 2 E EE%sT 2016 S 7 3 - +03 +L Europe/Istanbul Asia/Istanbul Z Europe/Kiev 2:2:4 - LMT 1880 2:2:4 - KMT 1924 May 2 2 - EET 1930 Jun 21 @@ -2888,6 +2955,7 @@ Z America/Phoenix -7:28:18 - LMT 1883 N 18 11:31:42 -7 - MST 1967 -7 u M%sT 1968 Mar 21 -7 - MST +L America/Phoenix America/Creston Z America/Boise -7:44:49 - LMT 1883 N 18 12:15:11 -8 u P%sT 1923 May 13 2 -7 u M%sT 1974 @@ -3186,6 +3254,7 @@ Z America/Toronto -5:17:32 - LMT 1895 -5 C E%sT 1946 -5 t E%sT 1974 -5 C E%sT +L America/Toronto America/Nassau Z America/Thunder_Bay -5:57 - LMT 1895 -6 - CST 1910 -5 - EST 1942 @@ -3636,10 +3705,31 @@ Z America/Managua -5:45:8 - LMT 1890 Z America/Panama -5:18:8 - LMT 1890 -5:19:36 - CMT 1908 Ap 22 -5 - EST +L America/Panama America/Atikokan +L America/Panama America/Cayman Z America/Puerto_Rico -4:24:25 - LMT 1899 Mar 28 12 -4 - AST 1942 May 3 -4 u A%sT 1946 -4 - AST +L America/Puerto_Rico America/Anguilla +L America/Puerto_Rico America/Antigua +L America/Puerto_Rico America/Aruba +L America/Puerto_Rico America/Curacao +L America/Puerto_Rico America/Blanc-Sablon +L America/Puerto_Rico America/Dominica +L America/Puerto_Rico America/Grenada +L America/Puerto_Rico America/Guadeloupe +L America/Puerto_Rico America/Kralendijk +L America/Puerto_Rico America/Lower_Princes +L America/Puerto_Rico America/Marigot +L America/Puerto_Rico America/Montserrat +L America/Puerto_Rico America/Port_of_Spain +L America/Puerto_Rico America/St_Barthelemy +L America/Puerto_Rico America/St_Kitts +L America/Puerto_Rico America/St_Lucia +L America/Puerto_Rico America/St_Thomas +L America/Puerto_Rico America/St_Vincent +L America/Puerto_Rico America/Tortola Z America/Miquelon -3:44:40 - LMT 1911 May 15 -4 - AST 1980 May -3 - -03 1987 @@ -4221,114 +4311,46 @@ Z Etc/GMT+10 -10 - -10 Z Etc/GMT+11 -11 - -11 Z Etc/GMT+12 -12 - -12 Z Factory 0 - -00 -L Africa/Abidjan Africa/Accra -L Africa/Nairobi Africa/Addis_Ababa -L Africa/Nairobi Africa/Asmara L Africa/Nairobi Africa/Asmera -L Africa/Abidjan Africa/Bamako -L Africa/Lagos Africa/Bangui -L Africa/Abidjan Africa/Banjul -L Africa/Maputo Africa/Blantyre -L Africa/Lagos Africa/Brazzaville -L Africa/Maputo Africa/Bujumbura -L Africa/Abidjan Africa/Conakry -L Africa/Abidjan Africa/Dakar -L Africa/Nairobi Africa/Dar_es_Salaam -L Africa/Nairobi Africa/Djibouti -L Africa/Lagos Africa/Douala -L Africa/Abidjan Africa/Freetown -L Africa/Maputo Africa/Gaborone -L Africa/Maputo Africa/Harare -L Africa/Nairobi Africa/Kampala -L Africa/Maputo Africa/Kigali -L Africa/Lagos Africa/Kinshasa -L Africa/Lagos Africa/Libreville -L Africa/Abidjan Africa/Lome -L Africa/Lagos Africa/Luanda -L Africa/Maputo Africa/Lubumbashi -L Africa/Maputo Africa/Lusaka -L Africa/Lagos Africa/Malabo -L Africa/Johannesburg Africa/Maseru -L Africa/Johannesburg Africa/Mbabane -L Africa/Nairobi Africa/Mogadishu -L Africa/Lagos Africa/Niamey -L Africa/Abidjan Africa/Nouakchott -L Africa/Abidjan Africa/Ouagadougou -L Africa/Lagos Africa/Porto-Novo L Africa/Abidjan Africa/Timbuktu -L America/Puerto_Rico America/Anguilla -L America/Puerto_Rico America/Antigua L America/Argentina/Catamarca America/Argentina/ComodRivadavia -L America/Puerto_Rico America/Aruba -L America/Panama America/Atikokan L America/Adak America/Atka -L America/Puerto_Rico America/Blanc-Sablon L America/Argentina/Buenos_Aires America/Buenos_Aires L America/Argentina/Catamarca America/Catamarca -L America/Panama America/Cayman L America/Panama America/Coral_Harbour L America/Argentina/Cordoba America/Cordoba -L America/Phoenix America/Creston -L America/Puerto_Rico America/Curacao -L America/Puerto_Rico America/Dominica L America/Tijuana America/Ensenada L America/Indiana/Indianapolis America/Fort_Wayne L America/Nuuk America/Godthab -L America/Puerto_Rico America/Grenada -L America/Puerto_Rico America/Guadeloupe L America/Indiana/Indianapolis America/Indianapolis L America/Argentina/Jujuy America/Jujuy L America/Indiana/Knox America/Knox_IN -L America/Puerto_Rico America/Kralendijk L America/Kentucky/Louisville America/Louisville -L America/Puerto_Rico America/Lower_Princes -L America/Puerto_Rico America/Marigot L America/Argentina/Mendoza America/Mendoza L America/Toronto America/Montreal -L America/Puerto_Rico America/Montserrat -L America/Toronto America/Nassau -L America/Puerto_Rico America/Port_of_Spain L America/Rio_Branco America/Porto_Acre L America/Argentina/Cordoba America/Rosario L America/Tijuana America/Santa_Isabel L America/Denver America/Shiprock -L America/Puerto_Rico America/St_Barthelemy -L America/Puerto_Rico America/St_Kitts -L America/Puerto_Rico America/St_Lucia -L America/Puerto_Rico America/St_Thomas -L America/Puerto_Rico America/St_Vincent -L America/Puerto_Rico America/Tortola L America/Puerto_Rico America/Virgin -L Pacific/Port_Moresby Antarctica/DumontDUrville -L Pacific/Auckland Antarctica/McMurdo L Pacific/Auckland Antarctica/South_Pole -L Asia/Riyadh Antarctica/Syowa -L Europe/Oslo Arctic/Longyearbyen -L Asia/Riyadh Asia/Aden L Asia/Ashgabat Asia/Ashkhabad -L Asia/Qatar Asia/Bahrain L Asia/Kolkata Asia/Calcutta L Asia/Shanghai Asia/Chongqing L Asia/Shanghai Asia/Chungking L Asia/Dhaka Asia/Dacca L Asia/Shanghai Asia/Harbin -L Europe/Istanbul Asia/Istanbul L Asia/Urumqi Asia/Kashgar L Asia/Kathmandu Asia/Katmandu -L Asia/Riyadh Asia/Kuwait L Asia/Macau Asia/Macao -L Asia/Dubai Asia/Muscat -L Asia/Bangkok Asia/Phnom_Penh L Asia/Yangon Asia/Rangoon L Asia/Ho_Chi_Minh Asia/Saigon L Asia/Jerusalem Asia/Tel_Aviv L Asia/Thimphu Asia/Thimbu L Asia/Makassar Asia/Ujung_Pandang L Asia/Ulaanbaatar Asia/Ulan_Bator -L Asia/Bangkok Asia/Vientiane L Atlantic/Faroe Atlantic/Faeroe -L Europe/Berlin Atlantic/Jan_Mayen -L Africa/Abidjan Atlantic/St_Helena +L Europe/Oslo Atlantic/Jan_Mayen L Australia/Sydney Australia/ACT L Australia/Sydney Australia/Canberra L Australia/Hobart Australia/Currie @@ -4360,22 +4382,7 @@ L Africa/Cairo Egypt L Europe/Dublin Eire L Etc/UTC Etc/UCT L Europe/London Europe/Belfast -L Europe/Prague Europe/Bratislava -L Europe/Zurich Europe/Busingen -L Europe/London Europe/Guernsey -L Europe/London Europe/Isle_of_Man -L Europe/London Europe/Jersey -L Europe/Belgrade Europe/Ljubljana -L Europe/Helsinki Europe/Mariehamn -L Asia/Nicosia Europe/Nicosia -L Europe/Belgrade Europe/Podgorica -L Europe/Rome Europe/San_Marino -L Europe/Belgrade Europe/Sarajevo -L Europe/Belgrade Europe/Skopje L Europe/Chisinau Europe/Tiraspol -L Europe/Zurich Europe/Vaduz -L Europe/Rome Europe/Vatican -L Europe/Belgrade Europe/Zagreb L Europe/London GB L Europe/London GB-Eire L Etc/GMT GMT+0 @@ -4384,9 +4391,6 @@ L Etc/GMT GMT0 L Etc/GMT Greenwich L Asia/Hong_Kong Hongkong L Atlantic/Reykjavik Iceland -L Africa/Nairobi Indian/Antananarivo -L Africa/Nairobi Indian/Comoro -L Africa/Nairobi Indian/Mayotte L Asia/Tehran Iran L Asia/Jerusalem Israel L America/Jamaica Jamaica @@ -4402,9 +4406,7 @@ L America/Denver Navajo L Asia/Shanghai PRC L Pacific/Kanton Pacific/Enderbury L Pacific/Honolulu Pacific/Johnston -L Pacific/Pago_Pago Pacific/Midway L Pacific/Pohnpei Pacific/Ponape -L Pacific/Guam Pacific/Saipan L Pacific/Pago_Pago Pacific/Samoa L Pacific/Chuuk Pacific/Truk L Pacific/Chuuk Pacific/Yap diff --git a/lib/tzdata/zones b/lib/tzdata/zones index 840009ba..e45aaa80 100644 --- a/lib/tzdata/zones +++ b/lib/tzdata/zones @@ -2,21 +2,58 @@ Africa/Algiers Atlantic/Cape_Verde Africa/Ndjamena Africa/Abidjan +Africa/Accra +Africa/Bamako +Africa/Banjul +Africa/Conakry +Africa/Dakar +Africa/Freetown +Africa/Lome +Africa/Nouakchott +Africa/Ouagadougou +Atlantic/St_Helena Africa/Cairo Africa/Bissau Africa/Nairobi +Africa/Addis_Ababa +Africa/Asmara +Africa/Dar_es_Salaam +Africa/Djibouti +Africa/Kampala +Africa/Mogadishu +Indian/Antananarivo +Indian/Comoro +Indian/Mayotte Africa/Monrovia Africa/Tripoli Indian/Mauritius Africa/Casablanca Africa/El_Aaiun Africa/Maputo +Africa/Blantyre +Africa/Bujumbura +Africa/Gaborone +Africa/Harare +Africa/Kigali +Africa/Lubumbashi +Africa/Lusaka Africa/Windhoek Africa/Lagos +Africa/Bangui +Africa/Brazzaville +Africa/Douala +Africa/Kinshasa +Africa/Libreville +Africa/Luanda +Africa/Malabo +Africa/Niamey +Africa/Porto-Novo Indian/Reunion Africa/Sao_Tome Indian/Mahe Africa/Johannesburg +Africa/Maseru +Africa/Mbabane Africa/Khartoum Africa/Juba Africa/Tunis @@ -42,6 +79,7 @@ Asia/Taipei Asia/Macau Asia/Nicosia Asia/Famagusta +Europe/Nicosia Asia/Tbilisi Asia/Dili Asia/Kolkata @@ -77,14 +115,21 @@ Asia/Gaza Asia/Hebron Asia/Manila Asia/Qatar +Asia/Bahrain Asia/Riyadh +Antarctica/Syowa +Asia/Aden +Asia/Kuwait Asia/Singapore Asia/Colombo Asia/Damascus Asia/Dushanbe Asia/Bangkok +Asia/Phnom_Penh +Asia/Vientiane Asia/Ashgabat Asia/Dubai +Asia/Muscat Asia/Samarkand Asia/Tashkent Asia/Ho_Chi_Minh @@ -107,6 +152,7 @@ Pacific/Gambier Pacific/Marquesas Pacific/Tahiti Pacific/Guam +Pacific/Saipan Pacific/Tarawa Pacific/Kanton Pacific/Kiritimati @@ -119,14 +165,17 @@ Pacific/Nauru Pacific/Noumea Pacific/Auckland Pacific/Chatham +Antarctica/McMurdo Pacific/Rarotonga Pacific/Niue Pacific/Norfolk Pacific/Palau Pacific/Port_Moresby +Antarctica/DumontDUrville Pacific/Bougainville Pacific/Pitcairn Pacific/Pago_Pago +Pacific/Midway Pacific/Apia Pacific/Guadalcanal Pacific/Fakaofo @@ -136,6 +185,9 @@ Pacific/Wake Pacific/Efate Pacific/Wallis Europe/London +Europe/Jersey +Europe/Guernsey +Europe/Isle_of_Man Europe/Dublin WET CET @@ -156,14 +208,19 @@ America/Nuuk America/Thule Europe/Tallinn Europe/Helsinki +Europe/Mariehamn Europe/Paris Europe/Berlin +Europe/Busingen Europe/Gibraltar Europe/Athens Europe/Budapest Atlantic/Reykjavik Europe/Rome +Europe/Vatican +Europe/San_Marino Europe/Riga +Europe/Vaduz Europe/Vilnius Europe/Luxembourg Europe/Malta @@ -171,6 +228,7 @@ Europe/Chisinau Europe/Monaco Europe/Amsterdam Europe/Oslo +Arctic/Longyearbyen Europe/Warsaw Europe/Lisbon Atlantic/Azores @@ -204,12 +262,19 @@ Asia/Ust-Nera Asia/Kamchatka Asia/Anadyr Europe/Belgrade +Europe/Ljubljana +Europe/Podgorica +Europe/Sarajevo +Europe/Skopje +Europe/Zagreb +Europe/Bratislava Europe/Madrid Africa/Ceuta Atlantic/Canary Europe/Stockholm Europe/Zurich Europe/Istanbul +Asia/Istanbul Europe/Kiev Europe/Uzhgorod Europe/Zaporozhye @@ -236,6 +301,7 @@ America/Nome America/Adak Pacific/Honolulu America/Phoenix +America/Creston America/Boise America/Indiana/Indianapolis America/Indiana/Marengo @@ -255,6 +321,7 @@ America/Halifax America/Glace_Bay America/Moncton America/Toronto +America/Nassau America/Thunder_Bay America/Nipigon America/Rainy_River @@ -299,7 +366,28 @@ America/Jamaica America/Martinique America/Managua America/Panama +America/Atikokan +America/Cayman America/Puerto_Rico +America/Anguilla +America/Antigua +America/Aruba +America/Curacao +America/Blanc-Sablon +America/Dominica +America/Grenada +America/Guadeloupe +America/Kralendijk +America/Lower_Princes +America/Marigot +America/Montserrat +America/Port_of_Spain +America/St_Barthelemy +America/St_Kitts +America/St_Lucia +America/St_Thomas +America/St_Vincent +America/Tortola America/Miquelon America/Grand_Turk America/Argentina/Buenos_Aires @@ -383,114 +471,46 @@ Etc/GMT+10 Etc/GMT+11 Etc/GMT+12 Factory -Africa/Accra -Africa/Addis_Ababa -Africa/Asmara Africa/Asmera -Africa/Bamako -Africa/Bangui -Africa/Banjul -Africa/Blantyre -Africa/Brazzaville -Africa/Bujumbura -Africa/Conakry -Africa/Dakar -Africa/Dar_es_Salaam -Africa/Djibouti -Africa/Douala -Africa/Freetown -Africa/Gaborone -Africa/Harare -Africa/Kampala -Africa/Kigali -Africa/Kinshasa -Africa/Libreville -Africa/Lome -Africa/Luanda -Africa/Lubumbashi -Africa/Lusaka -Africa/Malabo -Africa/Maseru -Africa/Mbabane -Africa/Mogadishu -Africa/Niamey -Africa/Nouakchott -Africa/Ouagadougou -Africa/Porto-Novo Africa/Timbuktu -America/Anguilla -America/Antigua America/Argentina/ComodRivadavia -America/Aruba -America/Atikokan America/Atka -America/Blanc-Sablon America/Buenos_Aires America/Catamarca -America/Cayman America/Coral_Harbour America/Cordoba -America/Creston -America/Curacao -America/Dominica America/Ensenada America/Fort_Wayne America/Godthab -America/Grenada -America/Guadeloupe America/Indianapolis America/Jujuy America/Knox_IN -America/Kralendijk America/Louisville -America/Lower_Princes -America/Marigot America/Mendoza America/Montreal -America/Montserrat -America/Nassau -America/Port_of_Spain America/Porto_Acre America/Rosario America/Santa_Isabel America/Shiprock -America/St_Barthelemy -America/St_Kitts -America/St_Lucia -America/St_Thomas -America/St_Vincent -America/Tortola America/Virgin -Antarctica/DumontDUrville -Antarctica/McMurdo Antarctica/South_Pole -Antarctica/Syowa -Arctic/Longyearbyen -Asia/Aden Asia/Ashkhabad -Asia/Bahrain Asia/Calcutta Asia/Chongqing Asia/Chungking Asia/Dacca Asia/Harbin -Asia/Istanbul Asia/Kashgar Asia/Katmandu -Asia/Kuwait Asia/Macao -Asia/Muscat -Asia/Phnom_Penh Asia/Rangoon Asia/Saigon Asia/Tel_Aviv Asia/Thimbu Asia/Ujung_Pandang Asia/Ulan_Bator -Asia/Vientiane Atlantic/Faeroe Atlantic/Jan_Mayen -Atlantic/St_Helena Australia/ACT Australia/Canberra Australia/Currie @@ -522,22 +542,7 @@ Egypt Eire Etc/UCT Europe/Belfast -Europe/Bratislava -Europe/Busingen -Europe/Guernsey -Europe/Isle_of_Man -Europe/Jersey -Europe/Ljubljana -Europe/Mariehamn -Europe/Nicosia -Europe/Podgorica -Europe/San_Marino -Europe/Sarajevo -Europe/Skopje Europe/Tiraspol -Europe/Vaduz -Europe/Vatican -Europe/Zagreb GB GB-Eire GMT+0 @@ -546,9 +551,6 @@ GMT0 Greenwich Hongkong Iceland -Indian/Antananarivo -Indian/Comoro -Indian/Mayotte Iran Israel Jamaica @@ -564,9 +566,7 @@ Navajo PRC Pacific/Enderbury Pacific/Johnston -Pacific/Midway Pacific/Ponape -Pacific/Saipan Pacific/Samoa Pacific/Truk Pacific/Yap diff --git a/lib/tzlocal/__init__.py b/lib/tzlocal/__init__.py index c8196d66..98ed04fd 100644 --- a/lib/tzlocal/__init__.py +++ b/lib/tzlocal/__init__.py @@ -1,5 +1,13 @@ import sys -if sys.platform == 'win32': - from tzlocal.win32 import get_localzone, reload_localzone + +if sys.platform == "win32": + from tzlocal.win32 import ( + get_localzone, + get_localzone_name, + reload_localzone, + ) # pragma: no cover else: - from tzlocal.unix import get_localzone, reload_localzone + from tzlocal.unix import get_localzone, get_localzone_name, reload_localzone + + +__all__ = ["get_localzone", "get_localzone_name", "reload_localzone"] diff --git a/lib/tzlocal/unix.py b/lib/tzlocal/unix.py index 8574965a..eaf96d92 100644 --- a/lib/tzlocal/unix.py +++ b/lib/tzlocal/unix.py @@ -1,97 +1,75 @@ import os -import pytz import re +import sys import warnings +from datetime import timezone +import pytz_deprecation_shim as pds from tzlocal import utils +if sys.version_info >= (3, 9): + from zoneinfo import ZoneInfo # pragma: no cover +else: + from backports.zoneinfo import ZoneInfo # pragma: no cover + _cache_tz = None +_cache_tz_name = None -def _tz_from_env(tzenv): - if tzenv[0] == ':': - tzenv = tzenv[1:] - - # TZ specifies a file - if os.path.isabs(tzenv) and os.path.exists(tzenv): - with open(tzenv, 'rb') as tzfile: - return pytz.tzfile.build_tzinfo('local', tzfile) - - # TZ specifies a zoneinfo zone. - try: - tz = pytz.timezone(tzenv) - # That worked, so we return this: - return tz - except pytz.UnknownTimeZoneError: - raise pytz.UnknownTimeZoneError( - "tzlocal() does not support non-zoneinfo timezones like %s. \n" - "Please use a timezone in the form of Continent/City") - - -def _try_tz_from_env(): - tzenv = os.environ.get('TZ') - if tzenv: - try: - return _tz_from_env(tzenv) - except pytz.UnknownTimeZoneError: - pass - - -def _get_localzone(_root='/'): +def _get_localzone_name(_root="/"): """Tries to find the local timezone configuration. - This method prefers finding the timezone name and passing that to pytz, - over passing in the localtime file, as in the later case the zoneinfo - name is unknown. + 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.""" - tzenv = _try_tz_from_env() + # First try the ENV setting. + tzenv = utils._tz_name_from_env() if tzenv: return tzenv # Are we under Termux on Android? - if os.path.exists('/system/bin/getprop'): + if os.path.exists(os.path.join(_root, "system/bin/getprop")): import subprocess - androidtz = subprocess.check_output(['getprop', 'persist.sys.timezone']).strip().decode() - return pytz.timezone(androidtz) + + androidtz = ( + subprocess.check_output(["getprop", "persist.sys.timezone"]) + .strip() + .decode() + ) + return androidtz # Now look for distribution specific configuration files # that contain the timezone name. - for configfile in ('etc/timezone', 'var/db/zoneinfo'): + + # 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) try: - with open(tzpath, 'rb') as tzfile: + with open(tzpath, "rt") as tzfile: data = tzfile.read() - # Issue #3 was that /etc/timezone was a zoneinfo file. - # That's a misconfiguration, but we need to handle it gracefully: - if data[:5] == b'TZif2': - continue - - etctz = data.strip().decode() + etctz = data.strip('/ \t\r\n') if not etctz: # Empty file, skip continue - for etctz in data.decode().splitlines(): + 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 " " in etctz: + etctz, dummy = etctz.split(" ", 1) + if "#" in etctz: + etctz, dummy = etctz.split("#", 1) if not etctz: continue - tz = pytz.timezone(etctz.replace(' ', '_')) - if _root == '/': - # We are using a file in etc to name the timezone. - # Verify that the timezone specified there is actually used: - utils.assert_tz_offset(tz) - return tz - except IOError: - # File doesn't exist or is a directory + found_configs[tzpath] = etctz.replace(" ", "_") + + except (IOError, UnicodeDecodeError): + # File doesn't exist or is a directory, or it's a binary file. continue # CentOS has a ZONE setting in /etc/sysconfig/clock, @@ -99,14 +77,14 @@ def _get_localzone(_root='/'): # Gentoo has a TIMEZONE setting in /etc/conf.d/clock # We look through these files for a timezone: - zone_re = re.compile(r'\s*ZONE\s*=\s*\"') - timezone_re = re.compile(r'\s*TIMEZONE\s*=\s*\"') - end_re = re.compile('\"') + zone_re = re.compile(r"\s*ZONE\s*=\s*\"") + timezone_re = re.compile(r"\s*TIMEZONE\s*=\s*\"") + end_re = re.compile('"') - for filename in ('etc/sysconfig/clock', 'etc/conf.d/clock'): + for filename in ("etc/sysconfig/clock", "etc/conf.d/clock"): tzpath = os.path.join(_root, filename) try: - with open(tzpath, 'rt') as tzfile: + with open(tzpath, "rt") as tzfile: data = tzfile.readlines() for line in data: @@ -118,48 +96,108 @@ def _get_localzone(_root='/'): if match is not None: # Some setting existed line = line[match.end():] - etctz = line[:end_re.search(line).start()] + etctz = line[: end_re.search(line).start()] # We found a timezone - tz = pytz.timezone(etctz.replace(' ', '_')) - if _root == '/': - # We are using a file in etc to name the timezone. - # Verify that the timezone specified there is actually used: - utils.assert_tz_offset(tz) - return tz + found_configs[tzpath] = etctz.replace(" ", "_") - except IOError: - # File doesn't exist or is a directory + except (IOError, UnicodeDecodeError): + # UnicodeDecode handles when clock is symlink to /etc/localtime continue # systemd distributions use symlinks that include the zone name, # see manpage of localtime(5) and timedatectl(1) - tzpath = os.path.join(_root, 'etc/localtime') + tzpath = os.path.join(_root, "etc/localtime") if os.path.exists(tzpath) and os.path.islink(tzpath): - tzpath = os.path.realpath(tzpath) - start = tzpath.find("/")+1 + etctz = realtzpath = os.path.realpath(tzpath) + start = etctz.find("/") + 1 while start != 0: - tzpath = tzpath[start:] + etctz = etctz[start:] try: - return pytz.timezone(tzpath) - except pytz.UnknownTimeZoneError: + pds.timezone(etctz) + tzinfo = f"{tzpath} is a symlink to" + found_configs[tzinfo] = etctz.replace(" ", "_") + except pds.UnknownTimeZoneError: pass - start = tzpath.find("/")+1 + start = etctz.find("/") + 1 - # No explicit setting existed. Use localtime - for filename in ('etc/localtime', 'usr/local/etc/localtime'): - tzpath = os.path.join(_root, filename) + if len(found_configs) > 0: + # We found some explicit config of some sort! + if len(found_configs) > 1: + # Uh-oh, multiple configs. See if they match: + unique_tzs = set() + zoneinfo = os.path.join(_root, "usr", "share", "zoneinfo") + directory_depth = len(zoneinfo.split(os.path.sep)) - if not os.path.exists(tzpath): - continue - with open(tzpath, 'rb') as tzfile: - return pytz.tzfile.build_tzinfo('local', tzfile) + 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(zoneinfo, *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 utils.ZoneInfoNotFoundError(message) + + # We found exactly one config! Use it. + return list(found_configs.values())[0] + + +def _get_localzone(_root="/"): + """Creates 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 = utils._tz_from_env() + if tzenv: + return tzenv + + tzname = _get_localzone_name(_root) + if tzname is None: + # 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 = pds.wrap_zone(ZoneInfo.from_file(tzfile, key="local")) + break + else: + warnings.warn("Can not find any timezone configuration, defaulting to UTC.") + tz = timezone.utc + else: + tz = pds.timezone(tzname) + + if _root == "/": + # We are using a file in etc to name the timezone. + # Verify that the timezone specified there is actually used: + utils.assert_tz_offset(tz) + 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 - warnings.warn('Can not find any timezone configuration, defaulting to UTC.') - return pytz.utc def get_localzone(): """Get the computers configured local timezone, if any.""" + global _cache_tz if _cache_tz is None: _cache_tz = _get_localzone() @@ -169,6 +207,9 @@ def get_localzone(): 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 diff --git a/lib/tzlocal/utils.py b/lib/tzlocal/utils.py index 5a677990..d3f92420 100644 --- a/lib/tzlocal/utils.py +++ b/lib/tzlocal/utils.py @@ -1,7 +1,24 @@ # -*- coding: utf-8 -*- +import os import time import datetime import calendar +import pytz_deprecation_shim as pds + +try: + import zoneinfo # pragma: no cover +except ImportError: + from backports import zoneinfo # pragma: no cover + +from tzlocal import windows_tz + + +class ZoneInfoNotFoundError(pds.UnknownTimeZoneError, zoneinfo.ZoneInfoNotFoundError): + """An exception derived from both pytz and zoneinfo + + This exception will be trappable both by pytz expecting clients and + zoneinfo expecting clients. + """ def get_system_offset(): @@ -21,9 +38,9 @@ def get_system_offset(): # so we check that the difference is less than one minute, because nobody # has that small DST differences. if abs(offset - time.altzone) < 60: - return -time.altzone + return -time.altzone # pragma: no cover else: - return -time.timezone + return -time.timezone # pragma: no cover def get_tz_offset(tz): @@ -39,8 +56,70 @@ def assert_tz_offset(tz): tz_offset = get_tz_offset(tz) system_offset = get_system_offset() if tz_offset != system_offset: - msg = ('Timezone offset does not match system offset: {0} != {1}. ' - 'Please, check your config files.').format( - tz_offset, system_offset - ) + msg = ( + "Timezone offset does not match system offset: {} != {}. " + "Please, check your config files." + ).format(tz_offset, system_offset) raise ValueError(msg) + + +def _tz_name_from_env(tzenv=None): + if tzenv is None: + tzenv = os.environ.get("TZ") + + if not tzenv: + return None + + if tzenv in windows_tz.tz_win: + # Yup, it's a timezone + return tzenv + + if os.path.isabs(tzenv) and os.path.exists(tzenv): + # It's a file specification + parts = tzenv.split(os.sep) + + # Is it a zone info zone? + possible_tz = "/".join(parts[-2:]) + if possible_tz in windows_tz.tz_win: + # Yup, it is + return possible_tz + + # Maybe it's a short one, like UTC? + if parts[-1] in windows_tz.tz_win: + # Indeed + return parts[-1] + + +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: + zone = zoneinfo.ZoneInfo.from_file(tzfile, key=tzname) + return pds.wrap_zone(zone) + + # TZ must specify a zoneinfo zone. + try: + tz = pds.timezone(tzenv) + # That worked, so we return this: + return tz + except pds.UnknownTimeZoneError: + # Nope, it's something like "PST4DST" etc, we can't handle that. + raise ZoneInfoNotFoundError( + "tzlocal() does not support non-zoneinfo timezones like %s. \n" + "Please use a timezone in the form of Continent/City" + ) from None diff --git a/lib/tzlocal/win32.py b/lib/tzlocal/win32.py index fcc42a23..720ab2b7 100644 --- a/lib/tzlocal/win32.py +++ b/lib/tzlocal/win32.py @@ -1,32 +1,53 @@ +from datetime import datetime +import pytz_deprecation_shim as pds + try: import _winreg as winreg except ImportError: import winreg -import pytz - from tzlocal.windows_tz import win_tz from tzlocal import utils _cache_tz = None +_cache_tz_name = None def valuestodict(key): """Convert a registry key's values to a dictionary.""" - dict = {} + result = {} size = winreg.QueryInfoKey(key)[1] for i in range(size): data = winreg.EnumValue(key, i) - dict[data[0]] = data[1] - return dict + result[data[0]] = data[1] + return result -def get_localzone_name(): +def _get_dst_info(tz): + # Find the offset for when it doesn't have DST: + dst_offset = std_offset = None + has_dst = False + year = datetime.now().year + for dt in (datetime(year, 1, 1), datetime(year, 6, 1)): + if tz.dst(dt).total_seconds() == 0.0: + # OK, no DST during winter, get this offset + std_offset = tz.utcoffset(dt).total_seconds() + else: + has_dst = True + + return has_dst, std_offset, dst_offset + + +def _get_localzone_name(): # Windows is special. It has unique time zone names (in several # meanings of the word) available, but unfortunately, they can be # translated to the language of the operating system, so we need to # do a backwards lookup, by going through all time zones and see which # one matches. + tzenv = utils._tz_name_from_env() + if tzenv: + return tzenv + handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation" @@ -34,44 +55,16 @@ def get_localzone_name(): keyvalues = valuestodict(localtz) localtz.Close() - if 'TimeZoneKeyName' in keyvalues: - # Windows 7 (and Vista?) + if "TimeZoneKeyName" in keyvalues: + # Windows 7 and later # For some reason this returns a string with loads of NUL bytes at # least on some systems. I don't know if this is a bug somewhere, I # just work around it. - tzkeyname = keyvalues['TimeZoneKeyName'].split('\x00', 1)[0] + tzkeyname = keyvalues["TimeZoneKeyName"].split("\x00", 1)[0] else: - # Windows 2000 or XP - - # This is the localized name: - tzwin = keyvalues['StandardName'] - - # Open the list of timezones to look up the real name: - TZKEYNAME = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones" - tzkey = winreg.OpenKey(handle, TZKEYNAME) - - # Now, match this value to Time Zone information - tzkeyname = None - for i in range(winreg.QueryInfoKey(tzkey)[0]): - subkey = winreg.EnumKey(tzkey, i) - sub = winreg.OpenKey(tzkey, subkey) - data = valuestodict(sub) - sub.Close() - try: - if data['Std'] == tzwin: - tzkeyname = subkey - break - except KeyError: - # This timezone didn't have proper configuration. - # Ignore it. - pass - - tzkey.Close() - handle.Close() - - if tzkeyname is None: - raise LookupError('Can not find Windows timezone configuration') + # Don't support XP any longer + raise LookupError("Can not find Windows timezone configuration") timezone = win_tz.get(tzkeyname) if timezone is None: @@ -81,24 +74,64 @@ def get_localzone_name(): # Return what we have. if timezone is None: - raise pytz.UnknownTimeZoneError('Can not find timezone ' + tzkeyname) + raise utils.ZoneInfoNotFoundError(tzkeyname) + + if keyvalues.get("DynamicDaylightTimeDisabled", 0) == 1: + # DST is disabled, so don't return the timezone name, + # instead return Etc/GMT+offset + + tz = pds.timezone(timezone) + has_dst, std_offset, dst_offset = _get_dst_info(tz) + if not has_dst: + # The DST is turned off in the windows configuration, + # but this timezone doesn't have DST so it doesn't matter + return timezone + + if std_offset is None: + raise utils.ZoneInfoNotFoundError( + f"{tzkeyname} claims to not have a non-DST time!?") + + if std_offset % 3600: + # I can't convert this to an hourly offset + raise utils.ZoneInfoNotFoundError( + f"tzlocal can't support disabling DST in the {timezone} zone.") + + # This has whole hours as offset, return it as Etc/GMT + return f"Etc/GMT{-std_offset//3600:+.0f}" return timezone +def get_localzone_name(): + """Get the zoneinfo timezone name that matches the Windows-configured timezone.""" + global _cache_tz_name + if _cache_tz_name is None: + _cache_tz_name = _get_localzone_name() + + return _cache_tz_name + + def get_localzone(): """Returns the zoneinfo-based tzinfo object that matches the Windows-configured timezone.""" + global _cache_tz if _cache_tz is None: - _cache_tz = pytz.timezone(get_localzone_name()) + _cache_tz = pds.timezone(get_localzone_name()) + + if not utils._tz_name_from_env(): + # If the timezone does NOT come from a TZ environment variable, + # verify that it's correct. If it's from the environment, + # we accept it, this is so you can run tests with different timezones. + utils.assert_tz_offset(_cache_tz) - utils.assert_tz_offset(_cache_tz) return _cache_tz def reload_localzone(): """Reload the cached localzone. You need to call this if the timezone has changed.""" global _cache_tz - _cache_tz = pytz.timezone(get_localzone_name()) + global _cache_tz_name + _cache_tz_name = _get_localzone_name() + _cache_tz = pds.timezone(_cache_tz_name) utils.assert_tz_offset(_cache_tz) return _cache_tz diff --git a/lib/tzlocal/windows_tz.py b/lib/tzlocal/windows_tz.py index 86ba807d..0d285037 100644 --- a/lib/tzlocal/windows_tz.py +++ b/lib/tzlocal/windows_tz.py @@ -104,6 +104,7 @@ win_tz = {'AUS Central Standard Time': 'Australia/Darwin', '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', @@ -118,7 +119,7 @@ win_tz = {'AUS Central Standard Time': 'Australia/Darwin', 'Turks And Caicos Standard Time': 'America/Grand_Turk', 'US Eastern Standard Time': 'America/Indianapolis', 'US Mountain Standard Time': 'America/Phoenix', - 'UTC': 'Etc/GMT', + 'UTC': 'Etc/UTC', 'UTC+12': 'Etc/GMT-12', 'UTC+13': 'Etc/GMT-13', 'UTC-02': 'Etc/GMT+2', @@ -136,7 +137,8 @@ win_tz = {'AUS Central Standard Time': 'Australia/Darwin', 'West Asia Standard Time': 'Asia/Tashkent', 'West Bank Standard Time': 'Asia/Hebron', 'West Pacific Standard Time': 'Pacific/Port_Moresby', - 'Yakutsk Standard Time': 'Asia/Yakutsk'} + 'Yakutsk Standard Time': 'Asia/Yakutsk', + 'Yukon Standard Time': 'America/Whitehorse'} # Old name for the win_tz variable: tz_names = win_tz @@ -166,7 +168,7 @@ tz_win = {'Africa/Abidjan': '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/Juba': 'South Sudan Standard Time', 'Africa/Kampala': 'E. Africa Standard Time', 'Africa/Khartoum': 'Sudan Standard Time', 'Africa/Kigali': 'South Africa Standard Time', @@ -234,8 +236,8 @@ tz_win = {'Africa/Abidjan': 'Greenwich 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/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', @@ -345,14 +347,14 @@ tz_win = {'Africa/Abidjan': 'Greenwich Standard Time', 'America/Tortola': 'SA Western Standard Time', 'America/Vancouver': 'Pacific Standard Time', 'America/Virgin': 'SA Western Standard Time', - 'America/Whitehorse': 'Pacific Standard Time', + 'America/Whitehorse': 'Yukon Standard Time', 'America/Winnipeg': 'Central Standard Time', 'America/Yakutat': 'Alaskan Standard Time', 'America/Yellowknife': 'Mountain Standard Time', - 'Antarctica/Casey': 'Singapore Standard Time', + 'Antarctica/Casey': 'Central Pacific Standard Time', 'Antarctica/Davis': 'SE Asia Standard Time', 'Antarctica/DumontDUrville': 'West Pacific Standard Time', - 'Antarctica/Macquarie': 'Central 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', @@ -501,7 +503,7 @@ tz_win = {'Africa/Abidjan': 'Greenwich Standard Time', 'Canada/Newfoundland': 'Newfoundland Standard Time', 'Canada/Pacific': 'Pacific Standard Time', 'Canada/Saskatchewan': 'Canada Central Standard Time', - 'Canada/Yukon': 'Pacific Standard Time', + 'Canada/Yukon': 'Yukon Standard Time', 'Chile/Continental': 'Pacific SA Standard Time', 'Chile/EasterIsland': 'Easter Island Standard Time', 'Cuba': 'Cuba Standard Time',