Skip to content

Commit

Permalink
fixup! Backport zoneinfo logic into tzfile
Browse files Browse the repository at this point in the history
  • Loading branch information
pganssle committed May 20, 2021
1 parent e6532eb commit 05fb944
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 32 deletions.
8 changes: 4 additions & 4 deletions dateutil/test/test_tz.py
Original file line number Diff line number Diff line change
Expand Up @@ -2019,8 +2019,9 @@ def testFileStart1(self):

def testFileEnd1(self):
tzc = tz.tzfile(BytesIO(base64.b64decode(TZFILE_EST5EDT)))
self.assertEqual(datetime(2003, 10, 26, 0, 59, tzinfo=tzc).tzname(),
"EDT")
self.assertEqual(
datetime(2003, 10, 26, 0, 59, tzinfo=tzc).tzname(), "EDT"
)
end_est = tz.enfold(datetime(2003, 10, 26, 1, 0, tzinfo=tzc))
self.assertEqual(end_est.tzname(), "EST")

Expand All @@ -2031,8 +2032,7 @@ def testFileLastTransition(self):
"EDT")

last_date = tz.enfold(datetime(2037, 10, 25, 1, 0, tzinfo=tzc), fold=1)
self.assertEqual(last_date.tzname(),
"EST")
self.assertEqual(last_date.tzname(), "EST")

self.assertEqual(datetime(2038, 5, 25, 12, 0, tzinfo=tzc).tzname(),
"EST")
Expand Down
63 changes: 38 additions & 25 deletions dateutil/tz/_tzfile.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import bisect
import calendar
import functools
import re
import struct
import sys
import re
from datetime import datetime, timedelta

from ._common import _tzinfo, tzname_in_python2, enfold

import six

from ._common import _tzinfo, enfold, tzname_in_python2

EPOCH = datetime(1970, 1, 1)
EPOCHORDINAL = EPOCH.toordinal()

Expand All @@ -24,6 +24,7 @@
def _nullcontext(context):
yield context


# It is relatively expensive to construct new timedelta objects, and in most
# cases we're looking at the same deltas, like integer numbers of hours, etc.
# To improve speed and memory use, we'll keep a dictionary with references
Expand All @@ -37,8 +38,10 @@ def _nullcontext(context):
# of memory.
_TIMEDELTA_CACHE_SIZE = 512
if six.PY2:

def _cache_load_timedelta(f):
from collections import OrderedDict

from six.moves import _thread

CACHE_LOCK = _thread.allocate_lock()
Expand All @@ -59,9 +62,12 @@ def cached_load_timedelta(seconds):
return rv

return cached_load_timedelta


else:
_cache_load_timedelta = functools.lru_cache(maxsize=512)


@_cache_load_timedelta
def _load_timedelta(seconds):
return timedelta(seconds=seconds)
Expand Down Expand Up @@ -155,7 +161,7 @@ def __init__(self, fileobj, filename=None, key=None):
file_opened_here = False
if isinstance(fileobj, six.string_types):
self._filename = fileobj
fileobj = open(fileobj, 'rb')
fileobj = open(fileobj, "rb")
file_opened_here = True
elif filename is not None:
self._filename = filename
Expand Down Expand Up @@ -198,7 +204,9 @@ def _is_ambiguous_or_imaginary(self, dt):
tti_1 = self._find_trans(dt, fold=1, timestamp=timestamp)

# Returns 1 if ambiguous, -1 if imaginary and 0 otherwise.
return int(tti_0.utcoff > tti_1.utcoff) - int(tti_1.utcoff > tti_0.utcoff)
return int(tti_0.utcoff > tti_1.utcoff) - int(
tti_1.utcoff > tti_0.utcoff
)

def utcoffset(self, dt):
return self._find_trans(dt).utcoff
Expand Down Expand Up @@ -354,7 +362,6 @@ def _load_from_data(self, data):
utcoff = [60 * ((s + 30) // 60) for s in utcoff]
dstoff = [60 * ((s + 30) // 60) for s in dstoff]


# Convert all the transition times (UTC) into "seconds since 1970-01-01 local time"
trans_local = self._ts_to_local(trans_idx, trans_utc, utcoff)

Expand Down Expand Up @@ -519,14 +526,15 @@ def _ts_to_local(trans_idx, trans_list_utc, utcoffsets):

return trans_list_wall


def __eq__(self, other):
if not isinstance(other, tzfile):
return NotImplemented

return (self._trans_utc == other._trans_utc and
self._trans_local == other._trans_local and
self._ttinfos == other._ttinfos)
return (
self._trans_utc == other._trans_utc
and self._trans_local == other._trans_local
and self._ttinfos == other._ttinfos
)

__hash__ = None

Expand Down Expand Up @@ -572,8 +580,7 @@ def load_data(fobj):
# The data portion starts with timecnt transitions and indices
if timecnt:
trans_list_utc = struct.unpack(
">%s%s" % (timecnt, time_type),
fobj.read(timecnt * time_size)
">%s%s" % (timecnt, time_type), fobj.read(timecnt * time_size)
)
trans_idx = struct.unpack(">%sB" % timecnt, fobj.read(timecnt))
else:
Expand Down Expand Up @@ -644,13 +651,13 @@ def get_abbr(idx):

class _TZifHeader:
__slots__ = [
'version',
'isutcnt',
'isstdcnt',
'leapcnt',
'timecnt',
'typecnt',
'charcnt',
"version",
"isutcnt",
"isstdcnt",
"leapcnt",
"timecnt",
"typecnt",
"charcnt",
]

def __init__(self, *args):
Expand All @@ -661,11 +668,11 @@ def __init__(self, *args):
@classmethod
def from_file(cls, stream):
# The header starts with a 4-byte "magic" value
if stream.read(4) != b'TZif':
raise ValueError('Invalid TZif file: magic not found')
if stream.read(4) != b"TZif":
raise ValueError("Invalid TZif file: magic not found")

_version = stream.read(1)
if _version == b'\x00':
if _version == b"\x00":
version = 1
else:
version = int(_version)
Expand All @@ -674,7 +681,7 @@ def from_file(cls, stream):
args = (version,)

# Slots are defined in the order that the bytes are arranged
args = args + struct.unpack('>6l', stream.read(24))
args = args + struct.unpack(">6l", stream.read(24))

return cls(*args)

Expand All @@ -695,17 +702,22 @@ def __eq__(self, other):
)

def __repr__(self): # pragma: nocover
return (
"%s(%s, %s, %s)" % (self.__class__.__name__, self.utcoff, self.dstoff, self.tzname,)
return "%s(%s, %s, %s)" % (
self.__class__.__name__,
self.utcoff,
self.dstoff,
self.tzname,
)


_NO_TTINFO = _ttinfo(None, None, None)


def _unpack(t):
# Python 2-specific backport for x, *y = t
return t[0], t[1:]


def _parse_tz_str(tz_str):
# The tz string has the format:
#
Expand Down Expand Up @@ -840,6 +852,7 @@ def _parse_tz_delta(tz_delta):

return total


class _TZStr:
__slots__ = (
"std",
Expand Down
12 changes: 9 additions & 3 deletions dateutil/tz/tz.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,15 @@
import six
from six import string_types
from six.moves import _thread
from ._common import tzname_in_python2, _tzinfo
from ._common import tzrangebase, enfold
from ._common import _validate_fromutc_inputs

from ._common import (
_tzinfo,
_validate_fromutc_inputs,
enfold,
tzname_in_python2,
tzrangebase,
)
from ._factories import _TzOffsetFactory, _TzSingleton, _TzStrFactory
from ._tzfile import tzfile

from ._factories import _TzSingleton, _TzOffsetFactory
Expand Down

0 comments on commit 05fb944

Please sign in to comment.