diff options
author | Stuart Bishop <stuart@stuartbishop.net> | 2009-09-28 22:28:45 +0700 |
---|---|---|
committer | Stuart Bishop <stuart@stuartbishop.net> | 2009-09-28 22:28:45 +0700 |
commit | 50a7b21c2013db03becdd0b75a2de7577321a50f (patch) | |
tree | c916f5571b93253c967bf085cfb2ea3b35f5ce8c | |
parent | 788dfc8d307413b5b34bf590511b42d01d1f9330 (diff) | |
download | pytz-50a7b21c2013db03becdd0b75a2de7577321a50f.tar.gz |
v2 format issues and fix local_to_utc zdump tests
-rw-r--r-- | gen_tests.py | 6 | ||||
-rw-r--r-- | src/pytz/tests/test_tzinfo.py | 130 | ||||
-rw-r--r-- | src/pytz/tzfile.py | 8 | ||||
-rw-r--r-- | src/pytz/tzinfo.py | 24 | ||||
-rw-r--r-- | test_zdump.py | 13 |
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) |