python-dateutil 4.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dateutil/__init__.py +25 -0
- dateutil/_common.py +40 -0
- dateutil/_tzdata_impl.py +75 -0
- dateutil/_version.py +24 -0
- dateutil/easter.py +88 -0
- dateutil/parser/__init__.py +63 -0
- dateutil/parser/_parser.py +1607 -0
- dateutil/parser/isoparser.py +426 -0
- dateutil/relativedelta.py +595 -0
- dateutil/rrule.py +1752 -0
- dateutil/tz/__init__.py +44 -0
- dateutil/tz/_common.py +308 -0
- dateutil/tz/_factories.py +78 -0
- dateutil/tz/_tzfile.py +1332 -0
- dateutil/tz/_tzpath.py +12 -0
- dateutil/tz/tz.py +1392 -0
- dateutil/tz/win.py +370 -0
- dateutil/tzwin.py +2 -0
- dateutil/utils.py +69 -0
- dateutil/zoneinfo/__init__.py +224 -0
- dateutil/zoneinfo/rebuild.py +42 -0
- python_dateutil-4.0.0.dist-info/METADATA +184 -0
- python_dateutil-4.0.0.dist-info/RECORD +26 -0
- python_dateutil-4.0.0.dist-info/WHEEL +4 -0
- python_dateutil-4.0.0.dist-info/licenses/AUTHORS.md +142 -0
- python_dateutil-4.0.0.dist-info/licenses/LICENSE +54 -0
dateutil/tz/win.py
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module provides an interface to the native time zone data on Windows,
|
|
3
|
+
including :py:class:`datetime.tzinfo` implementations.
|
|
4
|
+
|
|
5
|
+
Attempting to import this module on a non-Windows platform will raise an
|
|
6
|
+
:py:obj:`ImportError`.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
# This code was originally contributed by Jeffrey Harris.
|
|
10
|
+
import ctypes
|
|
11
|
+
import datetime
|
|
12
|
+
import os
|
|
13
|
+
import struct
|
|
14
|
+
import winreg
|
|
15
|
+
from ctypes import wintypes
|
|
16
|
+
|
|
17
|
+
from ._common import tzrangebase
|
|
18
|
+
|
|
19
|
+
__all__ = ["tzwin", "tzwinlocal", "tzres"]
|
|
20
|
+
|
|
21
|
+
ONEWEEK = datetime.timedelta(7)
|
|
22
|
+
|
|
23
|
+
TZKEYNAMENT = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones"
|
|
24
|
+
TZKEYNAME9X = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones"
|
|
25
|
+
TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _settzkeyname():
|
|
29
|
+
handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
|
|
30
|
+
try:
|
|
31
|
+
winreg.OpenKey(handle, TZKEYNAMENT).Close()
|
|
32
|
+
TZKEYNAME = TZKEYNAMENT
|
|
33
|
+
except OSError:
|
|
34
|
+
TZKEYNAME = TZKEYNAME9X
|
|
35
|
+
handle.Close()
|
|
36
|
+
return TZKEYNAME
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
TZKEYNAME = _settzkeyname()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class tzres:
|
|
43
|
+
"""
|
|
44
|
+
Class for accessing ``tzres.dll``, which contains timezone name related
|
|
45
|
+
resources.
|
|
46
|
+
|
|
47
|
+
.. versionadded:: 2.5.0
|
|
48
|
+
"""
|
|
49
|
+
p_wchar = ctypes.POINTER(wintypes.WCHAR) # Pointer to a wide char
|
|
50
|
+
|
|
51
|
+
def __init__(self, tzres_loc='tzres.dll'):
|
|
52
|
+
# Load the user32 DLL so we can load strings from tzres
|
|
53
|
+
user32 = ctypes.WinDLL('user32')
|
|
54
|
+
|
|
55
|
+
# Specify the LoadStringW function
|
|
56
|
+
user32.LoadStringW.argtypes = (wintypes.HINSTANCE,
|
|
57
|
+
wintypes.UINT,
|
|
58
|
+
wintypes.LPWSTR,
|
|
59
|
+
ctypes.c_int)
|
|
60
|
+
|
|
61
|
+
self.LoadStringW = user32.LoadStringW
|
|
62
|
+
self._tzres = ctypes.WinDLL(tzres_loc)
|
|
63
|
+
# PyPy's WinDLL._handle is not a native HMODULE. Keep the DLL loaded
|
|
64
|
+
# above and ask Windows for its handle instead.
|
|
65
|
+
kernel32 = ctypes.WinDLL("kernel32")
|
|
66
|
+
get_module_handle = kernel32.GetModuleHandleW
|
|
67
|
+
get_module_handle.argtypes = (wintypes.LPCWSTR,)
|
|
68
|
+
get_module_handle.restype = wintypes.HMODULE
|
|
69
|
+
self._tzres_handle = get_module_handle(os.path.normpath(tzres_loc))
|
|
70
|
+
self.tzres_loc = tzres_loc
|
|
71
|
+
|
|
72
|
+
def load_name(self, offset):
|
|
73
|
+
"""
|
|
74
|
+
Load a timezone name from a DLL offset (integer).
|
|
75
|
+
|
|
76
|
+
>>> from dateutil.tzwin import tzres
|
|
77
|
+
>>> tzr = tzres()
|
|
78
|
+
>>> print(tzr.load_name(112))
|
|
79
|
+
'Eastern Standard Time'
|
|
80
|
+
|
|
81
|
+
:param offset:
|
|
82
|
+
A positive integer value referring to a string from the tzres dll.
|
|
83
|
+
|
|
84
|
+
.. note::
|
|
85
|
+
|
|
86
|
+
Offsets found in the registry are generally of the form
|
|
87
|
+
``@tzres.dll,-114``. The offset in this case is 114, not -114.
|
|
88
|
+
|
|
89
|
+
"""
|
|
90
|
+
resource = self.p_wchar()
|
|
91
|
+
lpBuffer = ctypes.cast(ctypes.byref(resource), wintypes.LPWSTR)
|
|
92
|
+
nchar = self.LoadStringW(self._tzres_handle, offset, lpBuffer, 0)
|
|
93
|
+
return resource[:nchar]
|
|
94
|
+
|
|
95
|
+
def name_from_string(self, tzname_str):
|
|
96
|
+
"""
|
|
97
|
+
Parse strings as returned from the Windows registry into the time zone
|
|
98
|
+
name as defined in the registry.
|
|
99
|
+
|
|
100
|
+
>>> from dateutil.tzwin import tzres
|
|
101
|
+
>>> tzr = tzres()
|
|
102
|
+
>>> print(tzr.name_from_string('@tzres.dll,-251'))
|
|
103
|
+
'Dateline Daylight Time'
|
|
104
|
+
>>> print(tzr.name_from_string('Eastern Standard Time'))
|
|
105
|
+
'Eastern Standard Time'
|
|
106
|
+
|
|
107
|
+
:param tzname_str:
|
|
108
|
+
A timezone name string as returned from a Windows registry key.
|
|
109
|
+
|
|
110
|
+
:return:
|
|
111
|
+
Returns the localized timezone string from tzres.dll if the string
|
|
112
|
+
is of the form `@tzres.dll,-offset`, else returns the input string.
|
|
113
|
+
"""
|
|
114
|
+
if not tzname_str.startswith('@'):
|
|
115
|
+
return tzname_str
|
|
116
|
+
|
|
117
|
+
name_splt = tzname_str.split(',-')
|
|
118
|
+
try:
|
|
119
|
+
offset = int(name_splt[1])
|
|
120
|
+
except:
|
|
121
|
+
raise ValueError("Malformed timezone string.")
|
|
122
|
+
|
|
123
|
+
return self.load_name(offset)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class tzwinbase(tzrangebase):
|
|
127
|
+
"""tzinfo class based on win32's timezones available in the registry."""
|
|
128
|
+
def __init__(self):
|
|
129
|
+
raise NotImplementedError('tzwinbase is an abstract base class')
|
|
130
|
+
|
|
131
|
+
def __eq__(self, other):
|
|
132
|
+
# Compare on all relevant dimensions, including name.
|
|
133
|
+
if not isinstance(other, tzwinbase):
|
|
134
|
+
return NotImplemented
|
|
135
|
+
|
|
136
|
+
return (self._std_offset == other._std_offset and
|
|
137
|
+
self._dst_offset == other._dst_offset and
|
|
138
|
+
self._stddayofweek == other._stddayofweek and
|
|
139
|
+
self._dstdayofweek == other._dstdayofweek and
|
|
140
|
+
self._stdweeknumber == other._stdweeknumber and
|
|
141
|
+
self._dstweeknumber == other._dstweeknumber and
|
|
142
|
+
self._stdhour == other._stdhour and
|
|
143
|
+
self._dsthour == other._dsthour and
|
|
144
|
+
self._stdminute == other._stdminute and
|
|
145
|
+
self._dstminute == other._dstminute and
|
|
146
|
+
self._std_abbr == other._std_abbr and
|
|
147
|
+
self._dst_abbr == other._dst_abbr)
|
|
148
|
+
|
|
149
|
+
@staticmethod
|
|
150
|
+
def list():
|
|
151
|
+
"""Return a list of all time zones known to the system."""
|
|
152
|
+
with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
|
|
153
|
+
with winreg.OpenKey(handle, TZKEYNAME) as tzkey:
|
|
154
|
+
result = [winreg.EnumKey(tzkey, i)
|
|
155
|
+
for i in range(winreg.QueryInfoKey(tzkey)[0])]
|
|
156
|
+
return result
|
|
157
|
+
|
|
158
|
+
def display(self):
|
|
159
|
+
"""
|
|
160
|
+
Return the display name of the time zone.
|
|
161
|
+
"""
|
|
162
|
+
return self._display
|
|
163
|
+
|
|
164
|
+
def transitions(self, year):
|
|
165
|
+
"""
|
|
166
|
+
For a given year, get the DST on and off transition times, expressed
|
|
167
|
+
always on the standard time side. For zones with no transitions, this
|
|
168
|
+
function returns ``None``.
|
|
169
|
+
|
|
170
|
+
:param year:
|
|
171
|
+
The year whose transitions you would like to query.
|
|
172
|
+
|
|
173
|
+
:return:
|
|
174
|
+
Returns a :class:`tuple` of :class:`datetime.datetime` objects,
|
|
175
|
+
``(dston, dstoff)`` for zones with an annual DST transition, or
|
|
176
|
+
``None`` for fixed offset zones.
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
if not self.hasdst:
|
|
180
|
+
return None
|
|
181
|
+
|
|
182
|
+
dston = picknthweekday(year, self._dstmonth, self._dstdayofweek,
|
|
183
|
+
self._dsthour, self._dstminute,
|
|
184
|
+
self._dstweeknumber)
|
|
185
|
+
|
|
186
|
+
dstoff = picknthweekday(year, self._stdmonth, self._stddayofweek,
|
|
187
|
+
self._stdhour, self._stdminute,
|
|
188
|
+
self._stdweeknumber)
|
|
189
|
+
|
|
190
|
+
# Ambiguous dates default to the STD side
|
|
191
|
+
dstoff -= self._dst_base_offset
|
|
192
|
+
|
|
193
|
+
return dston, dstoff
|
|
194
|
+
|
|
195
|
+
def _get_hasdst(self):
|
|
196
|
+
return self._dstmonth != 0
|
|
197
|
+
|
|
198
|
+
@property
|
|
199
|
+
def _dst_base_offset(self):
|
|
200
|
+
return self._dst_base_offset_
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class tzwin(tzwinbase):
|
|
204
|
+
"""
|
|
205
|
+
Time zone object created from the zone info in the Windows registry
|
|
206
|
+
|
|
207
|
+
These are similar to :py:class:`dateutil.tz.tzrange` objects in that
|
|
208
|
+
the time zone data is provided in the format of a single offset rule
|
|
209
|
+
for either 0 or 2 time zone transitions per year.
|
|
210
|
+
|
|
211
|
+
:param: name
|
|
212
|
+
The name of a Windows time zone key, e.g. "Eastern Standard Time".
|
|
213
|
+
The full list of keys can be retrieved with :func:`tzwin.list`.
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
def __init__(self, name):
|
|
217
|
+
self._name = name
|
|
218
|
+
|
|
219
|
+
with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
|
|
220
|
+
tzkeyname = "{kn}\\{name}".format(kn=TZKEYNAME, name=name)
|
|
221
|
+
with winreg.OpenKey(handle, tzkeyname) as tzkey:
|
|
222
|
+
keydict = valuestodict(tzkey)
|
|
223
|
+
|
|
224
|
+
self._std_abbr = keydict["Std"]
|
|
225
|
+
self._dst_abbr = keydict["Dlt"]
|
|
226
|
+
|
|
227
|
+
self._display = keydict["Display"]
|
|
228
|
+
|
|
229
|
+
# See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm
|
|
230
|
+
tup = struct.unpack("=3l16h", keydict["TZI"])
|
|
231
|
+
stdoffset = -tup[0]-tup[1] # Bias + StandardBias * -1
|
|
232
|
+
dstoffset = stdoffset-tup[2] # + DaylightBias * -1
|
|
233
|
+
self._std_offset = datetime.timedelta(minutes=stdoffset)
|
|
234
|
+
self._dst_offset = datetime.timedelta(minutes=dstoffset)
|
|
235
|
+
|
|
236
|
+
# for the meaning see the win32 TIME_ZONE_INFORMATION structure docs
|
|
237
|
+
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms725481(v=vs.85).aspx
|
|
238
|
+
(self._stdmonth,
|
|
239
|
+
self._stddayofweek, # Sunday = 0
|
|
240
|
+
self._stdweeknumber, # Last = 5
|
|
241
|
+
self._stdhour,
|
|
242
|
+
self._stdminute) = tup[4:9]
|
|
243
|
+
|
|
244
|
+
(self._dstmonth,
|
|
245
|
+
self._dstdayofweek, # Sunday = 0
|
|
246
|
+
self._dstweeknumber, # Last = 5
|
|
247
|
+
self._dsthour,
|
|
248
|
+
self._dstminute) = tup[12:17]
|
|
249
|
+
|
|
250
|
+
self._dst_base_offset_ = self._dst_offset - self._std_offset
|
|
251
|
+
self.hasdst = self._get_hasdst()
|
|
252
|
+
|
|
253
|
+
def __repr__(self):
|
|
254
|
+
return "tzwin(%s)" % repr(self._name)
|
|
255
|
+
|
|
256
|
+
def __reduce__(self):
|
|
257
|
+
return (self.__class__, (self._name,))
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
class tzwinlocal(tzwinbase):
|
|
261
|
+
"""
|
|
262
|
+
Class representing the local time zone information in the Windows registry
|
|
263
|
+
|
|
264
|
+
While :class:`dateutil.tz.tzlocal` makes system calls (via the :mod:`time`
|
|
265
|
+
module) to retrieve time zone information, ``tzwinlocal`` retrieves the
|
|
266
|
+
rules directly from the Windows registry and creates an object like
|
|
267
|
+
:class:`dateutil.tz.tzwin`.
|
|
268
|
+
|
|
269
|
+
Because Windows does not have an equivalent of :func:`time.tzset`, on
|
|
270
|
+
Windows, :class:`dateutil.tz.tzlocal` instances will always reflect the
|
|
271
|
+
time zone settings *at the time that the process was started*, meaning
|
|
272
|
+
changes to the machine's time zone settings during the run of a program
|
|
273
|
+
on Windows will **not** be reflected by :class:`dateutil.tz.tzlocal`.
|
|
274
|
+
Because ``tzwinlocal`` reads the registry directly, it is unaffected by
|
|
275
|
+
this issue.
|
|
276
|
+
"""
|
|
277
|
+
def __init__(self):
|
|
278
|
+
with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
|
|
279
|
+
with winreg.OpenKey(handle, TZLOCALKEYNAME) as tzlocalkey:
|
|
280
|
+
keydict = valuestodict(tzlocalkey)
|
|
281
|
+
|
|
282
|
+
self._std_abbr = keydict["StandardName"]
|
|
283
|
+
self._dst_abbr = keydict["DaylightName"]
|
|
284
|
+
|
|
285
|
+
try:
|
|
286
|
+
tzkeyname = "{kn}\\{sn}".format(kn=TZKEYNAME, sn=self._std_abbr)
|
|
287
|
+
with winreg.OpenKey(handle, tzkeyname) as tzkey:
|
|
288
|
+
_keydict = valuestodict(tzkey)
|
|
289
|
+
self._display = _keydict["Display"]
|
|
290
|
+
except OSError:
|
|
291
|
+
self._display = None
|
|
292
|
+
|
|
293
|
+
stdoffset = -keydict["Bias"]-keydict["StandardBias"]
|
|
294
|
+
dstoffset = stdoffset-keydict["DaylightBias"]
|
|
295
|
+
|
|
296
|
+
self._std_offset = datetime.timedelta(minutes=stdoffset)
|
|
297
|
+
self._dst_offset = datetime.timedelta(minutes=dstoffset)
|
|
298
|
+
|
|
299
|
+
# For reasons unclear, in this particular key, the day of week has been
|
|
300
|
+
# moved to the END of the SYSTEMTIME structure.
|
|
301
|
+
tup = struct.unpack("=8h", keydict["StandardStart"])
|
|
302
|
+
|
|
303
|
+
(self._stdmonth,
|
|
304
|
+
self._stdweeknumber, # Last = 5
|
|
305
|
+
self._stdhour,
|
|
306
|
+
self._stdminute) = tup[1:5]
|
|
307
|
+
|
|
308
|
+
self._stddayofweek = tup[7]
|
|
309
|
+
|
|
310
|
+
tup = struct.unpack("=8h", keydict["DaylightStart"])
|
|
311
|
+
|
|
312
|
+
(self._dstmonth,
|
|
313
|
+
self._dstweeknumber, # Last = 5
|
|
314
|
+
self._dsthour,
|
|
315
|
+
self._dstminute) = tup[1:5]
|
|
316
|
+
|
|
317
|
+
self._dstdayofweek = tup[7]
|
|
318
|
+
|
|
319
|
+
self._dst_base_offset_ = self._dst_offset - self._std_offset
|
|
320
|
+
self.hasdst = self._get_hasdst()
|
|
321
|
+
|
|
322
|
+
def __repr__(self):
|
|
323
|
+
return "tzwinlocal()"
|
|
324
|
+
|
|
325
|
+
def __str__(self):
|
|
326
|
+
# str will return the standard name, not the daylight name.
|
|
327
|
+
return "tzwinlocal(%s)" % repr(self._std_abbr)
|
|
328
|
+
|
|
329
|
+
def __reduce__(self):
|
|
330
|
+
return (self.__class__, ())
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def picknthweekday(year, month, dayofweek, hour, minute, whichweek):
|
|
334
|
+
""" dayofweek == 0 means Sunday, whichweek 5 means last instance """
|
|
335
|
+
first = datetime.datetime(year, month, 1, hour, minute)
|
|
336
|
+
|
|
337
|
+
# This will work if dayofweek is ISO weekday (1-7) or Microsoft-style (0-6),
|
|
338
|
+
# Because 7 % 7 = 0
|
|
339
|
+
weekdayone = first.replace(day=((dayofweek - first.isoweekday()) % 7) + 1)
|
|
340
|
+
wd = weekdayone + ((whichweek - 1) * ONEWEEK)
|
|
341
|
+
if (wd.month != month):
|
|
342
|
+
wd -= ONEWEEK
|
|
343
|
+
|
|
344
|
+
return wd
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def valuestodict(key):
|
|
348
|
+
"""Convert a registry key's values to a dictionary."""
|
|
349
|
+
dout = {}
|
|
350
|
+
size = winreg.QueryInfoKey(key)[1]
|
|
351
|
+
tz_res = None
|
|
352
|
+
|
|
353
|
+
for i in range(size):
|
|
354
|
+
key_name, value, dtype = winreg.EnumValue(key, i)
|
|
355
|
+
if dtype == winreg.REG_DWORD or dtype == winreg.REG_DWORD_LITTLE_ENDIAN:
|
|
356
|
+
# If it's a DWORD (32-bit integer), it's stored as unsigned - convert
|
|
357
|
+
# that to a proper signed integer
|
|
358
|
+
if value & (1 << 31):
|
|
359
|
+
value = value - (1 << 32)
|
|
360
|
+
elif dtype == winreg.REG_SZ:
|
|
361
|
+
# If it's a reference to the tzres DLL, load the actual string
|
|
362
|
+
if value.startswith('@tzres'):
|
|
363
|
+
tz_res = tz_res or tzres()
|
|
364
|
+
value = tz_res.name_from_string(value)
|
|
365
|
+
|
|
366
|
+
value = value.rstrip('\x00') # Remove trailing nulls
|
|
367
|
+
|
|
368
|
+
dout[key_name] = value
|
|
369
|
+
|
|
370
|
+
return dout
|
dateutil/tzwin.py
ADDED
dateutil/utils.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module offers general convenience and utility functions for dealing with
|
|
3
|
+
datetimes.
|
|
4
|
+
|
|
5
|
+
.. versionadded:: 2.7.0
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from datetime import datetime, time
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def today(tzinfo=None):
|
|
12
|
+
"""
|
|
13
|
+
Returns a :py:class:`datetime` representing the current day at midnight
|
|
14
|
+
|
|
15
|
+
:param tzinfo:
|
|
16
|
+
The time zone to attach (also used to determine the current day).
|
|
17
|
+
|
|
18
|
+
:return:
|
|
19
|
+
A :py:class:`datetime.datetime` object representing the current day
|
|
20
|
+
at midnight.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
dt = datetime.now(tzinfo)
|
|
24
|
+
return datetime.combine(dt.date(), time(0, tzinfo=tzinfo))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def default_tzinfo(dt, tzinfo):
|
|
28
|
+
"""
|
|
29
|
+
Sets the ``tzinfo`` parameter on naive datetimes only
|
|
30
|
+
|
|
31
|
+
This is useful for example when you are provided a datetime that may have
|
|
32
|
+
either an implicit or explicit time zone, such as when parsing a time zone
|
|
33
|
+
string.
|
|
34
|
+
|
|
35
|
+
.. doctest::
|
|
36
|
+
|
|
37
|
+
>>> from dateutil.tz import tzoffset
|
|
38
|
+
>>> from dateutil.parser import parse
|
|
39
|
+
>>> from dateutil.utils import default_tzinfo
|
|
40
|
+
>>> dflt_tz = tzoffset("EST", -18000)
|
|
41
|
+
>>> print(default_tzinfo(parse('2014-01-01 12:30 UTC'), dflt_tz))
|
|
42
|
+
2014-01-01 12:30:00+00:00
|
|
43
|
+
>>> print(default_tzinfo(parse('2014-01-01 12:30'), dflt_tz))
|
|
44
|
+
2014-01-01 12:30:00-05:00
|
|
45
|
+
|
|
46
|
+
:param dt:
|
|
47
|
+
The datetime on which to replace the time zone
|
|
48
|
+
|
|
49
|
+
:param tzinfo:
|
|
50
|
+
The :py:class:`datetime.tzinfo` subclass instance to assign to
|
|
51
|
+
``dt`` if (and only if) it is naive.
|
|
52
|
+
|
|
53
|
+
:return:
|
|
54
|
+
Returns an aware :py:class:`datetime.datetime`.
|
|
55
|
+
"""
|
|
56
|
+
if dt.tzinfo is not None:
|
|
57
|
+
return dt
|
|
58
|
+
else:
|
|
59
|
+
return dt.replace(tzinfo=tzinfo)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def within_delta(dt1, dt2, delta):
|
|
63
|
+
"""
|
|
64
|
+
Useful for comparing two datetimes that may have a negligible difference
|
|
65
|
+
to be considered equal.
|
|
66
|
+
"""
|
|
67
|
+
delta = abs(delta)
|
|
68
|
+
difference = dt1 - dt2
|
|
69
|
+
return -delta <= difference <= delta
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The ``dateutil.zoneinfo`` module was originally designed to work with a tarball
|
|
3
|
+
shipped with ``dateutil``. Since ``dateutil`` now uses data from the same
|
|
4
|
+
sources as :py:mod:`zoneinfo`, this module is no longer necessary.
|
|
5
|
+
|
|
6
|
+
.. caution::
|
|
7
|
+
This module is deprecated, and its functionality has been dramatically
|
|
8
|
+
curtailed. It is recommended that you adopt the standard library module
|
|
9
|
+
:py:mod:`zoneinfo` or :func:`dateutil.tz.gettz` instead.
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import sys
|
|
15
|
+
import warnings
|
|
16
|
+
from tarfile import TarFile
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
from dateutil import _tzdata_impl
|
|
20
|
+
from dateutil.tz import tzfile as _tzfile
|
|
21
|
+
|
|
22
|
+
__all__ = ["get_zonefile_instance", "gettz", "gettz_db_metadata"]
|
|
23
|
+
|
|
24
|
+
ZONEFILENAME = None
|
|
25
|
+
METADATA_FN = 'METADATA'
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _importer_stacklevel():
|
|
29
|
+
"""The ``stacklevel`` that attributes a warning to the importing code.
|
|
30
|
+
|
|
31
|
+
This module can be imported through ``dateutil.__getattr__`` (e.g. by
|
|
32
|
+
``from dateutil import zoneinfo``), which calls
|
|
33
|
+
``importlib.import_module``; the warnings machinery skips the import
|
|
34
|
+
system's own frames, but not those two, so skip them here.
|
|
35
|
+
"""
|
|
36
|
+
level = 1
|
|
37
|
+
frame = sys._getframe(1) # The body of this module
|
|
38
|
+
while frame.f_back is not None:
|
|
39
|
+
frame = frame.f_back
|
|
40
|
+
filename = frame.f_code.co_filename
|
|
41
|
+
if "importlib" in filename and "_bootstrap" in filename:
|
|
42
|
+
# The warnings machinery skips these frames without counting them.
|
|
43
|
+
continue
|
|
44
|
+
|
|
45
|
+
level += 1
|
|
46
|
+
if frame.f_globals.get("__name__") not in ("importlib", "dateutil"):
|
|
47
|
+
return level
|
|
48
|
+
|
|
49
|
+
return 2
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
warnings.warn(
|
|
53
|
+
"The `dateutil.zoneinfo` module has been replaced with a wrapper around "
|
|
54
|
+
"the tzdata package, and its use is deprecated, to be removed in a future "
|
|
55
|
+
"version. Use the standard library module `zoneinfo` or `dateutil.tz` "
|
|
56
|
+
"instead.",
|
|
57
|
+
DeprecationWarning,
|
|
58
|
+
stacklevel=_importer_stacklevel(),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
tzfile = _tzfile
|
|
63
|
+
|
|
64
|
+
class ZoneInfoFile:
|
|
65
|
+
def __init__(self, zonefile_stream=None):
|
|
66
|
+
if zonefile_stream is not None:
|
|
67
|
+
self._load_legacy_zonefile_stream(zonefile_stream)
|
|
68
|
+
else:
|
|
69
|
+
self._eager_load_tzdata()
|
|
70
|
+
|
|
71
|
+
def _eager_load_tzdata(self):
|
|
72
|
+
try:
|
|
73
|
+
import tzdata
|
|
74
|
+
except ImportError as e:
|
|
75
|
+
raise ImportError(
|
|
76
|
+
"The tzdata module is required to use ZoneInfoFile; either add a dependency on tzdata or migrate away from dateutil.zoneinfo."
|
|
77
|
+
) from e
|
|
78
|
+
|
|
79
|
+
zone_dict = {}
|
|
80
|
+
|
|
81
|
+
for key in _tzdata_impl._load_tzdata_keys():
|
|
82
|
+
with _tzdata_impl._load_tzdata(key) as f:
|
|
83
|
+
zone_dict[key] = tzfile(f, key=key)
|
|
84
|
+
|
|
85
|
+
self.zones = zone_dict
|
|
86
|
+
self.metadata = {
|
|
87
|
+
"metadata_version": "3.0",
|
|
88
|
+
"tzversion": tzdata.IANA_VERSION,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
def _load_legacy_zonefile_stream(self, zonefile_stream):
|
|
92
|
+
with TarFile.open(fileobj=zonefile_stream) as tf:
|
|
93
|
+
self.zones = {
|
|
94
|
+
zf.name: tzfile(tf.extractfile(zf), filename=zf.name)
|
|
95
|
+
for zf in tf.getmembers()
|
|
96
|
+
if zf.isfile() and zf.name != METADATA_FN
|
|
97
|
+
}
|
|
98
|
+
# deal with links: They'll point to their parent object. Less
|
|
99
|
+
# waste of memory
|
|
100
|
+
links = {
|
|
101
|
+
zl.name: self.zones[zl.linkname]
|
|
102
|
+
for zl in tf.getmembers()
|
|
103
|
+
if zl.islnk() or zl.issym()
|
|
104
|
+
}
|
|
105
|
+
self.zones.update(links)
|
|
106
|
+
try:
|
|
107
|
+
metadata_json = tf.extractfile(tf.getmember(METADATA_FN))
|
|
108
|
+
metadata_str = metadata_json.read().decode("UTF-8")
|
|
109
|
+
self.metadata = json.loads(metadata_str)
|
|
110
|
+
except KeyError:
|
|
111
|
+
# no metadata in tar file
|
|
112
|
+
self.metadata = None
|
|
113
|
+
|
|
114
|
+
def get(self, name, default=None):
|
|
115
|
+
"""
|
|
116
|
+
Wrapper for :func:`ZoneInfoFile.zones.get`. This is a convenience method
|
|
117
|
+
for retrieving zones from the zone dictionary.
|
|
118
|
+
|
|
119
|
+
:param name:
|
|
120
|
+
The name of the zone to retrieve. (Generally IANA zone names)
|
|
121
|
+
|
|
122
|
+
:param default:
|
|
123
|
+
The value to return in the event of a missing key.
|
|
124
|
+
|
|
125
|
+
.. versionadded:: 2.6.0
|
|
126
|
+
|
|
127
|
+
"""
|
|
128
|
+
return self.zones.get(name, default)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
# The current API has gettz as a module function, although in fact it taps into
|
|
132
|
+
# a stateful class. So as a workaround for now, without changing the API, we
|
|
133
|
+
# will create a new "global" class instance the first time a user requests a
|
|
134
|
+
# timezone. Ugly, but adheres to the api.
|
|
135
|
+
#
|
|
136
|
+
# TODO: Remove after deprecation period.
|
|
137
|
+
_CLASS_ZONE_INSTANCE = []
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def get_zonefile_instance(new_instance=False):
|
|
141
|
+
"""
|
|
142
|
+
This is a convenience function which provides a :class:`ZoneInfoFile`
|
|
143
|
+
instance using the data provided by the ``dateutil`` package. By default, it
|
|
144
|
+
caches a single instance of the ZoneInfoFile object and returns that.
|
|
145
|
+
|
|
146
|
+
:param new_instance:
|
|
147
|
+
If ``True``, a new instance of :class:`ZoneInfoFile` is instantiated and
|
|
148
|
+
used as the cached instance for the next call. Otherwise, new instances
|
|
149
|
+
are created only as necessary.
|
|
150
|
+
|
|
151
|
+
:return:
|
|
152
|
+
Returns a :class:`ZoneInfoFile` object.
|
|
153
|
+
|
|
154
|
+
.. versionadded:: 2.6
|
|
155
|
+
"""
|
|
156
|
+
if new_instance:
|
|
157
|
+
zif = None
|
|
158
|
+
else:
|
|
159
|
+
zif = getattr(get_zonefile_instance, '_cached_instance', None)
|
|
160
|
+
|
|
161
|
+
if zif is None:
|
|
162
|
+
zif = ZoneInfoFile()
|
|
163
|
+
|
|
164
|
+
get_zonefile_instance._cached_instance = zif
|
|
165
|
+
|
|
166
|
+
return zif
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def gettz(name):
|
|
170
|
+
"""
|
|
171
|
+
This retrieves a time zone from the ``tzdata`` package.
|
|
172
|
+
|
|
173
|
+
:param name:
|
|
174
|
+
An IANA-style time zone name, as found in the zoneinfo file.
|
|
175
|
+
|
|
176
|
+
:return:
|
|
177
|
+
Returns a :class:`dateutil.tz.tzfile` time zone object.
|
|
178
|
+
|
|
179
|
+
.. warning::
|
|
180
|
+
It is generally inadvisable to use this function, and it is only
|
|
181
|
+
provided for API compatibility with earlier versions. This is *not*
|
|
182
|
+
equivalent to ``dateutil.tz.gettz()``, which selects an appropriate
|
|
183
|
+
time zone based on the inputs, favoring system zoneinfo. This is ONLY
|
|
184
|
+
for accessing the dateutil-specific zoneinfo (which may be out of
|
|
185
|
+
date compared to the system zoneinfo).
|
|
186
|
+
|
|
187
|
+
.. deprecated:: 2.6
|
|
188
|
+
If you need to use a specific zoneinfofile over the system zoneinfo,
|
|
189
|
+
instantiate a :class:`dateutil.zoneinfo.ZoneInfoFile` object and call
|
|
190
|
+
:func:`dateutil.zoneinfo.ZoneInfoFile.get(name)` instead.
|
|
191
|
+
|
|
192
|
+
Use :func:`get_zonefile_instance` to retrieve an instance of the
|
|
193
|
+
dateutil-provided zoneinfo.
|
|
194
|
+
"""
|
|
195
|
+
warnings.warn("zoneinfo.gettz() will be removed in future versions, "
|
|
196
|
+
"to use the dateutil-provided zoneinfo files, instantiate a "
|
|
197
|
+
"ZoneInfoFile object and use ZoneInfoFile.zones.get() "
|
|
198
|
+
"instead. See the documentation for details.",
|
|
199
|
+
DeprecationWarning)
|
|
200
|
+
|
|
201
|
+
if len(_CLASS_ZONE_INSTANCE) == 0:
|
|
202
|
+
_CLASS_ZONE_INSTANCE.append(ZoneInfoFile())
|
|
203
|
+
return _CLASS_ZONE_INSTANCE[0].zones.get(name)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def gettz_db_metadata():
|
|
207
|
+
"""Get the zonefile metadata
|
|
208
|
+
|
|
209
|
+
:returns:
|
|
210
|
+
A dictionary with the database metadata
|
|
211
|
+
|
|
212
|
+
.. deprecated:: 2.6
|
|
213
|
+
See deprecation warning in :func:`zoneinfo.gettz`. To get metadata,
|
|
214
|
+
query the attribute ``zoneinfo.ZoneInfoFile.metadata``.
|
|
215
|
+
"""
|
|
216
|
+
warnings.warn("zoneinfo.gettz_db_metadata() will be removed in future "
|
|
217
|
+
"versions, to use the dateutil-provided zoneinfo files, "
|
|
218
|
+
"ZoneInfoFile object and query the 'metadata' attribute "
|
|
219
|
+
"instead. See the documentation for details.",
|
|
220
|
+
DeprecationWarning)
|
|
221
|
+
|
|
222
|
+
if len(_CLASS_ZONE_INSTANCE) == 0:
|
|
223
|
+
_CLASS_ZONE_INSTANCE.append(ZoneInfoFile())
|
|
224
|
+
return _CLASS_ZONE_INSTANCE[0].metadata
|