168 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			168 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # -*- 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
 |