subtraction, work as documented. In the Python implementation, they weren't calling utcoffset() if both operands had the same tzinfo object. That's fine if it so happens that the shared tzinfo object returns a fixed offset (independent of operand), but can give wrong results if that's not so, and the latter obtains in a tzinfo subclass instance trying to model both standard and daylight times. The C implementation was already doing this "correctly", so we're just adding tests to verify it.
2462 lines
96 KiB
Python
2462 lines
96 KiB
Python
"""Test date/time type."""
|
|
|
|
import sys
|
|
import unittest
|
|
|
|
from test import test_support
|
|
|
|
from datetime import MINYEAR, MAXYEAR
|
|
from datetime import timedelta
|
|
from datetime import tzinfo
|
|
from datetime import time, timetz
|
|
from datetime import date, datetime, datetimetz
|
|
|
|
#############################################################################
|
|
# module tests
|
|
|
|
class TestModule(unittest.TestCase):
|
|
|
|
def test_constants(self):
|
|
import datetime
|
|
self.assertEqual(datetime.MINYEAR, 1)
|
|
self.assertEqual(datetime.MAXYEAR, 9999)
|
|
|
|
#############################################################################
|
|
# tzinfo tests
|
|
|
|
class FixedOffset(tzinfo):
|
|
def __init__(self, offset, name, dstoffset=42):
|
|
self.__offset = offset
|
|
self.__name = name
|
|
self.__dstoffset = dstoffset
|
|
def __repr__(self):
|
|
return self.__name.lower()
|
|
def utcoffset(self, dt):
|
|
return self.__offset
|
|
def tzname(self, dt):
|
|
return self.__name
|
|
def dst(self, dt):
|
|
return self.__dstoffset
|
|
|
|
class PicklableFixedOffset(FixedOffset):
|
|
def __init__(self, offset=None, name=None, dstoffset=None):
|
|
FixedOffset.__init__(self, offset, name, dstoffset)
|
|
|
|
class TestTZInfo(unittest.TestCase):
|
|
|
|
def test_non_abstractness(self):
|
|
# In order to allow subclasses to get pickled, the C implementation
|
|
# wasn't able to get away with having __init__ raise
|
|
# NotImplementedError.
|
|
useless = tzinfo()
|
|
dt = datetime.max
|
|
self.assertRaises(NotImplementedError, useless.tzname, dt)
|
|
self.assertRaises(NotImplementedError, useless.utcoffset, dt)
|
|
self.assertRaises(NotImplementedError, useless.dst, dt)
|
|
|
|
def test_subclass_must_override(self):
|
|
class NotEnough(tzinfo):
|
|
def __init__(self, offset, name):
|
|
self.__offset = offset
|
|
self.__name = name
|
|
self.failUnless(issubclass(NotEnough, tzinfo))
|
|
ne = NotEnough(3, "NotByALongShot")
|
|
self.failUnless(isinstance(ne, tzinfo))
|
|
|
|
dt = datetime.now()
|
|
self.assertRaises(NotImplementedError, ne.tzname, dt)
|
|
self.assertRaises(NotImplementedError, ne.utcoffset, dt)
|
|
self.assertRaises(NotImplementedError, ne.dst, dt)
|
|
|
|
def test_normal(self):
|
|
fo = FixedOffset(3, "Three")
|
|
self.failUnless(isinstance(fo, tzinfo))
|
|
for dt in datetime.now(), None:
|
|
self.assertEqual(fo.utcoffset(dt), 3)
|
|
self.assertEqual(fo.tzname(dt), "Three")
|
|
self.assertEqual(fo.dst(dt), 42)
|
|
|
|
def test_pickling_base(self):
|
|
import pickle, cPickle
|
|
|
|
# There's no point to pickling tzinfo objects on their own (they
|
|
# carry no data), but they need to be picklable anyway else
|
|
# concrete subclasses can't be pickled.
|
|
orig = tzinfo.__new__(tzinfo)
|
|
self.failUnless(type(orig) is tzinfo)
|
|
for pickler in pickle, cPickle:
|
|
for binary in 0, 1:
|
|
green = pickler.dumps(orig, binary)
|
|
derived = pickler.loads(green)
|
|
self.failUnless(type(derived) is tzinfo)
|
|
|
|
def test_pickling_subclass(self):
|
|
import pickle, cPickle
|
|
|
|
# Make sure we can pickle/unpickle an instance of a subclass.
|
|
orig = PicklableFixedOffset(-300, 'cookie')
|
|
self.failUnless(isinstance(orig, tzinfo))
|
|
self.failUnless(type(orig) is PicklableFixedOffset)
|
|
self.assertEqual(orig.utcoffset(None), -300)
|
|
self.assertEqual(orig.tzname(None), 'cookie')
|
|
for pickler in pickle, cPickle:
|
|
for binary in 0, 1:
|
|
green = pickler.dumps(orig, binary)
|
|
derived = pickler.loads(green)
|
|
self.failUnless(isinstance(derived, tzinfo))
|
|
self.failUnless(type(derived) is PicklableFixedOffset)
|
|
self.assertEqual(derived.utcoffset(None), -300)
|
|
self.assertEqual(derived.tzname(None), 'cookie')
|
|
|
|
#############################################################################
|
|
# timedelta tests
|
|
|
|
class TestTimeDelta(unittest.TestCase):
|
|
|
|
def test_constructor(self):
|
|
eq = self.assertEqual
|
|
td = timedelta
|
|
|
|
# Check keyword args to constructor
|
|
eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
|
|
milliseconds=0, microseconds=0))
|
|
eq(td(1), td(days=1))
|
|
eq(td(0, 1), td(seconds=1))
|
|
eq(td(0, 0, 1), td(microseconds=1))
|
|
eq(td(weeks=1), td(days=7))
|
|
eq(td(days=1), td(hours=24))
|
|
eq(td(hours=1), td(minutes=60))
|
|
eq(td(minutes=1), td(seconds=60))
|
|
eq(td(seconds=1), td(milliseconds=1000))
|
|
eq(td(milliseconds=1), td(microseconds=1000))
|
|
|
|
# Check float args to constructor
|
|
eq(td(weeks=1.0/7), td(days=1))
|
|
eq(td(days=1.0/24), td(hours=1))
|
|
eq(td(hours=1.0/60), td(minutes=1))
|
|
eq(td(minutes=1.0/60), td(seconds=1))
|
|
eq(td(seconds=0.001), td(milliseconds=1))
|
|
eq(td(milliseconds=0.001), td(microseconds=1))
|
|
|
|
def test_computations(self):
|
|
eq = self.assertEqual
|
|
td = timedelta
|
|
|
|
a = td(7) # One week
|
|
b = td(0, 60) # One minute
|
|
c = td(0, 0, 1000) # One millisecond
|
|
eq(a+b+c, td(7, 60, 1000))
|
|
eq(a-b, td(6, 24*3600 - 60))
|
|
eq(-a, td(-7))
|
|
eq(+a, td(7))
|
|
eq(-b, td(-1, 24*3600 - 60))
|
|
eq(-c, td(-1, 24*3600 - 1, 999000))
|
|
eq(abs(a), a)
|
|
eq(abs(-a), a)
|
|
eq(td(6, 24*3600), a)
|
|
eq(td(0, 0, 60*1000000), b)
|
|
eq(a*10, td(70))
|
|
eq(a*10, 10*a)
|
|
eq(a*10L, 10*a)
|
|
eq(b*10, td(0, 600))
|
|
eq(10*b, td(0, 600))
|
|
eq(b*10L, td(0, 600))
|
|
eq(c*10, td(0, 0, 10000))
|
|
eq(10*c, td(0, 0, 10000))
|
|
eq(c*10L, td(0, 0, 10000))
|
|
eq(a*-1, -a)
|
|
eq(b*-2, -b-b)
|
|
eq(c*-2, -c+-c)
|
|
eq(b*(60*24), (b*60)*24)
|
|
eq(b*(60*24), (60*b)*24)
|
|
eq(c*1000, td(0, 1))
|
|
eq(1000*c, td(0, 1))
|
|
eq(a//7, td(1))
|
|
eq(b//10, td(0, 6))
|
|
eq(c//1000, td(0, 0, 1))
|
|
eq(a//10, td(0, 7*24*360))
|
|
eq(a//3600000, td(0, 0, 7*24*1000))
|
|
|
|
def test_disallowed_computations(self):
|
|
a = timedelta(42)
|
|
|
|
# Add/sub ints, longs, floats should be illegal
|
|
for i in 1, 1L, 1.0:
|
|
self.assertRaises(TypeError, lambda: a+i)
|
|
self.assertRaises(TypeError, lambda: a-i)
|
|
self.assertRaises(TypeError, lambda: i+a)
|
|
self.assertRaises(TypeError, lambda: i-a)
|
|
|
|
# Mul/div by float isn't supported.
|
|
x = 2.3
|
|
self.assertRaises(TypeError, lambda: a*x)
|
|
self.assertRaises(TypeError, lambda: x*a)
|
|
self.assertRaises(TypeError, lambda: a/x)
|
|
self.assertRaises(TypeError, lambda: x/a)
|
|
self.assertRaises(TypeError, lambda: a // x)
|
|
self.assertRaises(TypeError, lambda: x // a)
|
|
|
|
# Divison of int by timedelta doesn't make sense.
|
|
# Division by zero doesn't make sense.
|
|
for zero in 0, 0L:
|
|
self.assertRaises(TypeError, lambda: zero // a)
|
|
self.assertRaises(ZeroDivisionError, lambda: a // zero)
|
|
|
|
def test_basic_attributes(self):
|
|
days, seconds, us = 1, 7, 31
|
|
td = timedelta(days, seconds, us)
|
|
self.assertEqual(td.days, days)
|
|
self.assertEqual(td.seconds, seconds)
|
|
self.assertEqual(td.microseconds, us)
|
|
|
|
def test_carries(self):
|
|
t1 = timedelta(days=100,
|
|
weeks=-7,
|
|
hours=-24*(100-49),
|
|
minutes=-3,
|
|
seconds=12,
|
|
microseconds=(3*60 - 12) * 1e6 + 1)
|
|
t2 = timedelta(microseconds=1)
|
|
self.assertEqual(t1, t2)
|
|
|
|
def test_hash_equality(self):
|
|
t1 = timedelta(days=100,
|
|
weeks=-7,
|
|
hours=-24*(100-49),
|
|
minutes=-3,
|
|
seconds=12,
|
|
microseconds=(3*60 - 12) * 1000000)
|
|
t2 = timedelta()
|
|
self.assertEqual(hash(t1), hash(t2))
|
|
|
|
t1 += timedelta(weeks=7)
|
|
t2 += timedelta(days=7*7)
|
|
self.assertEqual(t1, t2)
|
|
self.assertEqual(hash(t1), hash(t2))
|
|
|
|
d = {t1: 1}
|
|
d[t2] = 2
|
|
self.assertEqual(len(d), 1)
|
|
self.assertEqual(d[t1], 2)
|
|
|
|
def test_pickling(self):
|
|
import pickle, cPickle
|
|
args = 12, 34, 56
|
|
orig = timedelta(*args)
|
|
state = orig.__getstate__()
|
|
self.assertEqual(args, state)
|
|
derived = timedelta()
|
|
derived.__setstate__(state)
|
|
self.assertEqual(orig, derived)
|
|
for pickler in pickle, cPickle:
|
|
for binary in 0, 1:
|
|
green = pickler.dumps(orig, binary)
|
|
derived = pickler.loads(green)
|
|
self.assertEqual(orig, derived)
|
|
|
|
def test_compare(self):
|
|
t1 = timedelta(2, 3, 4)
|
|
t2 = timedelta(2, 3, 4)
|
|
self.failUnless(t1 == t2)
|
|
self.failUnless(t1 <= t2)
|
|
self.failUnless(t1 >= t2)
|
|
self.failUnless(not t1 != t2)
|
|
self.failUnless(not t1 < t2)
|
|
self.failUnless(not t1 > t2)
|
|
self.assertEqual(cmp(t1, t2), 0)
|
|
self.assertEqual(cmp(t2, t1), 0)
|
|
|
|
for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
|
|
t2 = timedelta(*args) # this is larger than t1
|
|
self.failUnless(t1 < t2)
|
|
self.failUnless(t2 > t1)
|
|
self.failUnless(t1 <= t2)
|
|
self.failUnless(t2 >= t1)
|
|
self.failUnless(t1 != t2)
|
|
self.failUnless(t2 != t1)
|
|
self.failUnless(not t1 == t2)
|
|
self.failUnless(not t2 == t1)
|
|
self.failUnless(not t1 > t2)
|
|
self.failUnless(not t2 < t1)
|
|
self.failUnless(not t1 >= t2)
|
|
self.failUnless(not t2 <= t1)
|
|
self.assertEqual(cmp(t1, t2), -1)
|
|
self.assertEqual(cmp(t2, t1), 1)
|
|
|
|
for badarg in 10, 10L, 34.5, "abc", {}, [], ():
|
|
self.assertRaises(TypeError, lambda: t1 == badarg)
|
|
self.assertRaises(TypeError, lambda: t1 != badarg)
|
|
self.assertRaises(TypeError, lambda: t1 <= badarg)
|
|
self.assertRaises(TypeError, lambda: t1 < badarg)
|
|
self.assertRaises(TypeError, lambda: t1 > badarg)
|
|
self.assertRaises(TypeError, lambda: t1 >= badarg)
|
|
self.assertRaises(TypeError, lambda: badarg == t1)
|
|
self.assertRaises(TypeError, lambda: badarg != t1)
|
|
self.assertRaises(TypeError, lambda: badarg <= t1)
|
|
self.assertRaises(TypeError, lambda: badarg < t1)
|
|
self.assertRaises(TypeError, lambda: badarg > t1)
|
|
self.assertRaises(TypeError, lambda: badarg >= t1)
|
|
|
|
def test_str(self):
|
|
td = timedelta
|
|
eq = self.assertEqual
|
|
|
|
eq(str(td(1)), "1 day, 0:00:00")
|
|
eq(str(td(-1)), "-1 day, 0:00:00")
|
|
eq(str(td(2)), "2 days, 0:00:00")
|
|
eq(str(td(-2)), "-2 days, 0:00:00")
|
|
|
|
eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
|
|
eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
|
|
eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
|
|
"-210 days, 23:12:34")
|
|
|
|
eq(str(td(milliseconds=1)), "0:00:00.001000")
|
|
eq(str(td(microseconds=3)), "0:00:00.000003")
|
|
|
|
eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
|
|
microseconds=999999)),
|
|
"999999999 days, 23:59:59.999999")
|
|
|
|
def test_roundtrip(self):
|
|
for td in (timedelta(days=999999999, hours=23, minutes=59,
|
|
seconds=59, microseconds=999999),
|
|
timedelta(days=-999999999),
|
|
timedelta(days=1, seconds=2, microseconds=3)):
|
|
|
|
# Verify td -> string -> td identity.
|
|
s = repr(td)
|
|
self.failUnless(s.startswith('datetime.'))
|
|
s = s[9:]
|
|
td2 = eval(s)
|
|
self.assertEqual(td, td2)
|
|
|
|
# Verify identity via reconstructing from pieces.
|
|
td2 = timedelta(td.days, td.seconds, td.microseconds)
|
|
self.assertEqual(td, td2)
|
|
|
|
def test_resolution_info(self):
|
|
self.assert_(isinstance(timedelta.min, timedelta))
|
|
self.assert_(isinstance(timedelta.max, timedelta))
|
|
self.assert_(isinstance(timedelta.resolution, timedelta))
|
|
self.assert_(timedelta.max > timedelta.min)
|
|
self.assertEqual(timedelta.min, timedelta(-999999999))
|
|
self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
|
|
self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
|
|
|
|
def test_overflow(self):
|
|
tiny = timedelta.resolution
|
|
|
|
td = timedelta.min + tiny
|
|
td -= tiny # no problem
|
|
self.assertRaises(OverflowError, td.__sub__, tiny)
|
|
self.assertRaises(OverflowError, td.__add__, -tiny)
|
|
|
|
td = timedelta.max - tiny
|
|
td += tiny # no problem
|
|
self.assertRaises(OverflowError, td.__add__, tiny)
|
|
self.assertRaises(OverflowError, td.__sub__, -tiny)
|
|
|
|
self.assertRaises(OverflowError, lambda: -timedelta.max)
|
|
|
|
def test_microsecond_rounding(self):
|
|
td = timedelta
|
|
eq = self.assertEqual
|
|
|
|
# Single-field rounding.
|
|
eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
|
|
eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
|
|
eq(td(milliseconds=0.6/1000), td(microseconds=1))
|
|
eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
|
|
|
|
# Rounding due to contributions from more than one field.
|
|
us_per_hour = 3600e6
|
|
us_per_day = us_per_hour * 24
|
|
eq(td(days=.4/us_per_day), td(0))
|
|
eq(td(hours=.2/us_per_hour), td(0))
|
|
eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
|
|
|
|
eq(td(days=-.4/us_per_day), td(0))
|
|
eq(td(hours=-.2/us_per_hour), td(0))
|
|
eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
|
|
|
|
def test_massive_normalization(self):
|
|
td = timedelta(microseconds=-1)
|
|
self.assertEqual((td.days, td.seconds, td.microseconds),
|
|
(-1, 24*3600-1, 999999))
|
|
|
|
def test_bool(self):
|
|
self.failUnless(timedelta(1))
|
|
self.failUnless(timedelta(0, 1))
|
|
self.failUnless(timedelta(0, 0, 1))
|
|
self.failUnless(timedelta(microseconds=1))
|
|
self.failUnless(not timedelta(0))
|
|
|
|
#############################################################################
|
|
# date tests
|
|
|
|
class TestDateOnly(unittest.TestCase):
|
|
# Tests here won't pass if also run on datetime objects, so don't
|
|
# subclass this to test datetimes too.
|
|
|
|
def test_delta_non_days_ignored(self):
|
|
dt = date(2000, 1, 2)
|
|
delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
|
|
microseconds=5)
|
|
days = timedelta(delta.days)
|
|
self.assertEqual(days, timedelta(1))
|
|
|
|
dt2 = dt + delta
|
|
self.assertEqual(dt2, dt + days)
|
|
|
|
dt2 = delta + dt
|
|
self.assertEqual(dt2, dt + days)
|
|
|
|
dt2 = dt - delta
|
|
self.assertEqual(dt2, dt - days)
|
|
|
|
delta = -delta
|
|
days = timedelta(delta.days)
|
|
self.assertEqual(days, timedelta(-2))
|
|
|
|
dt2 = dt + delta
|
|
self.assertEqual(dt2, dt + days)
|
|
|
|
dt2 = delta + dt
|
|
self.assertEqual(dt2, dt + days)
|
|
|
|
dt2 = dt - delta
|
|
self.assertEqual(dt2, dt - days)
|
|
|
|
class TestDate(unittest.TestCase):
|
|
# Tests here should pass for both dates and datetimes, except for a
|
|
# few tests that TestDateTime overrides.
|
|
|
|
theclass = date
|
|
|
|
def test_basic_attributes(self):
|
|
dt = self.theclass(2002, 3, 1)
|
|
self.assertEqual(dt.year, 2002)
|
|
self.assertEqual(dt.month, 3)
|
|
self.assertEqual(dt.day, 1)
|
|
|
|
def test_roundtrip(self):
|
|
for dt in (self.theclass(1, 2, 3),
|
|
self.theclass.today()):
|
|
# Verify dt -> string -> date identity.
|
|
s = repr(dt)
|
|
self.failUnless(s.startswith('datetime.'))
|
|
s = s[9:]
|
|
dt2 = eval(s)
|
|
self.assertEqual(dt, dt2)
|
|
|
|
# Verify identity via reconstructing from pieces.
|
|
dt2 = self.theclass(dt.year, dt.month, dt.day)
|
|
self.assertEqual(dt, dt2)
|
|
|
|
def test_ordinal_conversions(self):
|
|
# Check some fixed values.
|
|
for y, m, d, n in [(1, 1, 1, 1), # calendar origin
|
|
(1, 12, 31, 365),
|
|
(2, 1, 1, 366),
|
|
# first example from "Calendrical Calculations"
|
|
(1945, 11, 12, 710347)]:
|
|
d = self.theclass(y, m, d)
|
|
self.assertEqual(n, d.toordinal())
|
|
fromord = self.theclass.fromordinal(n)
|
|
self.assertEqual(d, fromord)
|
|
if hasattr(fromord, "hour"):
|
|
# if we're checking something fancier than a date, verify
|
|
# the extra fields have been zeroed out
|
|
self.assertEqual(fromord.hour, 0)
|
|
self.assertEqual(fromord.minute, 0)
|
|
self.assertEqual(fromord.second, 0)
|
|
self.assertEqual(fromord.microsecond, 0)
|
|
|
|
# Check first and last days of year across the whole range of years
|
|
# supported.
|
|
ordinal = 1
|
|
for year in xrange(MINYEAR, MAXYEAR+1):
|
|
# Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
|
|
d = self.theclass(year, 1, 1)
|
|
n = d.toordinal()
|
|
self.assertEqual(ordinal, n)
|
|
d2 = self.theclass.fromordinal(n)
|
|
self.assertEqual(d, d2)
|
|
self.assertEqual(d.timetuple().tm_yday, 1)
|
|
# Same for (year, 12, 31).
|
|
isleap = year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
|
|
days_in_year = 365 + isleap
|
|
d = self.theclass(year, 12, 31)
|
|
n = d.toordinal()
|
|
self.assertEqual(n, ordinal + days_in_year - 1)
|
|
self.assertEqual(d.timetuple().tm_yday, days_in_year)
|
|
d2 = self.theclass.fromordinal(n)
|
|
self.assertEqual(d, d2)
|
|
ordinal += days_in_year
|
|
|
|
# Test every day in a leap-year and a non-leap year.
|
|
dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
|
for year, isleap in (2000, True), (2002, False):
|
|
n = self.theclass(year, 1, 1).toordinal()
|
|
for month, maxday in zip(range(1, 13), dim):
|
|
if month == 2 and isleap:
|
|
maxday += 1
|
|
for day in range(1, maxday+1):
|
|
d = self.theclass(year, month, day)
|
|
self.assertEqual(d.toordinal(), n)
|
|
self.assertEqual(d, self.theclass.fromordinal(n))
|
|
n += 1
|
|
|
|
def test_extreme_ordinals(self):
|
|
a = self.theclass.min
|
|
a = self.theclass(a.year, a.month, a.day) # get rid of time parts
|
|
aord = a.toordinal()
|
|
b = a.fromordinal(aord)
|
|
self.assertEqual(a, b)
|
|
|
|
self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
|
|
|
|
b = a + timedelta(days=1)
|
|
self.assertEqual(b.toordinal(), aord + 1)
|
|
self.assertEqual(b, self.theclass.fromordinal(aord + 1))
|
|
|
|
a = self.theclass.max
|
|
a = self.theclass(a.year, a.month, a.day) # get rid of time parts
|
|
aord = a.toordinal()
|
|
b = a.fromordinal(aord)
|
|
self.assertEqual(a, b)
|
|
|
|
self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
|
|
|
|
b = a - timedelta(days=1)
|
|
self.assertEqual(b.toordinal(), aord - 1)
|
|
self.assertEqual(b, self.theclass.fromordinal(aord - 1))
|
|
|
|
def test_bad_constructor_arguments(self):
|
|
# bad years
|
|
self.theclass(MINYEAR, 1, 1) # no exception
|
|
self.theclass(MAXYEAR, 1, 1) # no exception
|
|
self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
|
|
self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
|
|
# bad months
|
|
self.theclass(2000, 1, 1) # no exception
|
|
self.theclass(2000, 12, 1) # no exception
|
|
self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
|
|
self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
|
|
# bad days
|
|
self.theclass(2000, 2, 29) # no exception
|
|
self.theclass(2004, 2, 29) # no exception
|
|
self.theclass(2400, 2, 29) # no exception
|
|
self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
|
|
self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
|
|
self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
|
|
self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
|
|
self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
|
|
self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
|
|
|
|
def test_hash_equality(self):
|
|
d = self.theclass(2000, 12, 31)
|
|
# same thing
|
|
e = self.theclass(2000, 12, 31)
|
|
self.assertEqual(d, e)
|
|
self.assertEqual(hash(d), hash(e))
|
|
|
|
dic = {d: 1}
|
|
dic[e] = 2
|
|
self.assertEqual(len(dic), 1)
|
|
self.assertEqual(dic[d], 2)
|
|
self.assertEqual(dic[e], 2)
|
|
|
|
d = self.theclass(2001, 1, 1)
|
|
# same thing
|
|
e = self.theclass(2001, 1, 1)
|
|
self.assertEqual(d, e)
|
|
self.assertEqual(hash(d), hash(e))
|
|
|
|
dic = {d: 1}
|
|
dic[e] = 2
|
|
self.assertEqual(len(dic), 1)
|
|
self.assertEqual(dic[d], 2)
|
|
self.assertEqual(dic[e], 2)
|
|
|
|
def test_computations(self):
|
|
a = self.theclass(2002, 1, 31)
|
|
b = self.theclass(1956, 1, 31)
|
|
|
|
diff = a-b
|
|
self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
|
|
self.assertEqual(diff.seconds, 0)
|
|
self.assertEqual(diff.microseconds, 0)
|
|
|
|
day = timedelta(1)
|
|
week = timedelta(7)
|
|
a = self.theclass(2002, 3, 2)
|
|
self.assertEqual(a + day, self.theclass(2002, 3, 3))
|
|
self.assertEqual(day + a, self.theclass(2002, 3, 3))
|
|
self.assertEqual(a - day, self.theclass(2002, 3, 1))
|
|
self.assertEqual(-day + a, self.theclass(2002, 3, 1))
|
|
self.assertEqual(a + week, self.theclass(2002, 3, 9))
|
|
self.assertEqual(a - week, self.theclass(2002, 2, 23))
|
|
self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
|
|
self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
|
|
self.assertEqual((a + week) - a, week)
|
|
self.assertEqual((a + day) - a, day)
|
|
self.assertEqual((a - week) - a, -week)
|
|
self.assertEqual((a - day) - a, -day)
|
|
self.assertEqual(a - (a + week), -week)
|
|
self.assertEqual(a - (a + day), -day)
|
|
self.assertEqual(a - (a - week), week)
|
|
self.assertEqual(a - (a - day), day)
|
|
|
|
# Add/sub ints, longs, floats should be illegal
|
|
for i in 1, 1L, 1.0:
|
|
self.assertRaises(TypeError, lambda: a+i)
|
|
self.assertRaises(TypeError, lambda: a-i)
|
|
self.assertRaises(TypeError, lambda: i+a)
|
|
self.assertRaises(TypeError, lambda: i-a)
|
|
|
|
# delta - date is senseless.
|
|
self.assertRaises(TypeError, lambda: day - a)
|
|
# mixing date and (delta or date) via * or // is senseless
|
|
self.assertRaises(TypeError, lambda: day * a)
|
|
self.assertRaises(TypeError, lambda: a * day)
|
|
self.assertRaises(TypeError, lambda: day // a)
|
|
self.assertRaises(TypeError, lambda: a // day)
|
|
self.assertRaises(TypeError, lambda: a * a)
|
|
self.assertRaises(TypeError, lambda: a // a)
|
|
# date + date is senseless
|
|
self.assertRaises(TypeError, lambda: a + a)
|
|
|
|
def test_overflow(self):
|
|
tiny = self.theclass.resolution
|
|
|
|
dt = self.theclass.min + tiny
|
|
dt -= tiny # no problem
|
|
self.assertRaises(OverflowError, dt.__sub__, tiny)
|
|
self.assertRaises(OverflowError, dt.__add__, -tiny)
|
|
|
|
dt = self.theclass.max - tiny
|
|
dt += tiny # no problem
|
|
self.assertRaises(OverflowError, dt.__add__, tiny)
|
|
self.assertRaises(OverflowError, dt.__sub__, -tiny)
|
|
|
|
def test_fromtimestamp(self):
|
|
import time
|
|
|
|
# Try an arbitrary fixed value.
|
|
year, month, day = 1999, 9, 19
|
|
ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
|
|
d = self.theclass.fromtimestamp(ts)
|
|
self.assertEqual(d.year, year)
|
|
self.assertEqual(d.month, month)
|
|
self.assertEqual(d.day, day)
|
|
|
|
def test_today(self):
|
|
import time
|
|
|
|
# We claim that today() is like fromtimestamp(time.time()), so
|
|
# prove it.
|
|
for dummy in range(3):
|
|
today = self.theclass.today()
|
|
ts = time.time()
|
|
todayagain = self.theclass.fromtimestamp(ts)
|
|
if today == todayagain:
|
|
break
|
|
# There are several legit reasons that could fail:
|
|
# 1. It recently became midnight, between the today() and the
|
|
# time() calls.
|
|
# 2. The platform time() has such fine resolution that we'll
|
|
# never get the same value twice.
|
|
# 3. The platform time() has poor resolution, and we just
|
|
# happened to call today() right before a resolution quantum
|
|
# boundary.
|
|
# 4. The system clock got fiddled between calls.
|
|
# In any case, wait a little while and try again.
|
|
time.sleep(0.1)
|
|
|
|
# It worked or it didn't. If it didn't, assume it's reason #2, and
|
|
# let the test pass if they're within half a second of each other.
|
|
self.failUnless(today == todayagain or
|
|
abs(todayagain - today) < timedelta(seconds=0.5))
|
|
|
|
def test_weekday(self):
|
|
for i in range(7):
|
|
# March 4, 2002 is a Monday
|
|
self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
|
|
self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
|
|
# January 2, 1956 is a Monday
|
|
self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
|
|
self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
|
|
|
|
def test_isocalendar(self):
|
|
# Check examples from
|
|
# http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
|
|
for i in range(7):
|
|
d = self.theclass(2003, 12, 22+i)
|
|
self.assertEqual(d.isocalendar(), (2003, 52, i+1))
|
|
d = self.theclass(2003, 12, 29) + timedelta(i)
|
|
self.assertEqual(d.isocalendar(), (2004, 1, i+1))
|
|
d = self.theclass(2004, 1, 5+i)
|
|
self.assertEqual(d.isocalendar(), (2004, 2, i+1))
|
|
d = self.theclass(2009, 12, 21+i)
|
|
self.assertEqual(d.isocalendar(), (2009, 52, i+1))
|
|
d = self.theclass(2009, 12, 28) + timedelta(i)
|
|
self.assertEqual(d.isocalendar(), (2009, 53, i+1))
|
|
d = self.theclass(2010, 1, 4+i)
|
|
self.assertEqual(d.isocalendar(), (2010, 1, i+1))
|
|
|
|
def test_iso_long_years(self):
|
|
# Calculate long ISO years and compare to table from
|
|
# http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
|
|
ISO_LONG_YEARS_TABLE = """
|
|
4 32 60 88
|
|
9 37 65 93
|
|
15 43 71 99
|
|
20 48 76
|
|
26 54 82
|
|
|
|
105 133 161 189
|
|
111 139 167 195
|
|
116 144 172
|
|
122 150 178
|
|
128 156 184
|
|
|
|
201 229 257 285
|
|
207 235 263 291
|
|
212 240 268 296
|
|
218 246 274
|
|
224 252 280
|
|
|
|
303 331 359 387
|
|
308 336 364 392
|
|
314 342 370 398
|
|
320 348 376
|
|
325 353 381
|
|
"""
|
|
iso_long_years = map(int, ISO_LONG_YEARS_TABLE.split())
|
|
iso_long_years.sort()
|
|
L = []
|
|
for i in range(400):
|
|
d = self.theclass(2000+i, 12, 31)
|
|
d1 = self.theclass(1600+i, 12, 31)
|
|
self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
|
|
if d.isocalendar()[1] == 53:
|
|
L.append(i)
|
|
self.assertEqual(L, iso_long_years)
|
|
|
|
def test_isoformat(self):
|
|
t = self.theclass(2, 3, 2)
|
|
self.assertEqual(t.isoformat(), "0002-03-02")
|
|
|
|
def test_ctime(self):
|
|
t = self.theclass(2002, 3, 2)
|
|
self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
|
|
|
|
def test_strftime(self):
|
|
t = self.theclass(2005, 3, 2)
|
|
self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
|
|
|
|
self.assertRaises(TypeError, t.strftime) # needs an arg
|
|
self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
|
|
self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
|
|
|
|
# A naive object replaces %z and %Z w/ empty strings.
|
|
self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
|
|
|
|
def test_resolution_info(self):
|
|
self.assert_(isinstance(self.theclass.min, self.theclass))
|
|
self.assert_(isinstance(self.theclass.max, self.theclass))
|
|
self.assert_(isinstance(self.theclass.resolution, timedelta))
|
|
self.assert_(self.theclass.max > self.theclass.min)
|
|
|
|
def test_extreme_timedelta(self):
|
|
big = self.theclass.max - self.theclass.min
|
|
# 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
|
|
n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
|
|
# n == 315537897599999999 ~= 2**58.13
|
|
justasbig = timedelta(0, 0, n)
|
|
self.assertEqual(big, justasbig)
|
|
self.assertEqual(self.theclass.min + big, self.theclass.max)
|
|
self.assertEqual(self.theclass.max - big, self.theclass.min)
|
|
|
|
def test_timetuple(self):
|
|
for i in range(7):
|
|
# January 2, 1956 is a Monday (0)
|
|
d = self.theclass(1956, 1, 2+i)
|
|
t = d.timetuple()
|
|
self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
|
|
# February 1, 1956 is a Wednesday (2)
|
|
d = self.theclass(1956, 2, 1+i)
|
|
t = d.timetuple()
|
|
self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
|
|
# March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
|
|
# of the year.
|
|
d = self.theclass(1956, 3, 1+i)
|
|
t = d.timetuple()
|
|
self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
|
|
self.assertEqual(t.tm_year, 1956)
|
|
self.assertEqual(t.tm_mon, 3)
|
|
self.assertEqual(t.tm_mday, 1+i)
|
|
self.assertEqual(t.tm_hour, 0)
|
|
self.assertEqual(t.tm_min, 0)
|
|
self.assertEqual(t.tm_sec, 0)
|
|
self.assertEqual(t.tm_wday, (3+i)%7)
|
|
self.assertEqual(t.tm_yday, 61+i)
|
|
self.assertEqual(t.tm_isdst, -1)
|
|
|
|
def test_pickling(self):
|
|
import pickle, cPickle
|
|
args = 6, 7, 23
|
|
orig = self.theclass(*args)
|
|
state = orig.__getstate__()
|
|
self.assertEqual(state, '\x00\x06\x07\x17')
|
|
derived = self.theclass(1, 1, 1)
|
|
derived.__setstate__(state)
|
|
self.assertEqual(orig, derived)
|
|
for pickler in pickle, cPickle:
|
|
for binary in 0, 1:
|
|
green = pickler.dumps(orig, binary)
|
|
derived = pickler.loads(green)
|
|
self.assertEqual(orig, derived)
|
|
|
|
def test_compare(self):
|
|
t1 = self.theclass(2, 3, 4)
|
|
t2 = self.theclass(2, 3, 4)
|
|
self.failUnless(t1 == t2)
|
|
self.failUnless(t1 <= t2)
|
|
self.failUnless(t1 >= t2)
|
|
self.failUnless(not t1 != t2)
|
|
self.failUnless(not t1 < t2)
|
|
self.failUnless(not t1 > t2)
|
|
self.assertEqual(cmp(t1, t2), 0)
|
|
self.assertEqual(cmp(t2, t1), 0)
|
|
|
|
for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
|
|
t2 = self.theclass(*args) # this is larger than t1
|
|
self.failUnless(t1 < t2)
|
|
self.failUnless(t2 > t1)
|
|
self.failUnless(t1 <= t2)
|
|
self.failUnless(t2 >= t1)
|
|
self.failUnless(t1 != t2)
|
|
self.failUnless(t2 != t1)
|
|
self.failUnless(not t1 == t2)
|
|
self.failUnless(not t2 == t1)
|
|
self.failUnless(not t1 > t2)
|
|
self.failUnless(not t2 < t1)
|
|
self.failUnless(not t1 >= t2)
|
|
self.failUnless(not t2 <= t1)
|
|
self.assertEqual(cmp(t1, t2), -1)
|
|
self.assertEqual(cmp(t2, t1), 1)
|
|
|
|
for badarg in 10, 10L, 34.5, "abc", {}, [], ():
|
|
self.assertRaises(TypeError, lambda: t1 == badarg)
|
|
self.assertRaises(TypeError, lambda: t1 != badarg)
|
|
self.assertRaises(TypeError, lambda: t1 <= badarg)
|
|
self.assertRaises(TypeError, lambda: t1 < badarg)
|
|
self.assertRaises(TypeError, lambda: t1 > badarg)
|
|
self.assertRaises(TypeError, lambda: t1 >= badarg)
|
|
self.assertRaises(TypeError, lambda: badarg == t1)
|
|
self.assertRaises(TypeError, lambda: badarg != t1)
|
|
self.assertRaises(TypeError, lambda: badarg <= t1)
|
|
self.assertRaises(TypeError, lambda: badarg < t1)
|
|
self.assertRaises(TypeError, lambda: badarg > t1)
|
|
self.assertRaises(TypeError, lambda: badarg >= t1)
|
|
|
|
def test_bool(self):
|
|
# All dates are considered true.
|
|
self.failUnless(self.theclass.min)
|
|
self.failUnless(self.theclass.max)
|
|
|
|
def test_srftime_out_of_range(self):
|
|
# For nasty technical reasons, we can't handle years before 1900.
|
|
cls = self.theclass
|
|
self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900")
|
|
for y in 1, 49, 51, 99, 100, 1000, 1899:
|
|
self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y")
|
|
|
|
def test_replace(self):
|
|
cls = self.theclass
|
|
args = [1, 2, 3]
|
|
base = cls(*args)
|
|
self.assertEqual(base, base.replace())
|
|
|
|
i = 0
|
|
for name, newval in (("year", 2),
|
|
("month", 3),
|
|
("day", 4)):
|
|
newargs = args[:]
|
|
newargs[i] = newval
|
|
expected = cls(*newargs)
|
|
got = base.replace(**{name: newval})
|
|
self.assertEqual(expected, got)
|
|
i += 1
|
|
|
|
# Out of bounds.
|
|
base = cls(2000, 2, 29)
|
|
self.assertRaises(ValueError, base.replace, year=2001)
|
|
|
|
#############################################################################
|
|
# datetime tests
|
|
|
|
class TestDateTime(TestDate):
|
|
|
|
theclass = datetime
|
|
|
|
def test_basic_attributes(self):
|
|
dt = self.theclass(2002, 3, 1, 12, 0)
|
|
self.assertEqual(dt.year, 2002)
|
|
self.assertEqual(dt.month, 3)
|
|
self.assertEqual(dt.day, 1)
|
|
self.assertEqual(dt.hour, 12)
|
|
self.assertEqual(dt.minute, 0)
|
|
self.assertEqual(dt.second, 0)
|
|
self.assertEqual(dt.microsecond, 0)
|
|
|
|
def test_basic_attributes_nonzero(self):
|
|
# Make sure all attributes are non-zero so bugs in
|
|
# bit-shifting access show up.
|
|
dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
|
|
self.assertEqual(dt.year, 2002)
|
|
self.assertEqual(dt.month, 3)
|
|
self.assertEqual(dt.day, 1)
|
|
self.assertEqual(dt.hour, 12)
|
|
self.assertEqual(dt.minute, 59)
|
|
self.assertEqual(dt.second, 59)
|
|
self.assertEqual(dt.microsecond, 8000)
|
|
|
|
def test_roundtrip(self):
|
|
for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
|
|
self.theclass.now()):
|
|
# Verify dt -> string -> datetime identity.
|
|
s = repr(dt)
|
|
self.failUnless(s.startswith('datetime.'))
|
|
s = s[9:]
|
|
dt2 = eval(s)
|
|
self.assertEqual(dt, dt2)
|
|
|
|
# Verify identity via reconstructing from pieces.
|
|
dt2 = self.theclass(dt.year, dt.month, dt.day,
|
|
dt.hour, dt.minute, dt.second,
|
|
dt.microsecond)
|
|
self.assertEqual(dt, dt2)
|
|
|
|
def test_isoformat(self):
|
|
t = self.theclass(2, 3, 2, 4, 5, 1, 123)
|
|
self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123")
|
|
self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
|
|
self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
|
|
# str is ISO format with the separator forced to a blank.
|
|
self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
|
|
|
|
t = self.theclass(2, 3, 2)
|
|
self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
|
|
self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
|
|
self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
|
|
# str is ISO format with the separator forced to a blank.
|
|
self.assertEqual(str(t), "0002-03-02 00:00:00")
|
|
|
|
def test_more_ctime(self):
|
|
# Test fields that TestDate doesn't touch.
|
|
import time
|
|
|
|
t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
|
|
self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
|
|
# Oops! The next line fails on Win2K under MSVC 6, so it's commented
|
|
# out. The difference is that t.ctime() produces " 2" for the day,
|
|
# but platform ctime() produces "02" for the day. According to
|
|
# C99, t.ctime() is correct here.
|
|
# self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
|
|
|
|
# So test a case where that difference doesn't matter.
|
|
t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
|
|
self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
|
|
|
|
def test_tz_independent_comparing(self):
|
|
dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
|
|
dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
|
|
dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
|
|
self.assertEqual(dt1, dt3)
|
|
self.assert_(dt2 > dt3)
|
|
|
|
# Make sure comparison doesn't forget microseconds, and isn't done
|
|
# via comparing a float timestamp (an IEEE double doesn't have enough
|
|
# precision to span microsecond resolution across years 1 thru 9999,
|
|
# so comparing via timestamp necessarily calls some distinct values
|
|
# equal).
|
|
dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
|
|
us = timedelta(microseconds=1)
|
|
dt2 = dt1 + us
|
|
self.assertEqual(dt2 - dt1, us)
|
|
self.assert_(dt1 < dt2)
|
|
|
|
def test_bad_constructor_arguments(self):
|
|
# bad years
|
|
self.theclass(MINYEAR, 1, 1) # no exception
|
|
self.theclass(MAXYEAR, 1, 1) # no exception
|
|
self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
|
|
self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
|
|
# bad months
|
|
self.theclass(2000, 1, 1) # no exception
|
|
self.theclass(2000, 12, 1) # no exception
|
|
self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
|
|
self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
|
|
# bad days
|
|
self.theclass(2000, 2, 29) # no exception
|
|
self.theclass(2004, 2, 29) # no exception
|
|
self.theclass(2400, 2, 29) # no exception
|
|
self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
|
|
self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
|
|
self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
|
|
self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
|
|
self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
|
|
self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
|
|
# bad hours
|
|
self.theclass(2000, 1, 31, 0) # no exception
|
|
self.theclass(2000, 1, 31, 23) # no exception
|
|
self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
|
|
self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
|
|
# bad minutes
|
|
self.theclass(2000, 1, 31, 23, 0) # no exception
|
|
self.theclass(2000, 1, 31, 23, 59) # no exception
|
|
self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
|
|
self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
|
|
# bad seconds
|
|
self.theclass(2000, 1, 31, 23, 59, 0) # no exception
|
|
self.theclass(2000, 1, 31, 23, 59, 59) # no exception
|
|
self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
|
|
self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
|
|
# bad microseconds
|
|
self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
|
|
self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
|
|
self.assertRaises(ValueError, self.theclass,
|
|
2000, 1, 31, 23, 59, 59, -1)
|
|
self.assertRaises(ValueError, self.theclass,
|
|
2000, 1, 31, 23, 59, 59,
|
|
1000000)
|
|
|
|
def test_hash_equality(self):
|
|
d = self.theclass(2000, 12, 31, 23, 30, 17)
|
|
e = self.theclass(2000, 12, 31, 23, 30, 17)
|
|
self.assertEqual(d, e)
|
|
self.assertEqual(hash(d), hash(e))
|
|
|
|
dic = {d: 1}
|
|
dic[e] = 2
|
|
self.assertEqual(len(dic), 1)
|
|
self.assertEqual(dic[d], 2)
|
|
self.assertEqual(dic[e], 2)
|
|
|
|
d = self.theclass(2001, 1, 1, 0, 5, 17)
|
|
e = self.theclass(2001, 1, 1, 0, 5, 17)
|
|
self.assertEqual(d, e)
|
|
self.assertEqual(hash(d), hash(e))
|
|
|
|
dic = {d: 1}
|
|
dic[e] = 2
|
|
self.assertEqual(len(dic), 1)
|
|
self.assertEqual(dic[d], 2)
|
|
self.assertEqual(dic[e], 2)
|
|
|
|
def test_computations(self):
|
|
a = self.theclass(2002, 1, 31)
|
|
b = self.theclass(1956, 1, 31)
|
|
diff = a-b
|
|
self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
|
|
self.assertEqual(diff.seconds, 0)
|
|
self.assertEqual(diff.microseconds, 0)
|
|
a = self.theclass(2002, 3, 2, 17, 6)
|
|
millisec = timedelta(0, 0, 1000)
|
|
hour = timedelta(0, 3600)
|
|
day = timedelta(1)
|
|
week = timedelta(7)
|
|
self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
|
|
self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
|
|
self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
|
|
self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
|
|
self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
|
|
self.assertEqual(a - hour, a + -hour)
|
|
self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
|
|
self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
|
|
self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
|
|
self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
|
|
self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
|
|
self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
|
|
self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
|
|
self.assertEqual((a + week) - a, week)
|
|
self.assertEqual((a + day) - a, day)
|
|
self.assertEqual((a + hour) - a, hour)
|
|
self.assertEqual((a + millisec) - a, millisec)
|
|
self.assertEqual((a - week) - a, -week)
|
|
self.assertEqual((a - day) - a, -day)
|
|
self.assertEqual((a - hour) - a, -hour)
|
|
self.assertEqual((a - millisec) - a, -millisec)
|
|
self.assertEqual(a - (a + week), -week)
|
|
self.assertEqual(a - (a + day), -day)
|
|
self.assertEqual(a - (a + hour), -hour)
|
|
self.assertEqual(a - (a + millisec), -millisec)
|
|
self.assertEqual(a - (a - week), week)
|
|
self.assertEqual(a - (a - day), day)
|
|
self.assertEqual(a - (a - hour), hour)
|
|
self.assertEqual(a - (a - millisec), millisec)
|
|
self.assertEqual(a + (week + day + hour + millisec),
|
|
self.theclass(2002, 3, 10, 18, 6, 0, 1000))
|
|
self.assertEqual(a + (week + day + hour + millisec),
|
|
(((a + week) + day) + hour) + millisec)
|
|
self.assertEqual(a - (week + day + hour + millisec),
|
|
self.theclass(2002, 2, 22, 16, 5, 59, 999000))
|
|
self.assertEqual(a - (week + day + hour + millisec),
|
|
(((a - week) - day) - hour) - millisec)
|
|
# Add/sub ints, longs, floats should be illegal
|
|
for i in 1, 1L, 1.0:
|
|
self.assertRaises(TypeError, lambda: a+i)
|
|
self.assertRaises(TypeError, lambda: a-i)
|
|
self.assertRaises(TypeError, lambda: i+a)
|
|
self.assertRaises(TypeError, lambda: i-a)
|
|
|
|
# delta - datetime is senseless.
|
|
self.assertRaises(TypeError, lambda: day - a)
|
|
# mixing datetime and (delta or datetime) via * or // is senseless
|
|
self.assertRaises(TypeError, lambda: day * a)
|
|
self.assertRaises(TypeError, lambda: a * day)
|
|
self.assertRaises(TypeError, lambda: day // a)
|
|
self.assertRaises(TypeError, lambda: a // day)
|
|
self.assertRaises(TypeError, lambda: a * a)
|
|
self.assertRaises(TypeError, lambda: a // a)
|
|
# datetime + datetime is senseless
|
|
self.assertRaises(TypeError, lambda: a + a)
|
|
|
|
def test_pickling(self):
|
|
import pickle, cPickle
|
|
args = 6, 7, 23, 20, 59, 1, 64**2
|
|
orig = self.theclass(*args)
|
|
state = orig.__getstate__()
|
|
self.assertEqual(state, '\x00\x06\x07\x17\x14\x3b\x01\x00\x10\x00')
|
|
derived = self.theclass(1, 1, 1)
|
|
derived.__setstate__(state)
|
|
self.assertEqual(orig, derived)
|
|
for pickler in pickle, cPickle:
|
|
for binary in 0, 1:
|
|
green = pickler.dumps(orig, binary)
|
|
derived = pickler.loads(green)
|
|
self.assertEqual(orig, derived)
|
|
|
|
def test_more_compare(self):
|
|
# The test_compare() inherited from TestDate covers the error cases.
|
|
# We just want to test lexicographic ordering on the members datetime
|
|
# has that date lacks.
|
|
args = [2000, 11, 29, 20, 58, 16, 999998]
|
|
t1 = self.theclass(*args)
|
|
t2 = self.theclass(*args)
|
|
self.failUnless(t1 == t2)
|
|
self.failUnless(t1 <= t2)
|
|
self.failUnless(t1 >= t2)
|
|
self.failUnless(not t1 != t2)
|
|
self.failUnless(not t1 < t2)
|
|
self.failUnless(not t1 > t2)
|
|
self.assertEqual(cmp(t1, t2), 0)
|
|
self.assertEqual(cmp(t2, t1), 0)
|
|
|
|
for i in range(len(args)):
|
|
newargs = args[:]
|
|
newargs[i] = args[i] + 1
|
|
t2 = self.theclass(*newargs) # this is larger than t1
|
|
self.failUnless(t1 < t2)
|
|
self.failUnless(t2 > t1)
|
|
self.failUnless(t1 <= t2)
|
|
self.failUnless(t2 >= t1)
|
|
self.failUnless(t1 != t2)
|
|
self.failUnless(t2 != t1)
|
|
self.failUnless(not t1 == t2)
|
|
self.failUnless(not t2 == t1)
|
|
self.failUnless(not t1 > t2)
|
|
self.failUnless(not t2 < t1)
|
|
self.failUnless(not t1 >= t2)
|
|
self.failUnless(not t2 <= t1)
|
|
self.assertEqual(cmp(t1, t2), -1)
|
|
self.assertEqual(cmp(t2, t1), 1)
|
|
|
|
|
|
# A helper for timestamp constructor tests.
|
|
def verify_field_equality(self, expected, got):
|
|
self.assertEqual(expected.tm_year, got.year)
|
|
self.assertEqual(expected.tm_mon, got.month)
|
|
self.assertEqual(expected.tm_mday, got.day)
|
|
self.assertEqual(expected.tm_hour, got.hour)
|
|
self.assertEqual(expected.tm_min, got.minute)
|
|
self.assertEqual(expected.tm_sec, got.second)
|
|
|
|
def test_fromtimestamp(self):
|
|
import time
|
|
|
|
ts = time.time()
|
|
expected = time.localtime(ts)
|
|
got = self.theclass.fromtimestamp(ts)
|
|
self.verify_field_equality(expected, got)
|
|
|
|
def test_utcfromtimestamp(self):
|
|
import time
|
|
|
|
ts = time.time()
|
|
expected = time.gmtime(ts)
|
|
got = self.theclass.utcfromtimestamp(ts)
|
|
self.verify_field_equality(expected, got)
|
|
|
|
def test_utcnow(self):
|
|
import time
|
|
|
|
# Call it a success if utcnow() and utcfromtimestamp() are within
|
|
# a second of each other.
|
|
tolerance = timedelta(seconds=1)
|
|
for dummy in range(3):
|
|
from_now = self.theclass.utcnow()
|
|
from_timestamp = self.theclass.utcfromtimestamp(time.time())
|
|
if abs(from_timestamp - from_now) <= tolerance:
|
|
break
|
|
# Else try again a few times.
|
|
self.failUnless(abs(from_timestamp - from_now) <= tolerance)
|
|
|
|
def test_more_timetuple(self):
|
|
# This tests fields beyond those tested by the TestDate.test_timetuple.
|
|
t = self.theclass(2004, 12, 31, 6, 22, 33)
|
|
self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
|
|
self.assertEqual(t.timetuple(),
|
|
(t.year, t.month, t.day,
|
|
t.hour, t.minute, t.second,
|
|
t.weekday(),
|
|
t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
|
|
-1))
|
|
tt = t.timetuple()
|
|
self.assertEqual(tt.tm_year, t.year)
|
|
self.assertEqual(tt.tm_mon, t.month)
|
|
self.assertEqual(tt.tm_mday, t.day)
|
|
self.assertEqual(tt.tm_hour, t.hour)
|
|
self.assertEqual(tt.tm_min, t.minute)
|
|
self.assertEqual(tt.tm_sec, t.second)
|
|
self.assertEqual(tt.tm_wday, t.weekday())
|
|
self.assertEqual(tt.tm_yday, t.toordinal() -
|
|
date(t.year, 1, 1).toordinal() + 1)
|
|
self.assertEqual(tt.tm_isdst, -1)
|
|
|
|
def test_more_strftime(self):
|
|
# This tests fields beyond those tested by the TestDate.test_strftime.
|
|
t = self.theclass(2004, 12, 31, 6, 22, 33)
|
|
self.assertEqual(t.strftime("%m %d %y %S %M %H %j"),
|
|
"12 31 04 33 22 06 366")
|
|
|
|
def test_extract(self):
|
|
dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
|
|
self.assertEqual(dt.date(), date(2002, 3, 4))
|
|
self.assertEqual(dt.time(), time(18, 45, 3, 1234))
|
|
|
|
def test_combine(self):
|
|
d = date(2002, 3, 4)
|
|
t = time(18, 45, 3, 1234)
|
|
expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
|
|
combine = self.theclass.combine
|
|
dt = combine(d, t)
|
|
self.assertEqual(dt, expected)
|
|
|
|
dt = combine(time=t, date=d)
|
|
self.assertEqual(dt, expected)
|
|
|
|
self.assertEqual(d, dt.date())
|
|
self.assertEqual(t, dt.time())
|
|
self.assertEqual(dt, combine(dt.date(), dt.time()))
|
|
|
|
self.assertRaises(TypeError, combine) # need an arg
|
|
self.assertRaises(TypeError, combine, d) # need two args
|
|
self.assertRaises(TypeError, combine, t, d) # args reversed
|
|
self.assertRaises(TypeError, combine, d, t, 1) # too many args
|
|
self.assertRaises(TypeError, combine, "date", "time") # wrong types
|
|
|
|
def test_replace(self):
|
|
cls = self.theclass
|
|
args = [1, 2, 3, 4, 5, 6, 7]
|
|
base = cls(*args)
|
|
self.assertEqual(base, base.replace())
|
|
|
|
i = 0
|
|
for name, newval in (("year", 2),
|
|
("month", 3),
|
|
("day", 4),
|
|
("hour", 5),
|
|
("minute", 6),
|
|
("second", 7),
|
|
("microsecond", 8)):
|
|
newargs = args[:]
|
|
newargs[i] = newval
|
|
expected = cls(*newargs)
|
|
got = base.replace(**{name: newval})
|
|
self.assertEqual(expected, got)
|
|
i += 1
|
|
|
|
# Out of bounds.
|
|
base = cls(2000, 2, 29)
|
|
self.assertRaises(ValueError, base.replace, year=2001)
|
|
|
|
def test_astimezone(self):
|
|
# Pretty boring for a datetime! datetimetz is more interesting here.
|
|
dt = self.theclass.now()
|
|
f = FixedOffset(44, "")
|
|
for dtz in dt.astimezone(f), dt.astimezone(tz=f):
|
|
self.failUnless(isinstance(dtz, datetimetz))
|
|
self.assertEqual(dt.date(), dtz.date())
|
|
self.assertEqual(dt.time(), dtz.time())
|
|
self.failUnless(dtz.tzinfo is f)
|
|
self.assertEqual(dtz.utcoffset(), timedelta(minutes=44))
|
|
|
|
self.assertRaises(TypeError, dt.astimezone) # not enough args
|
|
self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
|
|
self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
|
|
|
|
|
|
class TestTime(unittest.TestCase):
|
|
|
|
theclass = time
|
|
|
|
def test_basic_attributes(self):
|
|
t = self.theclass(12, 0)
|
|
self.assertEqual(t.hour, 12)
|
|
self.assertEqual(t.minute, 0)
|
|
self.assertEqual(t.second, 0)
|
|
self.assertEqual(t.microsecond, 0)
|
|
|
|
def test_basic_attributes_nonzero(self):
|
|
# Make sure all attributes are non-zero so bugs in
|
|
# bit-shifting access show up.
|
|
t = self.theclass(12, 59, 59, 8000)
|
|
self.assertEqual(t.hour, 12)
|
|
self.assertEqual(t.minute, 59)
|
|
self.assertEqual(t.second, 59)
|
|
self.assertEqual(t.microsecond, 8000)
|
|
|
|
def test_roundtrip(self):
|
|
t = self.theclass(1, 2, 3, 4)
|
|
|
|
# Verify t -> string -> time identity.
|
|
s = repr(t)
|
|
self.failUnless(s.startswith('datetime.'))
|
|
s = s[9:]
|
|
t2 = eval(s)
|
|
self.assertEqual(t, t2)
|
|
|
|
# Verify identity via reconstructing from pieces.
|
|
t2 = self.theclass(t.hour, t.minute, t.second,
|
|
t.microsecond)
|
|
self.assertEqual(t, t2)
|
|
|
|
def test_comparing(self):
|
|
args = [1, 2, 3, 4]
|
|
t1 = self.theclass(*args)
|
|
t2 = self.theclass(*args)
|
|
self.failUnless(t1 == t2)
|
|
self.failUnless(t1 <= t2)
|
|
self.failUnless(t1 >= t2)
|
|
self.failUnless(not t1 != t2)
|
|
self.failUnless(not t1 < t2)
|
|
self.failUnless(not t1 > t2)
|
|
self.assertEqual(cmp(t1, t2), 0)
|
|
self.assertEqual(cmp(t2, t1), 0)
|
|
|
|
for i in range(len(args)):
|
|
newargs = args[:]
|
|
newargs[i] = args[i] + 1
|
|
t2 = self.theclass(*newargs) # this is larger than t1
|
|
self.failUnless(t1 < t2)
|
|
self.failUnless(t2 > t1)
|
|
self.failUnless(t1 <= t2)
|
|
self.failUnless(t2 >= t1)
|
|
self.failUnless(t1 != t2)
|
|
self.failUnless(t2 != t1)
|
|
self.failUnless(not t1 == t2)
|
|
self.failUnless(not t2 == t1)
|
|
self.failUnless(not t1 > t2)
|
|
self.failUnless(not t2 < t1)
|
|
self.failUnless(not t1 >= t2)
|
|
self.failUnless(not t2 <= t1)
|
|
self.assertEqual(cmp(t1, t2), -1)
|
|
self.assertEqual(cmp(t2, t1), 1)
|
|
|
|
for badarg in (10, 10L, 34.5, "abc", {}, [], (), date(1, 1, 1),
|
|
datetime(1, 1, 1, 1, 1), timedelta(9)):
|
|
self.assertRaises(TypeError, lambda: t1 == badarg)
|
|
self.assertRaises(TypeError, lambda: t1 != badarg)
|
|
self.assertRaises(TypeError, lambda: t1 <= badarg)
|
|
self.assertRaises(TypeError, lambda: t1 < badarg)
|
|
self.assertRaises(TypeError, lambda: t1 > badarg)
|
|
self.assertRaises(TypeError, lambda: t1 >= badarg)
|
|
self.assertRaises(TypeError, lambda: badarg == t1)
|
|
self.assertRaises(TypeError, lambda: badarg != t1)
|
|
self.assertRaises(TypeError, lambda: badarg <= t1)
|
|
self.assertRaises(TypeError, lambda: badarg < t1)
|
|
self.assertRaises(TypeError, lambda: badarg > t1)
|
|
self.assertRaises(TypeError, lambda: badarg >= t1)
|
|
|
|
def test_bad_constructor_arguments(self):
|
|
# bad hours
|
|
self.theclass(0, 0) # no exception
|
|
self.theclass(23, 0) # no exception
|
|
self.assertRaises(ValueError, self.theclass, -1, 0)
|
|
self.assertRaises(ValueError, self.theclass, 24, 0)
|
|
# bad minutes
|
|
self.theclass(23, 0) # no exception
|
|
self.theclass(23, 59) # no exception
|
|
self.assertRaises(ValueError, self.theclass, 23, -1)
|
|
self.assertRaises(ValueError, self.theclass, 23, 60)
|
|
# bad seconds
|
|
self.theclass(23, 59, 0) # no exception
|
|
self.theclass(23, 59, 59) # no exception
|
|
self.assertRaises(ValueError, self.theclass, 23, 59, -1)
|
|
self.assertRaises(ValueError, self.theclass, 23, 59, 60)
|
|
# bad microseconds
|
|
self.theclass(23, 59, 59, 0) # no exception
|
|
self.theclass(23, 59, 59, 999999) # no exception
|
|
self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
|
|
self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
|
|
|
|
def test_hash_equality(self):
|
|
d = self.theclass(23, 30, 17)
|
|
e = self.theclass(23, 30, 17)
|
|
self.assertEqual(d, e)
|
|
self.assertEqual(hash(d), hash(e))
|
|
|
|
dic = {d: 1}
|
|
dic[e] = 2
|
|
self.assertEqual(len(dic), 1)
|
|
self.assertEqual(dic[d], 2)
|
|
self.assertEqual(dic[e], 2)
|
|
|
|
d = self.theclass(0, 5, 17)
|
|
e = self.theclass(0, 5, 17)
|
|
self.assertEqual(d, e)
|
|
self.assertEqual(hash(d), hash(e))
|
|
|
|
dic = {d: 1}
|
|
dic[e] = 2
|
|
self.assertEqual(len(dic), 1)
|
|
self.assertEqual(dic[d], 2)
|
|
self.assertEqual(dic[e], 2)
|
|
|
|
def test_isoformat(self):
|
|
t = self.theclass(4, 5, 1, 123)
|
|
self.assertEqual(t.isoformat(), "04:05:01.000123")
|
|
self.assertEqual(t.isoformat(), str(t))
|
|
|
|
t = self.theclass()
|
|
self.assertEqual(t.isoformat(), "00:00:00")
|
|
self.assertEqual(t.isoformat(), str(t))
|
|
|
|
t = self.theclass(microsecond=1)
|
|
self.assertEqual(t.isoformat(), "00:00:00.000001")
|
|
self.assertEqual(t.isoformat(), str(t))
|
|
|
|
t = self.theclass(microsecond=10)
|
|
self.assertEqual(t.isoformat(), "00:00:00.000010")
|
|
self.assertEqual(t.isoformat(), str(t))
|
|
|
|
t = self.theclass(microsecond=100)
|
|
self.assertEqual(t.isoformat(), "00:00:00.000100")
|
|
self.assertEqual(t.isoformat(), str(t))
|
|
|
|
t = self.theclass(microsecond=1000)
|
|
self.assertEqual(t.isoformat(), "00:00:00.001000")
|
|
self.assertEqual(t.isoformat(), str(t))
|
|
|
|
t = self.theclass(microsecond=10000)
|
|
self.assertEqual(t.isoformat(), "00:00:00.010000")
|
|
self.assertEqual(t.isoformat(), str(t))
|
|
|
|
t = self.theclass(microsecond=100000)
|
|
self.assertEqual(t.isoformat(), "00:00:00.100000")
|
|
self.assertEqual(t.isoformat(), str(t))
|
|
|
|
def test_strftime(self):
|
|
t = self.theclass(1, 2, 3, 4)
|
|
self.assertEqual(t.strftime('%H %M %S'), "01 02 03")
|
|
# A naive object replaces %z and %Z with empty strings.
|
|
self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
|
|
|
|
def test_str(self):
|
|
self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
|
|
self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
|
|
self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
|
|
self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
|
|
self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
|
|
|
|
def test_repr(self):
|
|
name = 'datetime.' + self.theclass.__name__
|
|
self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
|
|
"%s(1, 2, 3, 4)" % name)
|
|
self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
|
|
"%s(10, 2, 3, 4000)" % name)
|
|
self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
|
|
"%s(0, 2, 3, 400000)" % name)
|
|
self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
|
|
"%s(12, 2, 3)" % name)
|
|
self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
|
|
"%s(23, 15)" % name)
|
|
|
|
def test_resolution_info(self):
|
|
self.assert_(isinstance(self.theclass.min, self.theclass))
|
|
self.assert_(isinstance(self.theclass.max, self.theclass))
|
|
self.assert_(isinstance(self.theclass.resolution, timedelta))
|
|
self.assert_(self.theclass.max > self.theclass.min)
|
|
|
|
def test_pickling(self):
|
|
import pickle, cPickle
|
|
args = 20, 59, 16, 64**2
|
|
orig = self.theclass(*args)
|
|
state = orig.__getstate__()
|
|
self.assertEqual(state, '\x14\x3b\x10\x00\x10\x00')
|
|
derived = self.theclass()
|
|
derived.__setstate__(state)
|
|
self.assertEqual(orig, derived)
|
|
for pickler in pickle, cPickle:
|
|
for binary in 0, 1:
|
|
green = pickler.dumps(orig, binary)
|
|
derived = pickler.loads(green)
|
|
self.assertEqual(orig, derived)
|
|
|
|
def test_bool(self):
|
|
cls = self.theclass
|
|
self.failUnless(cls(1))
|
|
self.failUnless(cls(0, 1))
|
|
self.failUnless(cls(0, 0, 1))
|
|
self.failUnless(cls(0, 0, 0, 1))
|
|
self.failUnless(not cls(0))
|
|
self.failUnless(not cls())
|
|
|
|
def test_replace(self):
|
|
cls = self.theclass
|
|
args = [1, 2, 3, 4]
|
|
base = cls(*args)
|
|
self.assertEqual(base, base.replace())
|
|
|
|
i = 0
|
|
for name, newval in (("hour", 5),
|
|
("minute", 6),
|
|
("second", 7),
|
|
("microsecond", 8)):
|
|
newargs = args[:]
|
|
newargs[i] = newval
|
|
expected = cls(*newargs)
|
|
got = base.replace(**{name: newval})
|
|
self.assertEqual(expected, got)
|
|
i += 1
|
|
|
|
# Out of bounds.
|
|
base = cls(1)
|
|
self.assertRaises(ValueError, base.replace, hour=24)
|
|
self.assertRaises(ValueError, base.replace, minute=-1)
|
|
self.assertRaises(ValueError, base.replace, second=100)
|
|
self.assertRaises(ValueError, base.replace, microsecond=1000000)
|
|
|
|
# A mixin for classes with a tzinfo= argument. Subclasses must define
|
|
# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
|
|
# must be legit (which is true for timetz and datetimetz).
|
|
class TZInfoBase(unittest.TestCase):
|
|
|
|
def test_bad_tzinfo_classes(self):
|
|
cls = self.theclass
|
|
self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
|
|
|
|
class NiceTry(object):
|
|
def __init__(self): pass
|
|
def utcoffset(self, dt): pass
|
|
self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
|
|
|
|
class BetterTry(tzinfo):
|
|
def __init__(self): pass
|
|
def utcoffset(self, dt): pass
|
|
b = BetterTry()
|
|
t = cls(1, 1, 1, tzinfo=b)
|
|
self.failUnless(t.tzinfo is b)
|
|
|
|
def test_utc_offset_out_of_bounds(self):
|
|
class Edgy(tzinfo):
|
|
def __init__(self, offset):
|
|
self.offset = offset
|
|
def utcoffset(self, dt):
|
|
return self.offset
|
|
|
|
cls = self.theclass
|
|
for offset, legit in ((-1440, False),
|
|
(-1439, True),
|
|
(1439, True),
|
|
(1440, False)):
|
|
if cls is timetz:
|
|
t = cls(1, 2, 3, tzinfo=Edgy(offset))
|
|
elif cls is datetimetz:
|
|
t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
|
|
if legit:
|
|
aofs = abs(offset)
|
|
h, m = divmod(aofs, 60)
|
|
tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
|
|
if isinstance(t, datetimetz):
|
|
t = t.timetz()
|
|
self.assertEqual(str(t), "01:02:03" + tag)
|
|
else:
|
|
self.assertRaises(ValueError, str, t)
|
|
|
|
def test_tzinfo_classes(self):
|
|
cls = self.theclass
|
|
class C1(tzinfo):
|
|
def utcoffset(self, dt): return None
|
|
def dst(self, dt): return None
|
|
def tzname(self, dt): return None
|
|
for t in (cls(1, 1, 1),
|
|
cls(1, 1, 1, tzinfo=None),
|
|
cls(1, 1, 1, tzinfo=C1())):
|
|
self.failUnless(t.utcoffset() is None)
|
|
self.failUnless(t.dst() is None)
|
|
self.failUnless(t.tzname() is None)
|
|
|
|
class C2(tzinfo):
|
|
def utcoffset(self, dt): return -1439
|
|
def dst(self, dt): return 1439
|
|
def tzname(self, dt): return "aname"
|
|
class C3(tzinfo):
|
|
def utcoffset(self, dt): return timedelta(minutes=-1439)
|
|
def dst(self, dt): return timedelta(minutes=1439)
|
|
def tzname(self, dt): return "aname"
|
|
for t in cls(1, 1, 1, tzinfo=C2()), cls(1, 1, 1, tzinfo=C3()):
|
|
self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
|
|
self.assertEqual(t.dst(), timedelta(minutes=1439))
|
|
self.assertEqual(t.tzname(), "aname")
|
|
|
|
# Wrong types.
|
|
class C4(tzinfo):
|
|
def utcoffset(self, dt): return "aname"
|
|
def dst(self, dt): return ()
|
|
def tzname(self, dt): return 0
|
|
t = cls(1, 1, 1, tzinfo=C4())
|
|
self.assertRaises(TypeError, t.utcoffset)
|
|
self.assertRaises(TypeError, t.dst)
|
|
self.assertRaises(TypeError, t.tzname)
|
|
|
|
# Offset out of range.
|
|
class C5(tzinfo):
|
|
def utcoffset(self, dt): return -1440
|
|
def dst(self, dt): return 1440
|
|
class C6(tzinfo):
|
|
def utcoffset(self, dt): return timedelta(hours=-24)
|
|
def dst(self, dt): return timedelta(hours=24)
|
|
for t in cls(1, 1, 1, tzinfo=C5()), cls(1, 1, 1, tzinfo=C6()):
|
|
self.assertRaises(ValueError, t.utcoffset)
|
|
self.assertRaises(ValueError, t.dst)
|
|
|
|
# Not a whole number of minutes.
|
|
class C7(tzinfo):
|
|
def utcoffset(self, dt): return timedelta(seconds=61)
|
|
def dst(self, dt): return timedelta(microseconds=-81)
|
|
t = cls(1, 1, 1, tzinfo=C7())
|
|
self.assertRaises(ValueError, t.utcoffset)
|
|
self.assertRaises(ValueError, t.dst)
|
|
|
|
def test_aware_compare(self):
|
|
cls = self.theclass
|
|
|
|
# Primarily trying to ensure that utcoffset() gets called even if
|
|
# the comparands have the same tzinfo member. timetz comparison
|
|
# didn't used to do so, although datetimetz comparison did.
|
|
class OperandDependentOffset(tzinfo):
|
|
def utcoffset(self, t):
|
|
if t.minute < 10:
|
|
return t.minute # d0 and d1 equal after adjustment
|
|
else:
|
|
return 59 # d2 off in the weeds
|
|
|
|
base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
|
|
d0 = base.replace(minute=3)
|
|
d1 = base.replace(minute=9)
|
|
d2 = base.replace(minute=11)
|
|
for x in d0, d1, d2:
|
|
for y in d0, d1, d2:
|
|
got = cmp(x, y)
|
|
if (x is d0 or x is d1) and (y is d0 or y is d1):
|
|
expected = 0
|
|
elif x is y is d2:
|
|
expected = 0
|
|
elif x is d2:
|
|
expected = -1
|
|
else:
|
|
assert y is d2
|
|
expected = 1
|
|
self.assertEqual(got, expected)
|
|
|
|
|
|
class TestTimeTZ(TestTime, TZInfoBase):
|
|
theclass = timetz
|
|
|
|
def test_empty(self):
|
|
t = self.theclass()
|
|
self.assertEqual(t.hour, 0)
|
|
self.assertEqual(t.minute, 0)
|
|
self.assertEqual(t.second, 0)
|
|
self.assertEqual(t.microsecond, 0)
|
|
self.failUnless(t.tzinfo is None)
|
|
|
|
def test_zones(self):
|
|
est = FixedOffset(-300, "EST", 1)
|
|
utc = FixedOffset(0, "UTC", -2)
|
|
met = FixedOffset(60, "MET", 3)
|
|
t1 = timetz( 7, 47, tzinfo=est)
|
|
t2 = timetz(12, 47, tzinfo=utc)
|
|
t3 = timetz(13, 47, tzinfo=met)
|
|
t4 = timetz(microsecond=40)
|
|
t5 = timetz(microsecond=40, tzinfo=utc)
|
|
|
|
self.assertEqual(t1.tzinfo, est)
|
|
self.assertEqual(t2.tzinfo, utc)
|
|
self.assertEqual(t3.tzinfo, met)
|
|
self.failUnless(t4.tzinfo is None)
|
|
self.assertEqual(t5.tzinfo, utc)
|
|
|
|
self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
|
|
self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
|
|
self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
|
|
self.failUnless(t4.utcoffset() is None)
|
|
self.assertRaises(TypeError, t1.utcoffset, "no args")
|
|
|
|
self.assertEqual(t1.tzname(), "EST")
|
|
self.assertEqual(t2.tzname(), "UTC")
|
|
self.assertEqual(t3.tzname(), "MET")
|
|
self.failUnless(t4.tzname() is None)
|
|
self.assertRaises(TypeError, t1.tzname, "no args")
|
|
|
|
self.assertEqual(t1.dst(), timedelta(minutes=1))
|
|
self.assertEqual(t2.dst(), timedelta(minutes=-2))
|
|
self.assertEqual(t3.dst(), timedelta(minutes=3))
|
|
self.failUnless(t4.dst() is None)
|
|
self.assertRaises(TypeError, t1.dst, "no args")
|
|
|
|
self.assertEqual(hash(t1), hash(t2))
|
|
self.assertEqual(hash(t1), hash(t3))
|
|
self.assertEqual(hash(t2), hash(t3))
|
|
|
|
self.assertEqual(t1, t2)
|
|
self.assertEqual(t1, t3)
|
|
self.assertEqual(t2, t3)
|
|
self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
|
|
self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
|
|
self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
|
|
|
|
self.assertEqual(str(t1), "07:47:00-05:00")
|
|
self.assertEqual(str(t2), "12:47:00+00:00")
|
|
self.assertEqual(str(t3), "13:47:00+01:00")
|
|
self.assertEqual(str(t4), "00:00:00.000040")
|
|
self.assertEqual(str(t5), "00:00:00.000040+00:00")
|
|
|
|
self.assertEqual(t1.isoformat(), "07:47:00-05:00")
|
|
self.assertEqual(t2.isoformat(), "12:47:00+00:00")
|
|
self.assertEqual(t3.isoformat(), "13:47:00+01:00")
|
|
self.assertEqual(t4.isoformat(), "00:00:00.000040")
|
|
self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
|
|
|
|
d = 'datetime.timetz'
|
|
self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
|
|
self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
|
|
self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
|
|
self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
|
|
self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
|
|
|
|
self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
|
|
"07:47:00 %Z=EST %z=-0500")
|
|
self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
|
|
self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
|
|
|
|
yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
|
|
t1 = timetz(23, 59, tzinfo=yuck)
|
|
self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
|
|
"23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
|
|
|
|
# Check that an invalid tzname result raises an exception.
|
|
class Badtzname(tzinfo):
|
|
def tzname(self, dt): return 42
|
|
t = timetz(2, 3, 4, tzinfo=Badtzname())
|
|
self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
|
|
self.assertRaises(TypeError, t.strftime, "%Z")
|
|
|
|
def test_hash_edge_cases(self):
|
|
# Offsets that overflow a basic time.
|
|
t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
|
|
t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
|
|
self.assertEqual(hash(t1), hash(t2))
|
|
|
|
t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
|
|
t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
|
|
self.assertEqual(hash(t1), hash(t2))
|
|
|
|
def test_pickling(self):
|
|
import pickle, cPickle
|
|
|
|
# Try one without a tzinfo.
|
|
args = 20, 59, 16, 64**2
|
|
orig = self.theclass(*args)
|
|
state = orig.__getstate__()
|
|
self.assertEqual(state, ('\x14\x3b\x10\x00\x10\x00',))
|
|
derived = self.theclass()
|
|
derived.__setstate__(state)
|
|
self.assertEqual(orig, derived)
|
|
for pickler in pickle, cPickle:
|
|
for binary in 0, 1:
|
|
green = pickler.dumps(orig, binary)
|
|
derived = pickler.loads(green)
|
|
self.assertEqual(orig, derived)
|
|
|
|
# Try one with a tzinfo.
|
|
tinfo = PicklableFixedOffset(-300, 'cookie')
|
|
orig = self.theclass(5, 6, 7, tzinfo=tinfo)
|
|
state = orig.__getstate__()
|
|
derived = self.theclass()
|
|
derived.__setstate__(state)
|
|
self.assertEqual(orig, derived)
|
|
self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
|
|
self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
|
|
self.assertEqual(derived.tzname(), 'cookie')
|
|
|
|
for pickler in pickle, cPickle:
|
|
for binary in 0, 1:
|
|
green = pickler.dumps(orig, binary)
|
|
derived = pickler.loads(green)
|
|
self.assertEqual(orig, derived)
|
|
self.failUnless(isinstance(derived.tzinfo,
|
|
PicklableFixedOffset))
|
|
self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
|
|
self.assertEqual(derived.tzname(), 'cookie')
|
|
|
|
def test_more_bool(self):
|
|
# Test cases with non-None tzinfo.
|
|
cls = self.theclass
|
|
|
|
t = cls(0, tzinfo=FixedOffset(-300, ""))
|
|
self.failUnless(t)
|
|
|
|
t = cls(5, tzinfo=FixedOffset(-300, ""))
|
|
self.failUnless(t)
|
|
|
|
t = cls(5, tzinfo=FixedOffset(300, ""))
|
|
self.failUnless(not t)
|
|
|
|
t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
|
|
self.failUnless(not t)
|
|
|
|
# Mostly ensuring this doesn't overflow internally.
|
|
t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
|
|
self.failUnless(t)
|
|
|
|
# But this should yield a value error -- the utcoffset is bogus.
|
|
t = cls(0, tzinfo=FixedOffset(24*60, ""))
|
|
self.assertRaises(ValueError, lambda: bool(t))
|
|
|
|
# Likewise.
|
|
t = cls(0, tzinfo=FixedOffset(-24*60, ""))
|
|
self.assertRaises(ValueError, lambda: bool(t))
|
|
|
|
def test_replace(self):
|
|
cls = self.theclass
|
|
z100 = FixedOffset(100, "+100")
|
|
zm200 = FixedOffset(timedelta(minutes=-200), "-200")
|
|
args = [1, 2, 3, 4, z100]
|
|
base = cls(*args)
|
|
self.assertEqual(base, base.replace())
|
|
|
|
i = 0
|
|
for name, newval in (("hour", 5),
|
|
("minute", 6),
|
|
("second", 7),
|
|
("microsecond", 8),
|
|
("tzinfo", zm200)):
|
|
newargs = args[:]
|
|
newargs[i] = newval
|
|
expected = cls(*newargs)
|
|
got = base.replace(**{name: newval})
|
|
self.assertEqual(expected, got)
|
|
i += 1
|
|
|
|
# Ensure we can get rid of a tzinfo.
|
|
self.assertEqual(base.tzname(), "+100")
|
|
base2 = base.replace(tzinfo=None)
|
|
self.failUnless(base2.tzinfo is None)
|
|
self.failUnless(base2.tzname() is None)
|
|
|
|
# Ensure we can add one.
|
|
base3 = base2.replace(tzinfo=z100)
|
|
self.assertEqual(base, base3)
|
|
self.failUnless(base.tzinfo is base3.tzinfo)
|
|
|
|
# Out of bounds.
|
|
base = cls(1)
|
|
self.assertRaises(ValueError, base.replace, hour=24)
|
|
self.assertRaises(ValueError, base.replace, minute=-1)
|
|
self.assertRaises(ValueError, base.replace, second=100)
|
|
self.assertRaises(ValueError, base.replace, microsecond=1000000)
|
|
|
|
|
|
class TestDateTimeTZ(TestDateTime, TZInfoBase):
|
|
theclass = datetimetz
|
|
|
|
def test_trivial(self):
|
|
dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
|
|
self.assertEqual(dt.year, 1)
|
|
self.assertEqual(dt.month, 2)
|
|
self.assertEqual(dt.day, 3)
|
|
self.assertEqual(dt.hour, 4)
|
|
self.assertEqual(dt.minute, 5)
|
|
self.assertEqual(dt.second, 6)
|
|
self.assertEqual(dt.microsecond, 7)
|
|
self.assertEqual(dt.tzinfo, None)
|
|
|
|
def test_even_more_compare(self):
|
|
# The test_compare() and test_more_compare() inherited from TestDate
|
|
# and TestDateTime covered non-tzinfo cases.
|
|
|
|
# Smallest possible after UTC adjustment.
|
|
t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
|
|
# Largest possible after UTC adjustment.
|
|
t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
|
|
tzinfo=FixedOffset(-1439, ""))
|
|
|
|
# Make sure those compare correctly, and w/o overflow.
|
|
self.failUnless(t1 < t2)
|
|
self.failUnless(t1 != t2)
|
|
self.failUnless(t2 > t1)
|
|
|
|
self.failUnless(t1 == t1)
|
|
self.failUnless(t2 == t2)
|
|
|
|
# Equal afer adjustment.
|
|
t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
|
|
t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
|
|
self.assertEqual(t1, t2)
|
|
|
|
# Change t1 not to subtract a minute, and t1 should be larger.
|
|
t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
|
|
self.failUnless(t1 > t2)
|
|
|
|
# Change t1 to subtract 2 minutes, and t1 should be smaller.
|
|
t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
|
|
self.failUnless(t1 < t2)
|
|
|
|
# Back to the original t1, but make seconds resolve it.
|
|
t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
|
|
second=1)
|
|
self.failUnless(t1 > t2)
|
|
|
|
# Likewise, but make microseconds resolve it.
|
|
t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
|
|
microsecond=1)
|
|
self.failUnless(t1 > t2)
|
|
|
|
# Make t2 naive and it should fail.
|
|
t2 = self.theclass.min
|
|
self.assertRaises(TypeError, lambda: t1 == t2)
|
|
self.assertEqual(t2, t2)
|
|
|
|
# It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
|
|
class Naive(tzinfo):
|
|
def utcoffset(self, dt): return None
|
|
t2 = self.theclass(5, 6, 7, tzinfo=Naive())
|
|
self.assertRaises(TypeError, lambda: t1 == t2)
|
|
self.assertEqual(t2, t2)
|
|
|
|
# OTOH, it's OK to compare two of these mixing the two ways of being
|
|
# naive.
|
|
t1 = self.theclass(5, 6, 7)
|
|
self.assertEqual(t1, t2)
|
|
|
|
# Try a bogus uctoffset.
|
|
class Bogus(tzinfo):
|
|
def utcoffset(self, dt): return 1440 # out of bounds
|
|
t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
|
|
t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
|
|
self.assertRaises(ValueError, lambda: t1 == t1)
|
|
|
|
def test_pickling(self):
|
|
import pickle, cPickle
|
|
|
|
# Try one without a tzinfo.
|
|
args = 6, 7, 23, 20, 59, 1, 64**2
|
|
orig = self.theclass(*args)
|
|
state = orig.__getstate__()
|
|
self.assertEqual(state, ('\x00\x06\x07\x17\x14\x3b\x01\x00\x10\x00',))
|
|
derived = self.theclass(1, 1, 1)
|
|
derived.__setstate__(state)
|
|
self.assertEqual(orig, derived)
|
|
for pickler in pickle, cPickle:
|
|
for binary in 0, 1:
|
|
green = pickler.dumps(orig, binary)
|
|
derived = pickler.loads(green)
|
|
self.assertEqual(orig, derived)
|
|
|
|
# Try one with a tzinfo.
|
|
tinfo = PicklableFixedOffset(-300, 'cookie')
|
|
orig = self.theclass(*args, **{'tzinfo': tinfo})
|
|
state = orig.__getstate__()
|
|
derived = self.theclass(1, 1, 1)
|
|
derived.__setstate__(state)
|
|
self.assertEqual(orig, derived)
|
|
self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
|
|
self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
|
|
self.assertEqual(derived.tzname(), 'cookie')
|
|
|
|
for pickler in pickle, cPickle:
|
|
for binary in 0, 1:
|
|
green = pickler.dumps(orig, binary)
|
|
derived = pickler.loads(green)
|
|
self.assertEqual(orig, derived)
|
|
self.failUnless(isinstance(derived.tzinfo,
|
|
PicklableFixedOffset))
|
|
self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
|
|
self.assertEqual(derived.tzname(), 'cookie')
|
|
|
|
def test_extreme_hashes(self):
|
|
# If an attempt is made to hash these via subtracting the offset
|
|
# then hashing a datetime object, OverflowError results. The
|
|
# Python implementation used to blow up here.
|
|
t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
|
|
hash(t)
|
|
t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
|
|
tzinfo=FixedOffset(-1439, ""))
|
|
hash(t)
|
|
|
|
# OTOH, an OOB offset should blow up.
|
|
t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
|
|
self.assertRaises(ValueError, hash, t)
|
|
|
|
def test_zones(self):
|
|
est = FixedOffset(-300, "EST")
|
|
utc = FixedOffset(0, "UTC")
|
|
met = FixedOffset(60, "MET")
|
|
t1 = datetimetz(2002, 3, 19, 7, 47, tzinfo=est)
|
|
t2 = datetimetz(2002, 3, 19, 12, 47, tzinfo=utc)
|
|
t3 = datetimetz(2002, 3, 19, 13, 47, tzinfo=met)
|
|
self.assertEqual(t1.tzinfo, est)
|
|
self.assertEqual(t2.tzinfo, utc)
|
|
self.assertEqual(t3.tzinfo, met)
|
|
self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
|
|
self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
|
|
self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
|
|
self.assertEqual(t1.tzname(), "EST")
|
|
self.assertEqual(t2.tzname(), "UTC")
|
|
self.assertEqual(t3.tzname(), "MET")
|
|
self.assertEqual(hash(t1), hash(t2))
|
|
self.assertEqual(hash(t1), hash(t3))
|
|
self.assertEqual(hash(t2), hash(t3))
|
|
self.assertEqual(t1, t2)
|
|
self.assertEqual(t1, t3)
|
|
self.assertEqual(t2, t3)
|
|
self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
|
|
self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
|
|
self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
|
|
d = 'datetime.datetimetz(2002, 3, 19, '
|
|
self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
|
|
self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
|
|
self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
|
|
|
|
def test_combine(self):
|
|
met = FixedOffset(60, "MET")
|
|
d = date(2002, 3, 4)
|
|
tz = timetz(18, 45, 3, 1234, tzinfo=met)
|
|
dt = datetimetz.combine(d, tz)
|
|
self.assertEqual(dt, datetimetz(2002, 3, 4, 18, 45, 3, 1234,
|
|
tzinfo=met))
|
|
|
|
def test_extract(self):
|
|
met = FixedOffset(60, "MET")
|
|
dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
|
|
self.assertEqual(dt.date(), date(2002, 3, 4))
|
|
self.assertEqual(dt.time(), time(18, 45, 3, 1234))
|
|
self.assertEqual(dt.timetz(), timetz(18, 45, 3, 1234, tzinfo=met))
|
|
|
|
def test_tz_aware_arithmetic(self):
|
|
import random
|
|
|
|
now = self.theclass.now()
|
|
tz55 = FixedOffset(-330, "west 5:30")
|
|
timeaware = now.timetz().replace(tzinfo=tz55)
|
|
nowaware = self.theclass.combine(now.date(), timeaware)
|
|
self.failUnless(nowaware.tzinfo is tz55)
|
|
self.assertEqual(nowaware.timetz(), timeaware)
|
|
|
|
# Can't mix aware and non-aware.
|
|
self.assertRaises(TypeError, lambda: now - nowaware)
|
|
self.assertRaises(TypeError, lambda: nowaware - now)
|
|
|
|
# And adding datetimetz's doesn't make sense, aware or not.
|
|
self.assertRaises(TypeError, lambda: now + nowaware)
|
|
self.assertRaises(TypeError, lambda: nowaware + now)
|
|
self.assertRaises(TypeError, lambda: nowaware + nowaware)
|
|
|
|
# Subtracting should yield 0.
|
|
self.assertEqual(now - now, timedelta(0))
|
|
self.assertEqual(nowaware - nowaware, timedelta(0))
|
|
|
|
# Adding a delta should preserve tzinfo.
|
|
delta = timedelta(weeks=1, minutes=12, microseconds=5678)
|
|
nowawareplus = nowaware + delta
|
|
self.failUnless(nowaware.tzinfo is tz55)
|
|
nowawareplus2 = delta + nowaware
|
|
self.failUnless(nowawareplus2.tzinfo is tz55)
|
|
self.assertEqual(nowawareplus, nowawareplus2)
|
|
|
|
# that - delta should be what we started with, and that - what we
|
|
# started with should be delta.
|
|
diff = nowawareplus - delta
|
|
self.failUnless(diff.tzinfo is tz55)
|
|
self.assertEqual(nowaware, diff)
|
|
self.assertRaises(TypeError, lambda: delta - nowawareplus)
|
|
self.assertEqual(nowawareplus - nowaware, delta)
|
|
|
|
# Make up a random timezone.
|
|
tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
|
|
# Attach it to nowawareplus.
|
|
nowawareplus = nowawareplus.replace(tzinfo=tzr)
|
|
self.failUnless(nowawareplus.tzinfo is tzr)
|
|
# Make sure the difference takes the timezone adjustments into account.
|
|
got = nowaware - nowawareplus
|
|
# Expected: (nowaware base - nowaware offset) -
|
|
# (nowawareplus base - nowawareplus offset) =
|
|
# (nowaware base - nowawareplus base) +
|
|
# (nowawareplus offset - nowaware offset) =
|
|
# -delta + nowawareplus offset - nowaware offset
|
|
expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
|
|
self.assertEqual(got, expected)
|
|
|
|
# Try max possible difference.
|
|
min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
|
|
max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
|
|
tzinfo=FixedOffset(-1439, "max"))
|
|
maxdiff = max - min
|
|
self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
|
|
timedelta(minutes=2*1439))
|
|
|
|
def test_tzinfo_now(self):
|
|
meth = self.theclass.now
|
|
# Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
|
|
base = meth()
|
|
# Try with and without naming the keyword.
|
|
off42 = FixedOffset(42, "42")
|
|
another = meth(off42)
|
|
again = meth(tzinfo=off42)
|
|
self.failUnless(another.tzinfo is again.tzinfo)
|
|
self.assertEqual(another.utcoffset(), timedelta(minutes=42))
|
|
# Bad argument with and w/o naming the keyword.
|
|
self.assertRaises(TypeError, meth, 16)
|
|
self.assertRaises(TypeError, meth, tzinfo=16)
|
|
# Bad keyword name.
|
|
self.assertRaises(TypeError, meth, tinfo=off42)
|
|
# Too many args.
|
|
self.assertRaises(TypeError, meth, off42, off42)
|
|
|
|
def test_tzinfo_fromtimestamp(self):
|
|
import time
|
|
meth = self.theclass.fromtimestamp
|
|
ts = time.time()
|
|
# Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
|
|
base = meth(ts)
|
|
# Try with and without naming the keyword.
|
|
off42 = FixedOffset(42, "42")
|
|
another = meth(ts, off42)
|
|
again = meth(ts, tzinfo=off42)
|
|
self.failUnless(another.tzinfo is again.tzinfo)
|
|
self.assertEqual(another.utcoffset(), timedelta(minutes=42))
|
|
# Bad argument with and w/o naming the keyword.
|
|
self.assertRaises(TypeError, meth, ts, 16)
|
|
self.assertRaises(TypeError, meth, ts, tzinfo=16)
|
|
# Bad keyword name.
|
|
self.assertRaises(TypeError, meth, ts, tinfo=off42)
|
|
# Too many args.
|
|
self.assertRaises(TypeError, meth, ts, off42, off42)
|
|
# Too few args.
|
|
self.assertRaises(TypeError, meth)
|
|
|
|
def test_tzinfo_utcnow(self):
|
|
meth = self.theclass.utcnow
|
|
# Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
|
|
base = meth()
|
|
# Try with and without naming the keyword; for whatever reason,
|
|
# utcnow() doesn't accept a tzinfo argument.
|
|
off42 = FixedOffset(42, "42")
|
|
self.assertRaises(TypeError, meth, off42)
|
|
self.assertRaises(TypeError, meth, tzinfo=off42)
|
|
|
|
def test_tzinfo_utcfromtimestamp(self):
|
|
import time
|
|
meth = self.theclass.utcfromtimestamp
|
|
ts = time.time()
|
|
# Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
|
|
base = meth(ts)
|
|
# Try with and without naming the keyword; for whatever reason,
|
|
# utcfromtimestamp() doesn't accept a tzinfo argument.
|
|
off42 = FixedOffset(42, "42")
|
|
self.assertRaises(TypeError, meth, ts, off42)
|
|
self.assertRaises(TypeError, meth, ts, tzinfo=off42)
|
|
|
|
def test_tzinfo_timetuple(self):
|
|
# TestDateTime tested most of this. datetimetz adds a twist to the
|
|
# DST flag.
|
|
class DST(tzinfo):
|
|
def __init__(self, dstvalue):
|
|
self.dstvalue = dstvalue
|
|
def dst(self, dt):
|
|
return self.dstvalue
|
|
|
|
cls = self.theclass
|
|
for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
|
|
d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
|
|
t = d.timetuple()
|
|
self.assertEqual(1, t.tm_year)
|
|
self.assertEqual(1, t.tm_mon)
|
|
self.assertEqual(1, t.tm_mday)
|
|
self.assertEqual(10, t.tm_hour)
|
|
self.assertEqual(20, t.tm_min)
|
|
self.assertEqual(30, t.tm_sec)
|
|
self.assertEqual(0, t.tm_wday)
|
|
self.assertEqual(1, t.tm_yday)
|
|
self.assertEqual(flag, t.tm_isdst)
|
|
|
|
# dst() returns wrong type.
|
|
self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
|
|
|
|
# dst() at the edge.
|
|
self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
|
|
self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
|
|
|
|
# dst() out of range.
|
|
self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
|
|
self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
|
|
|
|
def test_utctimetuple(self):
|
|
class DST(tzinfo):
|
|
def __init__(self, dstvalue):
|
|
self.dstvalue = dstvalue
|
|
def dst(self, dt):
|
|
return self.dstvalue
|
|
|
|
cls = self.theclass
|
|
# This can't work: DST didn't implement utcoffset.
|
|
self.assertRaises(NotImplementedError,
|
|
cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
|
|
|
|
class UOFS(DST):
|
|
def __init__(self, uofs, dofs=None):
|
|
DST.__init__(self, dofs)
|
|
self.uofs = uofs
|
|
def utcoffset(self, dt):
|
|
return self.uofs
|
|
|
|
# Ensure tm_isdst is 0 regardless of what dst() says: DST is never
|
|
# in effect for a UTC time.
|
|
for dstvalue in -33, 33, 0, None:
|
|
d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
|
|
t = d.utctimetuple()
|
|
self.assertEqual(d.year, t.tm_year)
|
|
self.assertEqual(d.month, t.tm_mon)
|
|
self.assertEqual(d.day, t.tm_mday)
|
|
self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
|
|
self.assertEqual(13, t.tm_min)
|
|
self.assertEqual(d.second, t.tm_sec)
|
|
self.assertEqual(d.weekday(), t.tm_wday)
|
|
self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
|
|
t.tm_yday)
|
|
self.assertEqual(0, t.tm_isdst)
|
|
|
|
# At the edges, UTC adjustment can normalize into years out-of-range
|
|
# for a datetime object. Ensure that a correct timetuple is
|
|
# created anyway.
|
|
tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
|
|
# That goes back 1 minute less than a full day.
|
|
t = tiny.utctimetuple()
|
|
self.assertEqual(t.tm_year, MINYEAR-1)
|
|
self.assertEqual(t.tm_mon, 12)
|
|
self.assertEqual(t.tm_mday, 31)
|
|
self.assertEqual(t.tm_hour, 0)
|
|
self.assertEqual(t.tm_min, 1)
|
|
self.assertEqual(t.tm_sec, 37)
|
|
self.assertEqual(t.tm_yday, 366) # "year 0" is a leap year
|
|
self.assertEqual(t.tm_isdst, 0)
|
|
|
|
huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
|
|
# That goes forward 1 minute less than a full day.
|
|
t = huge.utctimetuple()
|
|
self.assertEqual(t.tm_year, MAXYEAR+1)
|
|
self.assertEqual(t.tm_mon, 1)
|
|
self.assertEqual(t.tm_mday, 1)
|
|
self.assertEqual(t.tm_hour, 23)
|
|
self.assertEqual(t.tm_min, 58)
|
|
self.assertEqual(t.tm_sec, 37)
|
|
self.assertEqual(t.tm_yday, 1)
|
|
self.assertEqual(t.tm_isdst, 0)
|
|
|
|
def test_tzinfo_isoformat(self):
|
|
zero = FixedOffset(0, "+00:00")
|
|
plus = FixedOffset(220, "+03:40")
|
|
minus = FixedOffset(-231, "-03:51")
|
|
unknown = FixedOffset(None, "")
|
|
|
|
cls = self.theclass
|
|
datestr = '0001-02-03'
|
|
for ofs in None, zero, plus, minus, unknown:
|
|
for us in 0, 987001:
|
|
d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
|
|
timestr = '04:05:59' + (us and '.987001' or '')
|
|
ofsstr = ofs is not None and d.tzname() or ''
|
|
tailstr = timestr + ofsstr
|
|
iso = d.isoformat()
|
|
self.assertEqual(iso, datestr + 'T' + tailstr)
|
|
self.assertEqual(iso, d.isoformat('T'))
|
|
self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
|
|
self.assertEqual(str(d), datestr + ' ' + tailstr)
|
|
|
|
def test_replace(self):
|
|
cls = self.theclass
|
|
z100 = FixedOffset(100, "+100")
|
|
zm200 = FixedOffset(timedelta(minutes=-200), "-200")
|
|
args = [1, 2, 3, 4, 5, 6, 7, z100]
|
|
base = cls(*args)
|
|
self.assertEqual(base, base.replace())
|
|
|
|
i = 0
|
|
for name, newval in (("year", 2),
|
|
("month", 3),
|
|
("day", 4),
|
|
("hour", 5),
|
|
("minute", 6),
|
|
("second", 7),
|
|
("microsecond", 8),
|
|
("tzinfo", zm200)):
|
|
newargs = args[:]
|
|
newargs[i] = newval
|
|
expected = cls(*newargs)
|
|
got = base.replace(**{name: newval})
|
|
self.assertEqual(expected, got)
|
|
i += 1
|
|
|
|
# Ensure we can get rid of a tzinfo.
|
|
self.assertEqual(base.tzname(), "+100")
|
|
base2 = base.replace(tzinfo=None)
|
|
self.failUnless(base2.tzinfo is None)
|
|
self.failUnless(base2.tzname() is None)
|
|
|
|
# Ensure we can add one.
|
|
base3 = base2.replace(tzinfo=z100)
|
|
self.assertEqual(base, base3)
|
|
self.failUnless(base.tzinfo is base3.tzinfo)
|
|
|
|
# Out of bounds.
|
|
base = cls(2000, 2, 29)
|
|
self.assertRaises(ValueError, base.replace, year=2001)
|
|
|
|
def test_more_astimezone(self):
|
|
# The inherited test_astimezone covered some trivial and error cases.
|
|
fnone = FixedOffset(None, "None")
|
|
f44m = FixedOffset(44, "44")
|
|
fm5h = FixedOffset(-timedelta(hours=5), "m300")
|
|
|
|
dt = self.theclass.now(tzinfo=f44m)
|
|
self.failUnless(dt.tzinfo is f44m)
|
|
# Replacing with degenerate tzinfo doesn't do any adjustment.
|
|
for x in dt.astimezone(fnone), dt.astimezone(tz=fnone):
|
|
self.failUnless(x.tzinfo is fnone)
|
|
self.assertEqual(x.date(), dt.date())
|
|
self.assertEqual(x.time(), dt.time())
|
|
# Ditt with None tz.
|
|
x = dt.astimezone(tz=None)
|
|
self.failUnless(x.tzinfo is None)
|
|
self.assertEqual(x.date(), dt.date())
|
|
self.assertEqual(x.time(), dt.time())
|
|
# Ditto replacing with same tzinfo.
|
|
x = dt.astimezone(dt.tzinfo)
|
|
self.failUnless(x.tzinfo is f44m)
|
|
self.assertEqual(x.date(), dt.date())
|
|
self.assertEqual(x.time(), dt.time())
|
|
|
|
# Replacing with different tzinfo does adjust.
|
|
got = dt.astimezone(fm5h)
|
|
self.failUnless(got.tzinfo is fm5h)
|
|
self.assertEqual(got.utcoffset(), timedelta(hours=-5))
|
|
expected = dt - dt.utcoffset() # in effect, convert to UTC
|
|
expected += fm5h.utcoffset(dt) # and from there to local time
|
|
expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
|
|
self.assertEqual(got.date(), expected.date())
|
|
self.assertEqual(got.time(), expected.time())
|
|
self.assertEqual(got.timetz(), expected.timetz())
|
|
self.failUnless(got.tzinfo is expected.tzinfo)
|
|
self.assertEqual(got, expected)
|
|
|
|
def test_aware_subtract(self):
|
|
cls = self.theclass
|
|
|
|
# Primarily trying to ensure that utcoffset() gets called even if
|
|
# the operands have the same tzinfo member. Subtraction didn't
|
|
# used to do this, and it makes a difference for DST-aware tzinfo
|
|
# instances.
|
|
class OperandDependentOffset(tzinfo):
|
|
def utcoffset(self, t):
|
|
if t.minute < 10:
|
|
return t.minute # d0 and d1 equal after adjustment
|
|
else:
|
|
return 59 # d2 off in the weeds
|
|
|
|
base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
|
|
d0 = base.replace(minute=3)
|
|
d1 = base.replace(minute=9)
|
|
d2 = base.replace(minute=11)
|
|
for x in d0, d1, d2:
|
|
for y in d0, d1, d2:
|
|
got = x - y
|
|
if (x is d0 or x is d1) and (y is d0 or y is d1):
|
|
expected = timedelta(0)
|
|
elif x is y is d2:
|
|
expected = timedelta(0)
|
|
elif x is d2:
|
|
expected = timedelta(minutes=(11-59)-0)
|
|
else:
|
|
assert y is d2
|
|
expected = timedelta(minutes=0-(11-59))
|
|
self.assertEqual(got, expected)
|
|
|
|
|
|
def test_suite():
|
|
allsuites = [unittest.makeSuite(klass, 'test')
|
|
for klass in (TestModule,
|
|
TestTZInfo,
|
|
TestTimeDelta,
|
|
TestDateOnly,
|
|
TestDate,
|
|
TestDateTime,
|
|
TestTime,
|
|
TestTimeTZ,
|
|
TestDateTimeTZ,
|
|
)
|
|
]
|
|
return unittest.TestSuite(allsuites)
|
|
|
|
def test_main():
|
|
import gc
|
|
import sys
|
|
|
|
thesuite = test_suite()
|
|
lastrc = None
|
|
while True:
|
|
test_support.run_suite(thesuite)
|
|
if 1: # change to 0, under a debug build, for some leak detection
|
|
break
|
|
gc.collect()
|
|
if gc.garbage:
|
|
raise SystemError("gc.garbage not empty after test run: %r" %
|
|
gc.garbage)
|
|
if hasattr(sys, 'gettotalrefcount'):
|
|
thisrc = sys.gettotalrefcount()
|
|
print >> sys.stderr, '*' * 10, 'total refs:', thisrc,
|
|
if lastrc:
|
|
print >> sys.stderr, 'delta:', thisrc - lastrc
|
|
else:
|
|
print >> sys.stderr
|
|
lastrc = thisrc
|
|
|
|
if __name__ == "__main__":
|
|
test_main()
|