summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStuart Bishop <stuart@stuartbishop.net>2011-02-08 19:50:50 +0700
committerStuart Bishop <stuart@stuartbishop.net>2011-02-08 19:50:50 +0700
commitcab8a37ab296020da8aac62fc8f7ba66b6aecf62 (patch)
tree32f841762611ade889ec81327b78ef6128730eb6
parent589fbdc2eae7ad19a993c1c3ab2ce8671e15cae3 (diff)
parent5f0fde4c674b82a82c91d5ac31dcdd96618ad040 (diff)
downloadpytz-git-cab8a37ab296020da8aac62fc8f7ba66b6aecf62.tar.gz
Merge Python 3.1 support from lp:~stub/pytz/python3x2011b_release
-rw-r--r--Makefile15
-rw-r--r--gen_tests.py11
-rw-r--r--src/CHANGES.txt4
-rw-r--r--src/README.txt84
-rw-r--r--src/pytz/__init__.py125
-rw-r--r--src/pytz/exceptions.py48
-rw-r--r--src/pytz/tests/test_tzinfo.py36
-rw-r--r--src/pytz/tzfile.py25
-rw-r--r--src/pytz/tzinfo.py94
-rw-r--r--test_zdump.py9
10 files changed, 280 insertions, 171 deletions
diff --git a/Makefile b/Makefile
index 9e0142b..1ca04a8 100644
--- a/Makefile
+++ b/Makefile
@@ -6,6 +6,7 @@ PYTHON24=python2.4
PYTHON25=python2.5
PYTHON26=python2.6
PYTHON27=python2.7
+PYTHON31=python3.1
PYTHON=${PYTHON26}
OLSON=./elsie.nci.nih.gov
TESTARGS=-vv
@@ -27,7 +28,8 @@ dist: build/dist/locales/pytz.pot .stamp-dist
${PYTHON24} setup.py bdist_egg --dist-dir=../tarballs && \
${PYTHON25} setup.py bdist_egg --dist-dir=../tarballs && \
${PYTHON26} setup.py bdist_egg --dist-dir=../tarballs && \
- ${PYTHON27} setup.py bdist_egg --dist-dir=../tarballs
+ ${PYTHON27} 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
${PYTHON26} setup.py register bdist_egg --dist-dir=../tarballs \
upload --sign && \
${PYTHON27} 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
&& ${PYTHON24} test_tzinfo.py ${TESTARGS} \
&& ${PYTHON25} test_tzinfo.py ${TESTARGS} \
&& ${PYTHON26} test_tzinfo.py ${TESTARGS} \
- && ${PYTHON27} test_tzinfo.py ${TESTARGS}
+ && ${PYTHON27} test_tzinfo.py ${TESTARGS} \
+ && ${PYTHON31} test_tzinfo.py ${TESTARGS}
test_docs: .stamp-tzinfo
cd build/dist/pytz/tests \
&& ${PYTHON24} test_docs.py ${TESTARGS} \
&& ${PYTHON25} test_docs.py ${TESTARGS} \
&& ${PYTHON26} test_docs.py ${TESTARGS} \
- && ${PYTHON27} test_docs.py ${TESTARGS}
+ && ${PYTHON27} test_docs.py ${TESTARGS} \
+ && ${PYTHON31} test_docs.py ${TESTARGS}
test_zdump: dist
${PYTHON} gen_tests.py ${TARGET} && \
- ${PYTHON} test_zdump.py ${TESTARGS}
+ ${PYTHON} test_zdump.py ${TESTARGS} && \
+ ${PYTHON31} test_zdump.py ${TESTARGS}
build/dist/test_zdump.py: .stamp-zoneinfo
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/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.
diff --git a/src/README.txt b/src/README.txt
index 521ac36..33342b5 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
@@ -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
@@ -261,18 +265,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
@@ -295,7 +300,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
@@ -306,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:
@@ -393,14 +402,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 +487,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
@@ -541,3 +550,4 @@ Contact
Stuart Bishop <stuart@stuartbishop.net>
+
diff --git a/src/pytz/__init__.py b/src/pytz/__init__.py
index 6d73c59..4da016a 100644
--- a/src/pytz/__init__.py
+++ b/src/pytz/__init__.py
@@ -26,16 +26,34 @@ __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.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
+
+
+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('unicode_escape')
+ except AttributeError:
+ return str(s)
def open_resource(name):
@@ -82,22 +100,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):
@@ -108,7 +110,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)
@@ -124,21 +126,24 @@ 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
try:
- zone = zone.encode('US-ASCII')
+ zone.encode('US-ASCII')
except UnicodeEncodeError:
# All valid timezones are ASCII
raise UnknownTimeZoneError(zone)
@@ -193,13 +198,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):
@@ -227,8 +232,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
@@ -263,6 +268,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()
@@ -275,13 +295,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'
@@ -289,8 +317,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."""
@@ -300,6 +329,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]
@@ -317,13 +347,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)
@@ -363,13 +394,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/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/tests/test_tzinfo.py b/src/pytz/tests/test_tzinfo.py
index 7e26068..6dc9327 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, time, timedelta, tzinfo
if __name__ == '__main__':
@@ -12,6 +15,7 @@ if __name__ == '__main__':
import pytz
from pytz import reference
+from pytz.tzfile import _byte_string
from pytz.tzinfo import StaticTzInfo
# I test for expected version to ensure the correct version of pytz is
@@ -28,7 +32,6 @@ UTC = pytz.timezone('UTC')
GMT = pytz.timezone('GMT')
assert isinstance(GMT, StaticTzInfo), 'GMT is no longer a StaticTzInfo'
-
def prettydt(dt):
"""datetime as a string using a known format.
@@ -121,7 +124,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)
@@ -130,7 +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(str(tz._utcoffset.seconds), 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)
@@ -140,23 +150,23 @@ 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(
- "cpytz\n_p\np1\n(S'US/Eastern'\np2\nI-18000\n"
- "I0\nS'EST'\np3\ntRp4\n."
- )
+ 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)
# 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)
diff --git a/src/pytz/tzfile.py b/src/pytz/tzfile.py
index 7ea00c1..05944da 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]))
@@ -98,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/src/pytz/tzinfo.py b/src/pytz/tzinfo.py
index 3c45675..2be4fb8 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__ = []
@@ -102,13 +103,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 +148,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 +194,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
@@ -227,10 +228,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,13 +256,14 @@ 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)'
+ raise ValueError('Not naive datetime (tzinfo is already set)')
# Find the two best possibilities.
possible_loc_dt = set()
@@ -329,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
@@ -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
@@ -465,29 +472,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.
diff --git a/test_zdump.py b/test_zdump.py
index c815a28..f70173e 100644
--- a/test_zdump.py
+++ b/test_zdump.py
@@ -1,10 +1,9 @@
-#!/usr/bin/python2.4
+#!/usr/bin/python2.7
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
@@ -37,7 +36,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))
@@ -59,13 +58,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