File size: 4,835 Bytes
f5fb003
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
"""This is an implementation of functions that interact with the tzdata module.

It will only live as long as :mod:`dateutil.zoneinfo` lives; when the
``dateutil.zoneinfo`` module is removed, this can all move under :mod:`tz`.
"""

import contextlib
import importlib
import io
import os
import pkgutil
import sys

import six

if six.PY2:
    _TZDATA_LOAD_EXCEPTIONS = (
        ImportError,
        IOError,
        UnicodeEncodeError,
        ValueError,
    )
else:
    _TZDATA_LOAD_EXCEPTIONS = (
        ImportError,
        FileNotFoundError,
        UnicodeEncodeError,
        IsADirectoryError,
        ValueError,
    )

if sys.version_info < (3, 8):
    _nullcontext = getattr(contextlib, "nullcontext", None)
    if _nullcontext is None:

        @contextlib.contextmanager
        def _nullcontext(v):
            yield v

    def _get_data(package, resource):
        # pkgutil.get_data returns None rather than raising when the package
        # cannot be found (and on Python 2 raises AttributeError if the
        # package has been blocked by setting sys.modules[name] = None), so
        # import the package first to get the ImportError the callers expect
        # from the importlib.resources versions of these functions.
        importlib.import_module(package)

        pkg_data = pkgutil.get_data(package, resource)
        if pkg_data is None:
            raise ImportError("No package named %r" % package)

        return pkg_data

    def _open_text(package, resource):
        str_package_data = _get_data(package, resource).decode("utf-8")

        return _nullcontext(io.StringIO(str_package_data))

    def _open_binary(package, resource):
        return io.BytesIO(_get_data(package, resource))

else:
    import importlib.resources

    if sys.version_info >= (3, 9):

        def _open_text(package, resource):
            return (
                importlib.resources.files(package)
                .joinpath(resource)
                .open("r", encoding="utf-8")
            )

        def _open_binary(package, resource):
            return (
                importlib.resources.files(package).joinpath(resource).open("rb")
            )

    else:
        _open_text = importlib.resources.open_text
        _open_binary = importlib.resources.open_binary


def _is_package_directory(package, resource):
    """Whether ``resource`` names a directory inside ``package``.

    Opening a directory raises IsADirectoryError on POSIX but PermissionError
    on Windows, and on Python < 3.9 there is no portable way to ask the
    resource API, so check the package's search path directly (the tzdata
    package is a regular directory-backed package).
    """
    try:
        pkg = importlib.import_module(package)
    except ImportError:
        return False

    for base in getattr(pkg, "__path__", ()):
        if os.path.isdir(os.path.join(base, resource)):
            return True

    return False


def _load_tzdata(key):
    components = key.split("/")
    package_name = ".".join(["tzdata.zoneinfo"] + components[:-1])
    resource_name = components[-1]

    # Empty or relative path components cannot name a zone, and they produce
    # module names that the import machinery handles inconsistently across
    # versions, so treat them as not found up front. A key naming a directory
    # (a subpackage) is not a zone either, and opening it raises a
    # platform-dependent error, see CPython gh-85702.
    if any(c in ("", ".", "..") for c in components) or _is_package_directory(
        package_name, resource_name
    ):
        raise TZFileNotFound("Time zone not found: %s", zone_key=key)

    try:
        return _open_binary(package_name, resource_name)
    except _TZDATA_LOAD_EXCEPTIONS:
        # ImportError: package does not exist (or tzdata is not installed)
        # FileNotFoundError: the key is not a file in the package
        # UnicodeEncodeError: the key is not encodable as a filename
        # IsADirectoryError: the key names a package rather than a resource
        # ValueError: the key is not a valid path (e.g. contains a NUL byte)
        six.raise_from(
            TZFileNotFound("Time zone not found: %s", zone_key=key), None
        )


def _load_tzdata_keys():
    with _open_text("tzdata", "zones") as f:
        return list(filter(None, (l.strip() for l in f)))


class TZFileNotFound(ValueError):
    """Indicates that a time zone file isn't present in tzdata."""

    def __init__(self, msg, *args, **kwargs):
        # TODO: Use keyword-only argument when this is Python 3-only
        zone_key = kwargs.pop("zone_key", None)
        if zone_key is None:  # pragma: nocover
            raise TypeError("Required keyword argument missing: zone_key")

        super(TZFileNotFound, self).__init__(msg % zone_key, *args)
        self.zone_key = zone_key