|
|
|
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: |
|
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} |
|
|
|
|
|
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: |
|
|
|
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) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_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 |
|
|