| # -*- coding: utf-8 -*- |
| import warnings |
| import json |
| |
| from tarfile import TarFile |
| from pkgutil import get_data |
| from io import BytesIO |
| |
| from dateutil.tz import tzfile as _tzfile |
| |
| __all__ = ["get_zonefile_instance", "gettz", "gettz_db_metadata"] |
| |
| ZONEFILENAME = "dateutil-zoneinfo.tar.gz" |
| METADATA_FN = 'METADATA' |
| |
| |
| class tzfile(_tzfile): |
| def __reduce__(self): |
| return (gettz, (self._filename,)) |
| |
| |
| def getzoneinfofile_stream(): |
| try: |
| return BytesIO(get_data(__name__, ZONEFILENAME)) |
| except IOError as e: # TODO switch to FileNotFoundError? |
| warnings.warn("I/O error({0}): {1}".format(e.errno, e.strerror)) |
| return None |
| |
| |
| class ZoneInfoFile(object): |
| def __init__(self, zonefile_stream=None): |
| if zonefile_stream is not None: |
| with TarFile.open(fileobj=zonefile_stream) as tf: |
| self.zones = {zf.name: tzfile(tf.extractfile(zf), filename=zf.name) |
| for zf in tf.getmembers() |
| if zf.isfile() and zf.name != METADATA_FN} |
| # deal with links: They'll point to their parent object. Less |
| # waste of memory |
| links = {zl.name: self.zones[zl.linkname] |
| for zl in tf.getmembers() if |
| zl.islnk() or zl.issym()} |
| self.zones.update(links) |
| try: |
| metadata_json = tf.extractfile(tf.getmember(METADATA_FN)) |
| metadata_str = metadata_json.read().decode('UTF-8') |
| self.metadata = json.loads(metadata_str) |
| except KeyError: |
| # no metadata in tar file |
| self.metadata = None |
| else: |
| self.zones = {} |
| self.metadata = None |
| |
| def get(self, name, default=None): |
| """ |
| Wrapper for :func:`ZoneInfoFile.zones.get`. This is a convenience method |
| for retrieving zones from the zone dictionary. |
| |
| :param name: |
| The name of the zone to retrieve. (Generally IANA zone names) |
| |
| :param default: |
| The value to return in the event of a missing key. |
| |
| .. versionadded:: 2.6.0 |
| |
| """ |
| return self.zones.get(name, default) |
| |
| |
| # The current API has gettz as a module function, although in fact it taps into |
| # a stateful class. So as a workaround for now, without changing the API, we |
| # will create a new "global" class instance the first time a user requests a |
| # timezone. Ugly, but adheres to the api. |
| # |
| # TODO: Remove after deprecation period. |
| _CLASS_ZONE_INSTANCE = [] |
| |
| |
| def get_zonefile_instance(new_instance=False): |
| """ |
| This is a convenience function which provides a :class:`ZoneInfoFile` |
| instance using the data provided by the ``dateutil`` package. By default, it |
| caches a single instance of the ZoneInfoFile object and returns that. |
| |
| :param new_instance: |
| If ``True``, a new instance of :class:`ZoneInfoFile` is instantiated and |
| used as the cached instance for the next call. Otherwise, new instances |
| are created only as necessary. |
| |
| :return: |
| Returns a :class:`ZoneInfoFile` object. |
| |
| .. versionadded:: 2.6 |
| """ |
| if new_instance: |
| zif = None |
| else: |
| zif = getattr(get_zonefile_instance, '_cached_instance', None) |
| |
| if zif is None: |
| zif = ZoneInfoFile(getzoneinfofile_stream()) |
| |
| get_zonefile_instance._cached_instance = zif |
| |
| return zif |
| |
| |
| def gettz(name): |
| """ |
| This retrieves a time zone from the local zoneinfo tarball that is packaged |
| with dateutil. |
| |
| :param name: |
| An IANA-style time zone name, as found in the zoneinfo file. |
| |
| :return: |
| Returns a :class:`dateutil.tz.tzfile` time zone object. |
| |
| .. warning:: |
| It is generally inadvisable to use this function, and it is only |
| provided for API compatibility with earlier versions. This is *not* |
| equivalent to ``dateutil.tz.gettz()``, which selects an appropriate |
| time zone based on the inputs, favoring system zoneinfo. This is ONLY |
| for accessing the dateutil-specific zoneinfo (which may be out of |
| date compared to the system zoneinfo). |
| |
| .. deprecated:: 2.6 |
| If you need to use a specific zoneinfofile over the system zoneinfo, |
| instantiate a :class:`dateutil.zoneinfo.ZoneInfoFile` object and call |
| :func:`dateutil.zoneinfo.ZoneInfoFile.get(name)` instead. |
| |
| Use :func:`get_zonefile_instance` to retrieve an instance of the |
| dateutil-provided zoneinfo. |
| """ |
| warnings.warn("zoneinfo.gettz() will be removed in future versions, " |
| "to use the dateutil-provided zoneinfo files, instantiate a " |
| "ZoneInfoFile object and use ZoneInfoFile.zones.get() " |
| "instead. See the documentation for details.", |
| DeprecationWarning) |
| |
| if len(_CLASS_ZONE_INSTANCE) == 0: |
| _CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream())) |
| return _CLASS_ZONE_INSTANCE[0].zones.get(name) |
| |
| |
| def gettz_db_metadata(): |
| """ Get the zonefile metadata |
| |
| See `zonefile_metadata`_ |
| |
| :returns: |
| A dictionary with the database metadata |
| |
| .. deprecated:: 2.6 |
| See deprecation warning in :func:`zoneinfo.gettz`. To get metadata, |
| query the attribute ``zoneinfo.ZoneInfoFile.metadata``. |
| """ |
| warnings.warn("zoneinfo.gettz_db_metadata() will be removed in future " |
| "versions, to use the dateutil-provided zoneinfo files, " |
| "ZoneInfoFile object and query the 'metadata' attribute " |
| "instead. See the documentation for details.", |
| DeprecationWarning) |
| |
| if len(_CLASS_ZONE_INSTANCE) == 0: |
| _CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream())) |
| return _CLASS_ZONE_INSTANCE[0].metadata |