From bd8a78ccad78890ebf50b35158e87198943812ee Mon Sep 17 00:00:00 2001 From: Stuart Bishop Date: Tue, 9 Mar 2010 18:00:04 +0700 Subject: Support Python 3.1, work in progress --- Makefile | 15 ++++++-- src/README.txt | 5 ++- src/pytz/__init__.py | 87 +++++++++++++++++++++++++++++++------------ src/pytz/tests/test_tzinfo.py | 17 ++++++--- src/pytz/tzfile.py | 21 +++++++++-- src/pytz/tzinfo.py | 10 ++--- src/setup.py | 4 +- 7 files changed, 113 insertions(+), 46 deletions(-) diff --git a/Makefile b/Makefile index 093c42a..9da8498 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ PYTHON23=python2.3 PYTHON24=python2.4 PYTHON25=python2.5 PYTHON26=python2.6 +PYTHON31=python3.1 PYTHON=${PYTHON25} OLSON=./elsie.nci.nih.gov TESTARGS=-vv @@ -27,7 +28,8 @@ dist: build/dist/locales/pytz.pot .stamp-dist ${PYTHON23} setup.py bdist_egg --dist-dir=../tarballs && \ ${PYTHON24} setup.py bdist_egg --dist-dir=../tarballs && \ ${PYTHON25} setup.py bdist_egg --dist-dir=../tarballs && \ - ${PYTHON26} setup.py bdist_egg --dist-dir=../tarballs + ${PYTHON26} setup.py bdist_egg --dist-dir=../tarballs && \ + ${PYTHON31} setup.py bdist_egg --dist-dir=../tarballs touch $@ upload: dist build/dist/locales/pytz.pot .stamp-upload @@ -43,6 +45,8 @@ upload: dist build/dist/locales/pytz.pot .stamp-upload ${PYTHON25} setup.py register bdist_egg --dist-dir=../tarballs \ upload --sign && \ ${PYTHON26} setup.py register bdist_egg --dist-dir=../tarballs \ + upload --sign && \ + ${PYTHON31} setup.py register bdist_egg --dist-dir=../tarballs \ upload --sign touch $@ @@ -59,18 +63,21 @@ test_tzinfo: .stamp-tzinfo && ${PYTHON23} test_tzinfo.py ${TESTARGS} \ && ${PYTHON24} test_tzinfo.py ${TESTARGS} \ && ${PYTHON25} test_tzinfo.py ${TESTARGS} \ - && ${PYTHON26} test_tzinfo.py ${TESTARGS} + && ${PYTHON26} test_tzinfo.py ${TESTARGS} \ + && ${PYTHON31} test_tzinfo.py ${TESTARGS} test_docs: .stamp-tzinfo cd build/dist/pytz/tests \ && ${PYTHON23} test_docs.py ${TESTARGS} \ && ${PYTHON24} test_docs.py ${TESTARGS} \ && ${PYTHON25} test_docs.py ${TESTARGS} \ - && ${PYTHON26} test_docs.py ${TESTARGS} + && ${PYTHON26} test_docs.py ${TESTARGS} \ + && ${PYTHON31} test_docs.py ${TESTARGS} test_zdump: dist ${PYTHON25} gen_tests.py ${TARGET} && \ - ${PYTHON25} test_zdump.py ${TESTARGS} + ${PYTHON25} test_zdump.py ${TESTARGS} && \ + ${PYTHON31} test_zdump.py ${TESTARGS} build/dist/test_zdump.py: .stamp-zoneinfo diff --git a/src/README.txt b/src/README.txt index 312e0a1..87e20f7 100644 --- a/src/README.txt +++ b/src/README.txt @@ -72,7 +72,7 @@ This is used to localize a naive datetime (datetime with no timezone information): >>> loc_dt = eastern.localize(datetime(2002, 10, 27, 6, 0, 0)) ->>> print loc_dt.strftime(fmt) +>>> print(loc_dt.strftime(fmt)) 2002-10-27 06:00:00 EST-0500 The second way of building a localized time is by converting an existing @@ -295,7 +295,7 @@ facility for constructing them unambiguously: >>> loc_dt = datetime(2002, 10, 27, 1, 30, 00) >>> est_dt = eastern.localize(loc_dt, is_dst=True) >>> edt_dt = eastern.localize(loc_dt, is_dst=False) ->>> print est_dt.strftime(fmt), '/', edt_dt.strftime(fmt) +>>> print(est_dt.strftime(fmt) + ' / ' + edt_dt.strftime(fmt)) 2002-10-27 01:30:00 EDT-0400 / 2002-10-27 01:30:00 EST-0500 If you pass None as the is_dst flag to localize(), pytz will refuse to @@ -541,3 +541,4 @@ Contact Stuart Bishop + diff --git a/src/pytz/__init__.py b/src/pytz/__init__.py index e825cc5..0d2353a 100644 --- a/src/pytz/__init__.py +++ b/src/pytz/__init__.py @@ -26,16 +26,21 @@ __all__ = [ ] import sys, datetime, os.path, gettext -from UserDict import DictMixin +try: + from UserDict import DictMixin +except ImportError: + from collections import Mapping as DictMixin try: from pkg_resources import resource_stream except ImportError: resource_stream = None -from tzinfo import AmbiguousTimeError, InvalidTimeError, NonExistentTimeError -from tzinfo import unpickler -from tzfile import build_tzinfo +from pytz.tzinfo import AmbiguousTimeError +from pytz.tzinfo import InvalidTimeError +from pytz.tzinfo import NonExistentTimeError +from pytz.tzinfo import unpickler +from pytz.tzfile import build_tzinfo, _byte_string # Use 2.3 sets module implementation if set builtin is not available try: @@ -43,6 +48,16 @@ try: except NameError: from sets import Set as set +try: + unicode +except NameError: + # Python 3.x doesn't have unicode(), making writing code + # for Python 2.3 and Python 3.x a pain. + def unicode(s): + try: + return s.decode('US-ASCII') + except AttributeError: + return str(s) def open_resource(name): """Open a resource from the zoneinfo subdir for reading. @@ -114,7 +129,7 @@ def timezone(zone): >>> eastern = timezone('US/Eastern') >>> eastern.zone 'US/Eastern' - >>> timezone(u'US/Eastern') is eastern + >>> timezone(unicode('US/Eastern')) is eastern True >>> utc_dt = datetime(2002, 10, 27, 6, 0, 0, tzinfo=utc) >>> loc_dt = utc_dt.astimezone(eastern) @@ -144,7 +159,7 @@ def timezone(zone): return utc try: - zone = zone.encode('US-ASCII') + zone.encode('US-ASCII') except UnicodeEncodeError: # All valid timezones are ASCII raise UnknownTimeZoneError(zone) @@ -195,13 +210,13 @@ class UTC(datetime.tzinfo): def localize(self, dt, is_dst=False): '''Convert naive time to local time''' if dt.tzinfo is not None: - raise ValueError, 'Not naive datetime (tzinfo is already set)' + raise ValueError('Not naive datetime (tzinfo is already set)') return dt.replace(tzinfo=self) def normalize(self, dt, is_dst=False): '''Correct the timezone information on the given datetime''' if dt.tzinfo is None: - raise ValueError, 'Naive time - no tzinfo set' + raise ValueError('Naive time - no tzinfo set') return dt.replace(tzinfo=self) def __repr__(self): @@ -229,8 +244,8 @@ def _UTC(): >>> naive = dt.replace(tzinfo=None) >>> p = pickle.dumps(dt, 1) >>> naive_p = pickle.dumps(naive, 1) - >>> len(p), len(naive_p), len(p) - len(naive_p) - (60, 43, 17) + >>> len(p) - len(naive_p) + 17 >>> new = pickle.loads(p) >>> new == dt True @@ -265,6 +280,21 @@ class _LazyDict(DictMixin): self._fill() return self.data[key.upper()] + def __contains__(self, key): + if self.data is None: + self._fill() + return key in self.data + + def __iter__(self): + if self.data is None: + self._fill() + return iter(self.data) + + def __len__(self): + if self.data is None: + self._fill() + return len(self.data) + def keys(self): if self.data is None: self._fill() @@ -277,13 +307,21 @@ class _CountryTimezoneDict(_LazyDict): iso3166_code is the two letter code used to identify the country. - >>> country_timezones['ch'] - ['Europe/Zurich'] - >>> country_timezones['CH'] - ['Europe/Zurich'] - >>> country_timezones[u'ch'] - ['Europe/Zurich'] - >>> country_timezones['XXX'] + >>> def print_list(list_of_strings): + ... 'We use a helper so doctests work under Python 2.3 -> 3.x' + ... for s in list_of_strings: + ... print(s) + + >>> print_list(country_timezones['nz']) + Pacific/Auckland + Pacific/Chatham + >>> print_list(country_timezones['ch']) + Europe/Zurich + >>> print_list(country_timezones['CH']) + Europe/Zurich + >>> print_list(country_timezones[unicode('ch')]) + Europe/Zurich + >>> print_list(country_timezones['XXX']) Traceback (most recent call last): ... KeyError: 'XXX' @@ -291,8 +329,9 @@ class _CountryTimezoneDict(_LazyDict): Previously, this information was exposed as a function rather than a dictionary. This is still supported:: - >>> country_timezones('nz') - ['Pacific/Auckland', 'Pacific/Chatham'] + >>> print_list(country_timezones('nz')) + Pacific/Auckland + Pacific/Chatham """ def __call__(self, iso3166_code): """Backwards compatibility.""" @@ -302,6 +341,7 @@ class _CountryTimezoneDict(_LazyDict): data = {} zone_tab = open_resource('zone.tab') for line in zone_tab: + line = line.decode('US-ASCII') if line.startswith('#'): continue code, coordinates, zone = line.split(None, 4)[:3] @@ -319,13 +359,14 @@ country_timezones = _CountryTimezoneDict() class _CountryNameDict(_LazyDict): '''Dictionary proving ISO3166 code -> English name. - >>> country_names['au'] - 'Australia' + >>> print(country_names['au']) + Australia ''' def _fill(self): data = {} zone_tab = open_resource('iso3166.tab') for line in zone_tab.readlines(): + line = line.decode('US-ASCII') if line.startswith('#'): continue code, name = line.split(None, 1) @@ -365,13 +406,13 @@ class _FixedOffset(datetime.tzinfo): def localize(self, dt, is_dst=False): '''Convert naive time to local time''' if dt.tzinfo is not None: - raise ValueError, 'Not naive datetime (tzinfo is already set)' + raise ValueError('Not naive datetime (tzinfo is already set)') return dt.replace(tzinfo=self) def normalize(self, dt, is_dst=False): '''Correct the timezone information on the given datetime''' if dt.tzinfo is None: - raise ValueError, 'Naive time - no tzinfo set' + raise ValueError('Naive time - no tzinfo set') return dt.replace(tzinfo=self) diff --git a/src/pytz/tests/test_tzinfo.py b/src/pytz/tests/test_tzinfo.py index ad49473..7bbccbf 100644 --- a/src/pytz/tests/test_tzinfo.py +++ b/src/pytz/tests/test_tzinfo.py @@ -2,7 +2,10 @@ import sys, os, os.path import unittest, doctest -import cPickle as pickle +try: + import cPickle as pickle +except ImportError: + import pickle from datetime import datetime, tzinfo, timedelta if __name__ == '__main__': @@ -12,6 +15,7 @@ if __name__ == '__main__': import pytz from pytz import reference +from pytz.tzfile import _byte_string # I test for expected version to ensure the correct version of pytz is # actually being tested. @@ -26,7 +30,6 @@ NOTIME = timedelta(0) UTC = pytz.timezone('UTC') GMT = pytz.timezone('GMT') - def prettydt(dt): """datetime as a string using a known format. @@ -104,7 +107,7 @@ class PicklingTest(unittest.TestCase): tz = pytz.timezone('Australia/Melbourne') p = pickle.dumps(tz) tzname = tz._tzname - hacked_p = p.replace(tzname, '???') + hacked_p = p.replace(_byte_string(tzname), _byte_string('???')) self.failIfEqual(p, hacked_p) unpickled_tz = pickle.loads(hacked_p) self.failUnless(tz is unpickled_tz) @@ -113,7 +116,9 @@ class PicklingTest(unittest.TestCase): # data will continue to be used. p = pickle.dumps(tz) new_utcoffset = tz._utcoffset.seconds + 42 - hacked_p = p.replace(str(tz._utcoffset.seconds), str(new_utcoffset)) + hacked_p = p.replace( + _byte_string(str(tz._utcoffset.seconds)), + _byte_string(str(new_utcoffset))) self.failIfEqual(p, hacked_p) unpickled_tz = pickle.loads(hacked_p) self.failUnlessEqual(unpickled_tz._utcoffset.seconds, new_utcoffset) @@ -123,10 +128,10 @@ class PicklingTest(unittest.TestCase): # Ensure that applications serializing pytz instances as pickles # have no troubles upgrading to a new pytz release. These pickles # where created with pytz2006j - east1 = pickle.loads( + east1 = pickle.loads(_byte_string( "cpytz\n_p\np1\n(S'US/Eastern'\np2\nI-18000\n" "I0\nS'EST'\np3\ntRp4\n." - ) + )) east2 = pytz.timezone('US/Eastern') self.failUnless(east1 is east2) diff --git a/src/pytz/tzfile.py b/src/pytz/tzfile.py index 7ea00c1..2e631a3 100644 --- a/src/pytz/tzfile.py +++ b/src/pytz/tzfile.py @@ -3,13 +3,25 @@ $Id: tzfile.py,v 1.8 2004/06/03 00:15:24 zenzen Exp $ ''' -from cStringIO import StringIO +try: + from cStringIO import StringIO +except ImportError: + from io import StringIO from datetime import datetime, timedelta from struct import unpack, calcsize from pytz.tzinfo import StaticTzInfo, DstTzInfo, memorized_ttinfo from pytz.tzinfo import memorized_datetime, memorized_timedelta +def _byte_string(s): + """Cast a string or byte string to an ASCII byte string.""" + return s.encode('US-ASCII') + +_NULL = _byte_string('\0') + +def _std_string(s): + """Cast a string or byte string to an ASCII string.""" + return str(s.decode('US-ASCII')) def build_tzinfo(zone, fp): head_fmt = '>4s c 15x 6l' @@ -18,7 +30,7 @@ def build_tzinfo(zone, fp): typecnt, charcnt) = unpack(head_fmt, fp.read(head_size)) # Make sure it is a tzfile(5) file - assert magic == 'TZif' + assert magic == _byte_string('TZif'), 'Got magic %s' % repr(magic) # Read out the transition times, localtime indices and ttinfo structures. data_fmt = '>%(timecnt)dl %(timecnt)dB %(ttinfo)s %(charcnt)ds' % dict( @@ -43,10 +55,11 @@ def build_tzinfo(zone, fp): # have we looked up this timezone name yet? tzname_offset = ttinfo_raw[i+2] if tzname_offset not in tznames: - nul = tznames_raw.find('\0', tzname_offset) + nul = tznames_raw.find(_NULL, tzname_offset) if nul < 0: nul = len(tznames_raw) - tznames[tzname_offset] = tznames_raw[tzname_offset:nul] + tznames[tzname_offset] = _std_string( + tznames_raw[tzname_offset:nul]) ttinfo.append((ttinfo_raw[i], bool(ttinfo_raw[i+1]), tznames[tzname_offset])) diff --git a/src/pytz/tzinfo.py b/src/pytz/tzinfo.py index 19edbb2..a505250 100644 --- a/src/pytz/tzinfo.py +++ b/src/pytz/tzinfo.py @@ -102,13 +102,13 @@ class StaticTzInfo(BaseTzInfo): def localize(self, dt, is_dst=False): '''Convert naive time to local time''' if dt.tzinfo is not None: - raise ValueError, 'Not naive datetime (tzinfo is already set)' + raise ValueError('Not naive datetime (tzinfo is already set)') return dt.replace(tzinfo=self) def normalize(self, dt, is_dst=False): '''Correct the timezone information on the given datetime''' if dt.tzinfo is None: - raise ValueError, 'Naive time - no tzinfo set' + raise ValueError('Naive time - no tzinfo set') return dt.replace(tzinfo=self) def __repr__(self): @@ -147,7 +147,7 @@ class DstTzInfo(BaseTzInfo): self._utcoffset, self._dst, self._tzname = self._transition_info[0] _tzinfos[self._transition_info[0]] = self for inf in self._transition_info[1:]: - if not _tzinfos.has_key(inf): + if inf not in _tzinfos: _tzinfos[inf] = self.__class__(inf, _tzinfos) def fromutc(self, dt): @@ -193,7 +193,7 @@ class DstTzInfo(BaseTzInfo): '2002-10-27 01:50:00 EDT (-0400)' ''' if dt.tzinfo is None: - raise ValueError, 'Naive time - no tzinfo set' + raise ValueError('Naive time - no tzinfo set') # Convert dt in localtime to UTC offset = dt.tzinfo._utcoffset @@ -260,7 +260,7 @@ class DstTzInfo(BaseTzInfo): NonExistentTimeError: 2008-03-09 02:00:00 ''' if dt.tzinfo is not None: - raise ValueError, 'Not naive datetime (tzinfo is already set)' + raise ValueError('Not naive datetime (tzinfo is already set)') # Find the two best possibilities. possible_loc_dt = set() diff --git a/src/setup.py b/src/setup.py index 159d528..8c9866f 100644 --- a/src/setup.py +++ b/src/setup.py @@ -8,8 +8,8 @@ try: from setuptools import setup except ImportError: if sys.version_info[:2] == (2,3): - print 'Python 2.3 install requires setuptools' - print 'http://www.python.org/pypi/setuptools/' + print('Python 2.3 install requires setuptools') + print('http://www.python.org/pypi/setuptools/') sys.exit(1) else: from distutils.core import setup -- cgit v1.2.1 From 03160f6e684add68599ac270cca8d0cddc7f82d9 Mon Sep 17 00:00:00 2001 From: Stuart Bishop Date: Tue, 8 Feb 2011 16:09:52 +0700 Subject: Work around doctest exception handling difference between Python 2.x and Python 3.x --- src/pytz/__init__.py | 21 ++++++++++++--------- src/pytz/tzinfo.py | 47 +++++++++++++++++++++++++++-------------------- 2 files changed, 39 insertions(+), 29 deletions(-) diff --git a/src/pytz/__init__.py b/src/pytz/__init__.py index ee757c4..82fed83 100644 --- a/src/pytz/__init__.py +++ b/src/pytz/__init__.py @@ -55,7 +55,7 @@ except NameError: # for Python 2.3 and Python 3.x a pain. def unicode(s): try: - return s.decode('US-ASCII') + return s.decode('unicode_escape') except AttributeError: return str(s) @@ -145,15 +145,18 @@ def timezone(zone): Raises UnknownTimeZoneError if passed an unknown zone. - >>> timezone('Asia/Shangri-La') - Traceback (most recent call last): - ... - UnknownTimeZoneError: 'Asia/Shangri-La' + >>> try: + ... timezone('Asia/Shangri-La') + ... except UnknownTimeZoneError: + ... print('Unknown') + Unknown + + >>> try: + ... timezone(unicode('\N{TRADE MARK SIGN}')) + ... except UnknownTimeZoneError: + ... print('Unknown') + Unknown - >>> timezone(u'\N{TRADE MARK SIGN}') - Traceback (most recent call last): - ... - UnknownTimeZoneError: u'\u2122' ''' if zone.upper() == 'UTC': return utc diff --git a/src/pytz/tzinfo.py b/src/pytz/tzinfo.py index ce6770d..0af8b96 100644 --- a/src/pytz/tzinfo.py +++ b/src/pytz/tzinfo.py @@ -227,10 +227,11 @@ class DstTzInfo(BaseTzInfo): Use is_dst=None to raise an AmbiguousTimeError for ambiguous times at the end of daylight savings - >>> loc_dt1 = amdam.localize(dt, is_dst=None) - Traceback (most recent call last): - [...] - AmbiguousTimeError: 2004-10-31 02:00:00 + >>> try: + ... loc_dt1 = amdam.localize(dt, is_dst=None) + ... except AmbiguousTimeError: + ... print('Ambiguous') + Ambiguous is_dst defaults to False @@ -254,10 +255,11 @@ class DstTzInfo(BaseTzInfo): Use is_dst=None to raise a NonExistentTimeError for these skipped times. - >>> loc_dt1 = pacific.localize(dt, is_dst=None) - Traceback (most recent call last): - [...] - NonExistentTimeError: 2008-03-09 02:00:00 + >>> try: + ... loc_dt1 = pacific.localize(dt, is_dst=None) + ... except NonExistentTimeError: + ... print('Non-existent') + Non-existent ''' if dt.tzinfo is not None: raise ValueError('Not naive datetime (tzinfo is already set)') @@ -353,10 +355,12 @@ class DstTzInfo(BaseTzInfo): >>> tz.utcoffset(ambiguous, is_dst=True) datetime.timedelta(-1, 77400) - >>> tz.utcoffset(ambiguous) - Traceback (most recent call last): - [...] - AmbiguousTimeError: 2009-10-31 23:30:00 + >>> try: + ... tz.utcoffset(ambiguous) + ... except AmbiguousTimeError: + ... print('Ambiguous') + Ambiguous + ''' if dt is None: return None @@ -390,10 +394,12 @@ class DstTzInfo(BaseTzInfo): datetime.timedelta(0) >>> tz.dst(ambiguous, is_dst=True) datetime.timedelta(0, 3600) - >>> tz.dst(ambiguous) - Traceback (most recent call last): - [...] - AmbiguousTimeError: 2009-10-31 23:30:00 + >>> try: + ... tz.dst(ambiguous) + ... except AmbiguousTimeError: + ... print('Ambiguous') + Ambiguous + ''' if dt is None: return None @@ -427,10 +433,11 @@ class DstTzInfo(BaseTzInfo): 'NST' >>> tz.tzname(ambiguous, is_dst=True) 'NDT' - >>> tz.tzname(ambiguous) - Traceback (most recent call last): - [...] - AmbiguousTimeError: 2009-10-31 23:30:00 + >>> try: + ... tz.tzname(ambiguous) + ... except AmbiguousTimeError: + ... print('Ambiguous') + Ambiguous ''' if dt is None: return self.zone -- cgit v1.2.1 From ef497fc9472ca3f7f8a44f50f389cd4cffaa0391 Mon Sep 17 00:00:00 2001 From: Stuart Bishop Date: Tue, 8 Feb 2011 17:18:52 +0700 Subject: Change division operators to explicit floor division --- src/pytz/tzfile.py | 4 ++-- test_zdump.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pytz/tzfile.py b/src/pytz/tzfile.py index 2e631a3..05944da 100644 --- a/src/pytz/tzfile.py +++ b/src/pytz/tzfile.py @@ -111,8 +111,8 @@ def build_tzinfo(zone, fp): # datetime library will complain. Conversions to these timezones # might be up to plus or minus 30 seconds out, but it is # the best we can do. - utcoffset = int((utcoffset + 30) / 60) * 60 - dst = int((dst + 30) / 60) * 60 + utcoffset = int((utcoffset + 30) // 60) * 60 + dst = int((dst + 30) // 60) * 60 transition_info.append(memorized_ttinfo(utcoffset, dst, tzname)) cls = type(zone, (DstTzInfo,), dict( diff --git a/test_zdump.py b/test_zdump.py index c815a28..20ed8be 100644 --- a/test_zdump.py +++ b/test_zdump.py @@ -1,4 +1,4 @@ -#!/usr/bin/python2.4 +#!/usr/bin/python2.7 import os.path, sys sys.path.insert(0, os.path.join('build', 'dist')) @@ -59,13 +59,13 @@ def test_suite(): # minute, so we need to break our tests to match this limitation real_offset = loc_dt - utc_dt secs = real_offset.seconds + real_offset.days*86400 - fake_offset = timedelta(seconds=int((secs+30)/60)*60) + fake_offset = timedelta(seconds=int((secs+30)//60)*60) return utc_dt + fake_offset loc_dt = round_dt(loc_dt, utc_dt) # If the naive time on the next line is less than on this - # line, and we arn't seeing an end-of-dst transition, then + # line, and we aren't seeing an end-of-dst transition, then # we can't do our local->utc tests for either this nor the # next line since we are in an ambiguous time period (ie. # we have wound back the clock but don't have differing -- cgit v1.2.1 From 9afd970cb95df82f10a5cf6043abbd07b336cdef Mon Sep 17 00:00:00 2001 From: Stuart Bishop Date: Tue, 8 Feb 2011 17:26:42 +0700 Subject: Fix testOldPickles test --- src/pytz/tests/test_tzinfo.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/pytz/tests/test_tzinfo.py b/src/pytz/tests/test_tzinfo.py index dc2363c..31c34e6 100644 --- a/src/pytz/tests/test_tzinfo.py +++ b/src/pytz/tests/test_tzinfo.py @@ -146,22 +146,22 @@ class PicklingTest(unittest.TestCase): # have no troubles upgrading to a new pytz release. These pickles # where created with pytz2006j east1 = pickle.loads(_byte_string( - "cpytz\n_p\np1\n(S'US/Eastern'\np2\nI-18000\n" - "I0\nS'EST'\np3\ntRp4\n." - )) + "cpytz\n_p\np1\n(S'US/Eastern'\np2\nI-18000\n" + "I0\nS'EST'\np3\ntRp4\n." + )) east2 = pytz.timezone('US/Eastern') self.failUnless(east1 is east2) # Confirm changes in name munging between 2006j and 2007c cause # no problems. - pap1 = pickle.loads( - "cpytz\n_p\np1\n(S'America/Port_minus_au_minus_Prince'" - "\np2\nI-17340\nI0\nS'PPMT'\np3\ntRp4\n." - ) + pap1 = pickle.loads(_byte_string( + "cpytz\n_p\np1\n(S'America/Port_minus_au_minus_Prince'" + "\np2\nI-17340\nI0\nS'PPMT'\np3\ntRp4\n.")) pap2 = pytz.timezone('America/Port-au-Prince') self.failUnless(pap1 is pap2) - gmt1 = pickle.loads("cpytz\n_p\np1\n(S'Etc/GMT_plus_10'\np2\ntRp3\n.") + gmt1 = pickle.loads(_byte_string( + "cpytz\n_p\np1\n(S'Etc/GMT_plus_10'\np2\ntRp3\n.")) gmt2 = pytz.timezone('Etc/GMT+10') self.failUnless(gmt1 is gmt2) -- cgit v1.2.1 From d0666f743b6f72a674a4240cbaee71ab5077f657 Mon Sep 17 00:00:00 2001 From: Stuart Bishop Date: Tue, 8 Feb 2011 17:37:38 +0700 Subject: Fix remaining pickle tests --- src/pytz/tests/test_tzinfo.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/pytz/tests/test_tzinfo.py b/src/pytz/tests/test_tzinfo.py index 31c34e6..16baca1 100644 --- a/src/pytz/tests/test_tzinfo.py +++ b/src/pytz/tests/test_tzinfo.py @@ -133,9 +133,14 @@ class PicklingTest(unittest.TestCase): # data will continue to be used. p = pickle.dumps(tz) new_utcoffset = tz._utcoffset.seconds + 42 - hacked_p = p.replace( - _byte_string(str(tz._utcoffset.seconds)), - _byte_string(str(new_utcoffset))) + + # Python 3 introduced a new pickle protocol where numbers are stored in + # hexadecimal representation. Here we extract the pickle + # representation of the number for the current Python version. + old_pickle_pattern = pickle.dumps(tz._utcoffset.seconds)[3:-1] + new_pickle_pattern = pickle.dumps(new_utcoffset)[3:-1] + hacked_p = p.replace(old_pickle_pattern, new_pickle_pattern) + self.failIfEqual(p, hacked_p) unpickled_tz = pickle.loads(hacked_p) self.failUnlessEqual(unpickled_tz._utcoffset.seconds, new_utcoffset) -- cgit v1.2.1 From a8ce5e08c8c24d7c7489cd42b5388ffa2647e734 Mon Sep 17 00:00:00 2001 From: Stuart Bishop Date: Tue, 8 Feb 2011 17:44:18 +0700 Subject: Work around unicode string representation issues in main README --- src/README.txt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/README.txt b/src/README.txt index 0fdf58a..97f145f 100644 --- a/src/README.txt +++ b/src/README.txt @@ -393,14 +393,14 @@ for a particular country, looked up using the ISO 3166 country code. It returns a list of strings that can be used to retrieve the relevant tzinfo instance using ``pytz.timezone()``: ->>> pytz.country_timezones['nz'] -['Pacific/Auckland', 'Pacific/Chatham'] +>>> print(' '.join(pytz.country_timezones['nz'])) +Pacific/Auckland Pacific/Chatham The Olson database comes with a ISO 3166 country code to English country name mapping that pytz exposes as a dictionary: ->>> pytz.country_names['nz'] -'New Zealand' +>>> print(pytz.country_names['nz']) +New Zealand What is UTC @@ -478,10 +478,10 @@ using the ``country_timezones()`` function. It requires an ISO-3166 two letter country code. >>> from pytz import country_timezones ->>> country_timezones('ch') -['Europe/Zurich'] ->>> country_timezones('CH') -['Europe/Zurich'] +>>> print(' '.join(country_timezones('ch'))) +Europe/Zurich +>>> print(' '.join(country_timezones('CH'))) +Europe/Zurich License -- cgit v1.2.1 From 0c3c515b5cace92a512ad5eeceb5b1d9c830608f Mon Sep 17 00:00:00 2001 From: Stuart Bishop Date: Tue, 8 Feb 2011 18:01:52 +0700 Subject: Move exceptions to a dedicated module for consistent Python 3.x doctests --- src/pytz/__init__.py | 23 ++++------------------- src/pytz/exceptions.py | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ src/pytz/tzinfo.py | 24 +----------------------- 3 files changed, 53 insertions(+), 42 deletions(-) create mode 100644 src/pytz/exceptions.py diff --git a/src/pytz/__init__.py b/src/pytz/__init__.py index 82fed83..bd3c8f4 100644 --- a/src/pytz/__init__.py +++ b/src/pytz/__init__.py @@ -36,9 +36,10 @@ try: except ImportError: resource_stream = None -from pytz.tzinfo import AmbiguousTimeError -from pytz.tzinfo import InvalidTimeError -from pytz.tzinfo import NonExistentTimeError +from pytz.exceptions import AmbiguousTimeError +from pytz.exceptions import InvalidTimeError +from pytz.exceptions import NonExistentTimeError +from pytz.exceptions import UnknownTimeZoneError from pytz.tzinfo import unpickler from pytz.tzfile import build_tzinfo, _byte_string @@ -103,22 +104,6 @@ def resource_exists(name): # return t.ugettext(timezone_name) -class UnknownTimeZoneError(KeyError): - '''Exception raised when pytz is passed an unknown timezone. - - >>> isinstance(UnknownTimeZoneError(), LookupError) - True - - This class is actually a subclass of KeyError to provide backwards - compatibility with code relying on the undocumented behavior of earlier - pytz releases. - - >>> isinstance(UnknownTimeZoneError(), KeyError) - True - ''' - pass - - _tzinfo_cache = {} def timezone(zone): diff --git a/src/pytz/exceptions.py b/src/pytz/exceptions.py new file mode 100644 index 0000000..0376108 --- /dev/null +++ b/src/pytz/exceptions.py @@ -0,0 +1,48 @@ +''' +Custom exceptions raised by pytz. +''' + +__all__ = [ + 'UnknownTimeZoneError', 'InvalidTimeError', 'AmbiguousTimeError', + 'NonExistentTimeError', + ] + + +class UnknownTimeZoneError(KeyError): + '''Exception raised when pytz is passed an unknown timezone. + + >>> isinstance(UnknownTimeZoneError(), LookupError) + True + + This class is actually a subclass of KeyError to provide backwards + compatibility with code relying on the undocumented behavior of earlier + pytz releases. + + >>> isinstance(UnknownTimeZoneError(), KeyError) + True + ''' + pass + + +class InvalidTimeError(Exception): + '''Base class for invalid time exceptions.''' + + +class AmbiguousTimeError(InvalidTimeError): + '''Exception raised when attempting to create an ambiguous wallclock time. + + At the end of a DST transition period, a particular wallclock time will + occur twice (once before the clocks are set back, once after). Both + possibilities may be correct, unless further information is supplied. + + See DstTzInfo.normalize() for more info + ''' + + +class NonExistentTimeError(InvalidTimeError): + '''Exception raised when attempting to create a wallclock time that + cannot exist. + + At the start of a DST transition period, the wallclock time jumps forward. + The instants jumped over never occur. + ''' diff --git a/src/pytz/tzinfo.py b/src/pytz/tzinfo.py index 0af8b96..836e4b4 100644 --- a/src/pytz/tzinfo.py +++ b/src/pytz/tzinfo.py @@ -8,6 +8,7 @@ except NameError: from sets import Set as set import pytz +from pytz.exceptions import AmbiguousTimeError, NonExistentTimeError __all__ = [] @@ -472,29 +473,6 @@ class DstTzInfo(BaseTzInfo): ) -class InvalidTimeError(Exception): - '''Base class for invalid time exceptions.''' - - -class AmbiguousTimeError(InvalidTimeError): - '''Exception raised when attempting to create an ambiguous wallclock time. - - At the end of a DST transition period, a particular wallclock time will - occur twice (once before the clocks are set back, once after). Both - possibilities may be correct, unless further information is supplied. - - See DstTzInfo.normalize() for more info - ''' - - -class NonExistentTimeError(InvalidTimeError): - '''Exception raised when attempting to create a wallclock time that - cannot exist. - - At the start of a DST transition period, the wallclock time jumps forward. - The instants jumped over never occur. - ''' - def unpickler(zone, utcoffset=None, dstoffset=None, tzname=None): """Factory function for unpickling pytz tzinfo instances. -- cgit v1.2.1 From eacdbb2ca48d6ee558dc0ac11bd0e35fc89dae1c Mon Sep 17 00:00:00 2001 From: Stuart Bishop Date: Tue, 8 Feb 2011 18:25:42 +0700 Subject: Work around list.sort imcompatibility between Python 2.x and Python 3.x --- src/README.txt | 13 +++++++------ src/pytz/tzinfo.py | 13 ++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/README.txt b/src/README.txt index 97f145f..27400e4 100644 --- a/src/README.txt +++ b/src/README.txt @@ -261,18 +261,19 @@ in different timezones or analyze log files it is not acceptable. The best and simplest solution is to stick with using UTC. The pytz package encourages using UTC for internal timezone representation by including a special UTC implementation based on the standard Python -reference implementation in the Python documentation. This timezone -unpickles to be the same instance, and pickles to a relatively small -size. The UTC implementation can be obtained as pytz.utc, pytz.UTC, -or pytz.timezone('UTC'). +reference implementation in the Python documentation. + +The UTC timezone unpickles to be the same instance, and pickles to a +smaller size than other pytz tzinfo instances. The UTC implementation +can be obtained as pytz.utc, pytz.UTC, or pytz.timezone('UTC'). >>> import pickle, pytz >>> dt = datetime(2005, 3, 1, 14, 13, 21, tzinfo=utc) >>> naive = dt.replace(tzinfo=None) >>> p = pickle.dumps(dt, 1) >>> naive_p = pickle.dumps(naive, 1) ->>> len(p), len(naive_p), len(p) - len(naive_p) -(60, 43, 17) +>>> len(p) - len(naive_p) +17 >>> new = pickle.loads(p) >>> new == dt True diff --git a/src/pytz/tzinfo.py b/src/pytz/tzinfo.py index 836e4b4..2be4fb8 100644 --- a/src/pytz/tzinfo.py +++ b/src/pytz/tzinfo.py @@ -332,13 +332,12 @@ class DstTzInfo(BaseTzInfo): # but that is just getting silly. # # Choose the earliest (by UTC) applicable timezone. - def mycmp(a,b): - return cmp( - a.replace(tzinfo=None) - a.tzinfo._utcoffset, - b.replace(tzinfo=None) - b.tzinfo._utcoffset, - ) - filtered_possible_loc_dt.sort(mycmp) - return filtered_possible_loc_dt[0] + sorting_keys = {} + for local_dt in filtered_possible_loc_dt: + key = local_dt.replace(tzinfo=None) - local_dt.tzinfo._utcoffset + sorting_keys[key] = local_dt + first_key = sorted(sorting_keys)[0] + return sorting_keys[first_key] def utcoffset(self, dt, is_dst=None): '''See datetime.tzinfo.utcoffset -- cgit v1.2.1 From b314f589733f161eb8398688515e9682d29c4d4f Mon Sep 17 00:00:00 2001 From: Stuart Bishop Date: Tue, 8 Feb 2011 19:19:27 +0700 Subject: Tweak README so tests pass on Python 2.4+ --- src/README.txt | 50 +++++++++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/src/README.txt b/src/README.txt index 27400e4..33342b5 100644 --- a/src/README.txt +++ b/src/README.txt @@ -209,7 +209,7 @@ datetime.timedelta(0) 'NST' If ``is_dst`` is not specified, ambiguous timestamps will raise -an ``AmbiguousTimeError`` exception. +an ``pytz.exceptions.AmbiguousTimeError`` exception. >>> tz.utcoffset(normal) datetime.timedelta(-1, 77400) @@ -218,18 +218,22 @@ datetime.timedelta(0, 3600) >>> tz.tzname(normal) 'NDT' ->>> tz.utcoffset(ambiguous) -Traceback (most recent call last): -[...] -AmbiguousTimeError: 2009-10-31 23:30:00 ->>> tz.dst(ambiguous) -Traceback (most recent call last): -[...] -AmbiguousTimeError: 2009-10-31 23:30:00 ->>> tz.tzname(ambiguous) -Traceback (most recent call last): -[...] -AmbiguousTimeError: 2009-10-31 23:30:00 +>>> import pytz.exceptions +>>> try: +... tz.utcoffset(ambiguous) +... except pytz.exceptions.AmbiguousTimeError: +... print('pytz.exceptions.AmbiguousTimeError: %s' % ambiguous) +pytz.exceptions.AmbiguousTimeError: 2009-10-31 23:30:00 +>>> try: +... tz.dst(ambiguous) +... except pytz.exceptions.AmbiguousTimeError: +... print('pytz.exceptions.AmbiguousTimeError: %s' % ambiguous) +pytz.exceptions.AmbiguousTimeError: 2009-10-31 23:30:00 +>>> try: +... tz.tzname(ambiguous) +... except pytz.exceptions.AmbiguousTimeError: +... print('pytz.exceptions.AmbiguousTimeError: %s' % ambiguous) +pytz.exceptions.AmbiguousTimeError: 2009-10-31 23:30:00 Problems with Localtime @@ -307,19 +311,23 @@ For example, 1:30am on 27th Oct 2002 happened twice in the US/Eastern timezone when the clocks where put back at the end of Daylight Savings Time: ->>> eastern.localize(datetime(2002, 10, 27, 1, 30, 00), is_dst=None) -Traceback (most recent call last): -... -AmbiguousTimeError: 2002-10-27 01:30:00 +>>> dt = datetime(2002, 10, 27, 1, 30, 00) +>>> try: +... eastern.localize(dt, is_dst=None) +... except pytz.exceptions.AmbiguousTimeError: +... print('pytz.exceptions.AmbiguousTimeError: %s' % dt) +pytz.exceptions.AmbiguousTimeError: 2002-10-27 01:30:00 Similarly, 2:30am on 7th April 2002 never happened at all in the US/Eastern timezone, as the clocks where put forward at 2:00am skipping the entire hour: ->>> eastern.localize(datetime(2002, 4, 7, 2, 30, 00), is_dst=None) -Traceback (most recent call last): -... -NonExistentTimeError: 2002-04-07 02:30:00 +>>> dt = datetime(2002, 4, 7, 2, 30, 00) +>>> try: +... eastern.localize(dt, is_dst=None) +... except pytz.exceptions.NonExistentTimeError: +... print('pytz.exceptions.NonExistentTimeError: %s' % dt) +pytz.exceptions.NonExistentTimeError: 2002-04-07 02:30:00 Both of these exceptions share a common base class to make error handling easier: -- cgit v1.2.1 From 2fdd36f6efd4169e852e2675aa7919b7416f25b0 Mon Sep 17 00:00:00 2001 From: Stuart Bishop Date: Tue, 8 Feb 2011 19:19:53 +0700 Subject: Old syntax --- test_zdump.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_zdump.py b/test_zdump.py index 20ed8be..f6425e9 100644 --- a/test_zdump.py +++ b/test_zdump.py @@ -37,7 +37,7 @@ def test_suite(): line = raw_data[i] m = zdump_line_re.search(line) if m is None: - raise RuntimeError, 'Dud line %r' % (line,) + raise RuntimeError('Dud line %r' % (line,)) zone, utc_string, loc_string, tzname, is_dst = m.groups() is_dst = bool(int(is_dst)) -- cgit v1.2.1 From 208079901e90b1bae76f33c945045f075c7bb7f3 Mon Sep 17 00:00:00 2001 From: Stuart Bishop Date: Tue, 8 Feb 2011 19:26:29 +0700 Subject: Don't use deprecated stuff --- gen_tests.py | 11 +++++++---- test_zdump.py | 1 - 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/gen_tests.py b/gen_tests.py index a195c1c..085f94d 100644 --- a/gen_tests.py +++ b/gen_tests.py @@ -4,7 +4,7 @@ $Id: gen_tests.py,v 1.15 2005/01/07 04:51:30 zenzen Exp $ ''' -import os, os.path, popen2, re, sys +import os, os.path, re, subprocess, sys from gen_tzinfo import allzones import gen_tzinfo from time import strptime @@ -27,12 +27,15 @@ def main(): # 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 1902,2038 %s' % (zdump, zone)) - zd_in.close() + command = '%s -v -c 1902,2038 %s' % (zdump, zone) + process = subprocess.Popen( + command, shell=True, + stdin=subprocess.PIPE, stdout=subprocess.PIPE, close_fds=True) + zd_in, zd_out = (process.stdin, process.stdout) # Skip bogus output on 64bit architectures, per Bug #213816 lines = [ line.strip() for line in zd_out.readlines() - if not line.strip().endswith('NULL')] + if not line.decode('utf-8').strip().endswith('NULL')] for line in lines: print >> datf, line diff --git a/test_zdump.py b/test_zdump.py index f6425e9..f70173e 100644 --- a/test_zdump.py +++ b/test_zdump.py @@ -4,7 +4,6 @@ import os.path, sys sys.path.insert(0, os.path.join('build', 'dist')) from datetime import datetime, timedelta -import new import re from time import strptime import unittest -- cgit v1.2.1 From 5f0fde4c674b82a82c91d5ac31dcdd96618ad040 Mon Sep 17 00:00:00 2001 From: Stuart Bishop Date: Tue, 8 Feb 2011 19:48:43 +0700 Subject: Update CHANGES.txt --- src/CHANGES.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 94eb836..7cb8b1b 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -48,3 +48,7 @@ - Fix test_zdump tests and bugs the fixed tests picked up, including the fix for Bug #427444. + +2011-02-08 + + - Python 3.1 support. -- cgit v1.2.1