123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167 |
- # -*- 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
|