summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStuart Bishop <stuart@stuartbishop.net>2009-09-28 22:28:45 +0700
committerStuart Bishop <stuart@stuartbishop.net>2009-09-28 22:28:45 +0700
commit50a7b21c2013db03becdd0b75a2de7577321a50f (patch)
treec916f5571b93253c967bf085cfb2ea3b35f5ce8c
parent788dfc8d307413b5b34bf590511b42d01d1f9330 (diff)
downloadpytz-50a7b21c2013db03becdd0b75a2de7577321a50f.tar.gz
v2 format issues and fix local_to_utc zdump tests
-rw-r--r--gen_tests.py6
-rw-r--r--src/pytz/tests/test_tzinfo.py130
-rw-r--r--src/pytz/tzfile.py8
-rw-r--r--src/pytz/tzinfo.py24
-rw-r--r--test_zdump.py13
5 files changed, 153 insertions, 28 deletions
diff --git a/gen_tests.py b/gen_tests.py
index 14d8512..82140dc 100644
--- a/gen_tests.py
+++ b/gen_tests.py
@@ -23,7 +23,11 @@ def main():
print 'Collecting zdump(1) output for %s in zdump.out' % (zone,)
tname = zone.replace(
'+', '_plus_').replace('-', '_minus_').replace('/','_')
- zd_out, zd_in = popen2.popen2('%s -v -c 1800,2038 %s' % (zdump, zone))
+ # We don't yet support v2 format tzfile(5) files, so limit
+ # the daterange we test against - zdump understands v2 format
+ # files and will output historical records we can't cope with
+ # otherwise.
+ zd_out, zd_in = popen2.popen2('%s -v -c 1901,2038 %s' % (zdump, zone))
zd_in.close()
# Skip bogus output on 64bit architectures, per Bug #213816
lines = [
diff --git a/src/pytz/tests/test_tzinfo.py b/src/pytz/tests/test_tzinfo.py
index d79b26f..313a2e2 100644
--- a/src/pytz/tests/test_tzinfo.py
+++ b/src/pytz/tests/test_tzinfo.py
@@ -26,6 +26,22 @@ NOTIME = timedelta(0)
UTC = pytz.timezone('UTC')
GMT = pytz.timezone('GMT')
+
+def prettydt(dt):
+ """datetime as a string using a known format.
+
+ We don't use strftime as it doesn't handle years earlier than 1900
+ per http://bugs.python.org/issue1777412
+ """
+ if dt.utcoffset() >= timedelta(0):
+ offset = '+%s' % (dt.utcoffset(),)
+ else:
+ offset = '-%s' % (-1 * dt.utcoffset(),)
+ return '%04d-%02d-%02d %02d:%02d:%02d %s %s' % (
+ dt.year, dt.month, dt.day,
+ dt.hour, dt.minute, dt.second,
+ dt.tzname(), offset)
+
class BasicTest(unittest.TestCase):
def testVersion(self):
@@ -194,13 +210,15 @@ class USEasternDSTStartTestCase(unittest.TestCase):
# Make sure arithmetic crossing DST boundaries ends
# up in the correct timezone after normalization
+ utc_plus_delta = (utc_dt + delta).astimezone(self.tzinfo)
+ local_plus_delta = self.tzinfo.normalize(dt + delta)
self.failUnlessEqual(
- (utc_dt + delta).astimezone(self.tzinfo).strftime(fmt),
- self.tzinfo.normalize(dt + delta).strftime(fmt),
+ prettydt(utc_plus_delta),
+ prettydt(local_plus_delta),
'Incorrect result for delta==%d days. Wanted %r. Got %r'%(
days,
- (utc_dt + delta).astimezone(self.tzinfo).strftime(fmt),
- self.tzinfo.normalize(dt + delta).strftime(fmt),
+ prettydt(utc_plus_delta),
+ prettydt(local_plus_delta),
)
)
@@ -340,7 +358,6 @@ class VilniusCESTStartTestCase(USEasternDSTStartTestCase):
# causing the clocks to go backwards for this summer time
# switchover.
tzinfo = pytz.timezone('Europe/Vilnius')
- instant = timedelta(seconds=31)
transition_time = datetime(1941, 6, 23, 21, 00, 00, tzinfo=UTC)
before = {
'tzname': 'MSK',
@@ -354,6 +371,109 @@ class VilniusCESTStartTestCase(USEasternDSTStartTestCase):
}
+class LondonHistoryStartTestCase(USEasternDSTStartTestCase):
+ # The first known timezone transition in London was in 1847 when
+ # clocks where synchronized to GMT. However, we currently only
+ # understand v1 format tzfile(5) files which does handle years
+ # this far in the past, so our earliest known transition is in
+ # 1916.
+ tzinfo = pytz.timezone('Europe/London')
+ # transition_time = datetime(1847, 12, 1, 1, 15, 00, tzinfo=UTC)
+ # before = {
+ # 'tzname': 'LMT',
+ # 'utcoffset': timedelta(minutes=-75),
+ # 'dst': timedelta(0),
+ # }
+ # after = {
+ # 'tzname': 'GMT',
+ # 'utcoffset': timedelta(0),
+ # 'dst': timedelta(0),
+ # }
+ transition_time = datetime(1916, 5, 21, 2, 00, 00, tzinfo=UTC)
+ before = {
+ 'tzname': 'GMT',
+ 'utcoffset': timedelta(0),
+ 'dst': timedelta(0),
+ }
+ after = {
+ 'tzname': 'BST',
+ 'utcoffset': timedelta(hours=1),
+ 'dst': timedelta(hours=1),
+ }
+
+
+class LondonHistoryEndTestCase(USEasternDSTStartTestCase):
+ # Timezone switchovers are projected into the future, even
+ # though no official statements exist or could be believed even
+ # if they did exist. We currently only check the last known
+ # transition in 2037, as we are still using v1 format tzfile(5)
+ # files.
+ tzinfo = pytz.timezone('Europe/London')
+ # transition_time = datetime(2499, 10, 25, 1, 0, 0, tzinfo=UTC)
+ transition_time = datetime(2037, 10, 25, 1, 0, 0, tzinfo=UTC)
+ before = {
+ 'tzname': 'BST',
+ 'utcoffset': timedelta(hours=1),
+ 'dst': timedelta(hours=1),
+ }
+ after = {
+ 'tzname': 'GMT',
+ 'utcoffset': timedelta(0),
+ 'dst': timedelta(0),
+ }
+
+
+class NoumeaHistoryStartTestCase(USEasternDSTStartTestCase):
+ # Noumea adopted a whole hour offset in 1912. Previously
+ # it was 11 hours, 5 minutes and 48 seconds off UTC. However,
+ # due to limitations of the Python datetime library, we need
+ # to round that to 11 hours 6 minutes.
+ tzinfo = pytz.timezone('Pacific/Noumea')
+ transition_time = datetime(1912, 1, 12, 12, 54, 12, tzinfo=UTC)
+ before = {
+ 'tzname': 'LMT',
+ 'utcoffset': timedelta(hours=11, minutes=6),
+ 'dst': timedelta(0),
+ }
+ after = {
+ 'tzname': 'NCT',
+ 'utcoffset': timedelta(hours=11),
+ 'dst': timedelta(0),
+ }
+
+
+class NoumeaDSTEndTestCase(USEasternDSTStartTestCase):
+ # Noumea dropped DST in 1997.
+ tzinfo = pytz.timezone('Pacific/Noumea')
+ transition_time = datetime(1997, 3, 1, 15, 00, 00, tzinfo=UTC)
+ before = {
+ 'tzname': 'NCST',
+ 'utcoffset': timedelta(hours=12),
+ 'dst': timedelta(hours=1),
+ }
+ after = {
+ 'tzname': 'NCT',
+ 'utcoffset': timedelta(hours=11),
+ 'dst': timedelta(0),
+ }
+
+
+class NoumeaNoMoreDSTTestCase(NoumeaDSTEndTestCase):
+ # Noumea dropped DST in 1997. Here we test that it stops occuring.
+ tzinfo = pytz.timezone('Pacific/Noumea')
+ transition_time = datetime(2037, 3, 1, 15, 00, 00, tzinfo=UTC)
+ before = {
+ 'tzname': 'NCT',
+ 'utcoffset': timedelta(hours=11),
+ 'dst': timedelta(0),
+ }
+ after = {
+ 'tzname': 'NCT',
+ 'utcoffset': timedelta(hours=11),
+ 'dst': timedelta(0),
+ }
+
+
class ReferenceUSEasternDSTStartTestCase(USEasternDSTStartTestCase):
tzinfo = reference.Eastern
def test_arithmetic(self):
diff --git a/src/pytz/tzfile.py b/src/pytz/tzfile.py
index b5f818f..ea8032a 100644
--- a/src/pytz/tzfile.py
+++ b/src/pytz/tzfile.py
@@ -12,12 +12,12 @@ from pytz.tzinfo import memorized_datetime, memorized_timedelta
def build_tzinfo(zone, fp):
- head_fmt = '>4s 16x 6l'
+ head_fmt = '>4s c 15x 6l'
head_size = calcsize(head_fmt)
- (magic,ttisgmtcnt,ttisstdcnt,leapcnt,
- timecnt,typecnt,charcnt) = unpack(head_fmt, fp.read(head_size))
+ (magic, format, ttisgmtcnt, ttisstdcnt,leapcnt, timecnt,
+ typecnt, charcnt) = unpack(head_fmt, fp.read(head_size))
- # Make sure it is a tzinfo(5) file
+ # Make sure it is a tzfile(5) file
assert magic == 'TZif'
# Read out the transition times, localtime indices and ttinfo structures.
diff --git a/src/pytz/tzinfo.py b/src/pytz/tzinfo.py
index 55afcb2..ed035a3 100644
--- a/src/pytz/tzinfo.py
+++ b/src/pytz/tzinfo.py
@@ -68,13 +68,13 @@ class BaseTzInfo(tzinfo):
class StaticTzInfo(BaseTzInfo):
'''A timezone that has a constant offset from UTC
- These timezones are rare, as most regions have changed their
- offset from UTC at some point in their history
+ These timezones are rare, as most locations have changed their
+ offset at some point in their history
'''
def fromutc(self, dt):
'''See datetime.tzinfo.fromutc'''
return (dt + self._utcoffset).replace(tzinfo=self)
-
+
def utcoffset(self,dt):
'''See datetime.tzinfo.utcoffset'''
return self._utcoffset
@@ -110,11 +110,10 @@ class StaticTzInfo(BaseTzInfo):
class DstTzInfo(BaseTzInfo):
'''A timezone that has a variable offset from UTC
-
- The offset might change if daylight savings time comes into effect,
- or at a point in history when the region decides to change their
- timezone definition.
+ The offset might change if daylight savings time comes into effect,
+ or at a point in history when the region decides to change their
+ timezone definition.
'''
# Overridden in subclass
_utc_transition_times = None # Sorted list of DST transition times in UTC
@@ -180,7 +179,6 @@ class DstTzInfo(BaseTzInfo):
>>> before = eastern.normalize(before)
>>> before.strftime(fmt)
'2002-10-27 01:50:00 EDT (-0400)'
-
'''
if dt.tzinfo is None:
raise ValueError, 'Naive time - no tzinfo set'
@@ -194,13 +192,13 @@ class DstTzInfo(BaseTzInfo):
def localize(self, dt, is_dst=False):
'''Convert naive time to local time.
-
+
This method should be used to construct localtimes, rather
than passing a tzinfo argument to a datetime constructor.
is_dst is used to determine the correct timezone in the ambigous
period at the end of daylight savings time.
-
+
>>> from pytz import timezone
>>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'
>>> amdam = timezone('Europe/Amsterdam')
@@ -223,7 +221,7 @@ class DstTzInfo(BaseTzInfo):
AmbiguousTimeError: 2004-10-31 02:00:00
is_dst defaults to False
-
+
>>> amdam.localize(dt) == amdam.localize(dt, False)
True
@@ -324,7 +322,7 @@ class DstTzInfo(BaseTzInfo):
)
filtered_possible_loc_dt.sort(mycmp)
return filtered_possible_loc_dt[0]
-
+
def utcoffset(self, dt):
'''See datetime.tzinfo.utcoffset'''
return self._utcoffset
@@ -388,7 +386,7 @@ class NonExistentTimeError(InvalidTimeError):
def unpickler(zone, utcoffset=None, dstoffset=None, tzname=None):
"""Factory function for unpickling pytz tzinfo instances.
-
+
This is shared for both StaticTzInfo and DstTzInfo instances, because
database changes could cause a zones implementation to switch between
these two base classes and we can't break pickles on a pytz version
diff --git a/test_zdump.py b/test_zdump.py
index 6e6299c..9e5b440 100644
--- a/test_zdump.py
+++ b/test_zdump.py
@@ -19,7 +19,6 @@ class ZdumpTestCase(unittest.TestCase):
loc_dt.replace(tzinfo=None))
def local_to_utc_check(self, zone, utc_dt, loc_dt, loc_tzname, is_dst):
- loc_tz = pytz.timezone(zone)
self.failUnlessEqual(
loc_dt.astimezone(pytz.utc).replace(tzinfo=None),
utc_dt.replace(tzinfo=None))
@@ -43,6 +42,8 @@ def test_suite():
else:
raise RuntimeError, 'Dud line %r' % (line,)
+ is_dst = bool(int(is_dst))
+
if zone != last_zone:
classname = zone.replace(
'+', '_plus_').replace('-', '_minus_').replace('/','_')
@@ -88,19 +89,21 @@ def test_suite():
is_dst=is_dst):
self.utc_to_local_check(zone, utc_dt, loc_dt, tzname, is_dst)
test_utc_to_local.__name__ = test_name
- #test_utc_to_local.__doc__ = line
setattr(test_class, test_name, test_utc_to_local)
if not skip_local:
test_name = 'test_local_to_utc_%04d_%02d_%02d_%02d_%02d_%02d' % (
- utc_dt.year, utc_dt.month, utc_dt.day,
- utc_dt.hour, utc_dt.minute, utc_dt.second)
+ loc_dt.year, loc_dt.month, loc_dt.day,
+ loc_dt.hour, loc_dt.minute, loc_dt.second)
+ if is_dst:
+ test_name += '_dst'
+ else:
+ test_name += '_nodst'
def test_local_to_utc(
self, zone=zone, utc_dt=utc_dt, loc_dt=loc_dt, tzname=tzname,
is_dst=is_dst):
self.local_to_utc_check(zone, utc_dt, loc_dt, tzname, is_dst)
test_local_to_utc.__name__ = test_name
- #test_local_to_utc.__doc__ = line
setattr(test_class, test_name, test_local_to_utc)