aboutsummaryrefslogtreecommitdiff
path: root/dateutil/zoneinfo/__init__.py
blob: 71f6e134dbe5983588ed20c408bb778b33af0f8c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# -*- coding: utf-8 -*-
import logging
import os
import warnings
from subprocess import check_call
from tarfile import TarFile
from pkgutil import get_data
from io import BytesIO

from dateutil.tz import tzfile

__all__ = ["setcachesize", "gettz", "rebuild"]

_ZONEFILENAME = "dateutil-zoneinfo.tar.gz"

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,mode='r') as tf:
                # dict comprehension does not work on python2.6 which we still support
                # TODO: get back to the nicer syntax when we ditch python2.6
                #self.zones = {zf.name: tzfile(tf.extractfile(zf), filename = zf.name)
                #              for zf in tf.getmembers() if zf.isfile()}
                self.zones = dict((zf.name, tzfile(tf.extractfile(zf), filename = zf.name))
                                  for zf in tf.getmembers() if zf.isfile())
                # 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()}
                links = dict((zl.name, self.zones[zl.linkname])
                             for zl in tf.getmembers() if zl.islnk() or zl.issym())
                self.zones.update(links)
        else:
            self.zones = dict()


# 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: deprecate this.
_CLASS_ZONE_INSTANCE = list()
def gettz(name):
    if len(_CLASS_ZONE_INSTANCE) == 0:
        _CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream()))
    return _CLASS_ZONE_INSTANCE[0].zones.get(name)



def rebuild(filename, tag=None, format="gz",zonegroups=[]):
    """Rebuild the internal timezone info in dateutil/zoneinfo/zoneinfo*tar*

    filename is the timezone tarball from ftp.iana.org/tz.

    """
    import tempfile, shutil
    tmpdir = tempfile.mkdtemp()
    zonedir = os.path.join(tmpdir, "zoneinfo")
    moduledir = os.path.dirname(__file__)
    try:
        with TarFile.open(filename) as tf:
            # The "backwards" zone file contains links to other files, so must be
            # processed as last
            for name in zonegroups:
                tf.extract(name, tmpdir)
            filepaths = [os.path.join(tmpdir, n) for n in zonegroups]
            try:
                check_call(["zic", "-d", zonedir] + filepaths)
            except OSError as e:
                if e.errno == 2:
                    logging.error(
                        "Could not find zic. Perhaps you need to install "
                        "libc-bin or some other package that provides it, "
                        "or it's not in your PATH?")
                    raise
        target = os.path.join(moduledir, _ZONEFILENAME)
        with TarFile.open(target, "w:%s" % format) as tf:
            for entry in os.listdir(zonedir):
                entrypath = os.path.join(zonedir, entry)
                tf.add(entrypath, entry)
    finally:
        shutil.rmtree(tmpdir)