See http://www.zope.org/Members/fdrake/DateTimeWiki/TestCases
from test
import test_support
from datetime
import MINYEAR
, MAXYEAR
from datetime
import timedelta
from datetime
import tzinfo
from datetime
import time
from datetime
import date
, datetime
pickle_choices
= [(pickler
, unpickler
, proto
)
for pickler
in pickle
, cPickle
for unpickler
in pickle
, cPickle
assert len(pickle_choices
) == 2*2*3
# An arbitrary collection of objects of non-datetime types, for testing
# mixed-type comparisons.
OTHERSTUFF
= (10, 10L, 34.5, "abc", {}, [], ())
#############################################################################
class TestModule(unittest
.TestCase
):
def test_constants(self
):
self
.assertEqual(datetime
.MINYEAR
, 1)
self
.assertEqual(datetime
.MAXYEAR
, 9999)
#############################################################################
class FixedOffset(tzinfo
):
def __init__(self
, offset
, name
, dstoffset
=42):
if isinstance(offset
, int):
offset
= timedelta(minutes
=offset
)
if isinstance(dstoffset
, int):
dstoffset
= timedelta(minutes
=dstoffset
)
self
.__dstoffset
= dstoffset
return self
.__name
.lower()
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
self
.assertRaises(NotImplementedError, useless
.tzname
, dt
)
self
.assertRaises(NotImplementedError, useless
.utcoffset
, dt
)
self
.assertRaises(NotImplementedError, useless
.dst
, dt
)
def test_subclass_must_override(self
):
def __init__(self
, offset
, name
):
self
.failUnless(issubclass(NotEnough
, tzinfo
))
ne
= NotEnough(3, "NotByALongShot")
self
.failUnless(isinstance(ne
, tzinfo
))
self
.assertRaises(NotImplementedError, ne
.tzname
, dt
)
self
.assertRaises(NotImplementedError, ne
.utcoffset
, dt
)
self
.assertRaises(NotImplementedError, ne
.dst
, dt
)
fo
= FixedOffset(3, "Three")
self
.failUnless(isinstance(fo
, tzinfo
))
for dt
in datetime
.now(), None:
self
.assertEqual(fo
.utcoffset(dt
), timedelta(minutes
=3))
self
.assertEqual(fo
.tzname(dt
), "Three")
self
.assertEqual(fo
.dst(dt
), timedelta(minutes
=42))
def test_pickling_base(self
):
# 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
, unpickler
, proto
in pickle_choices
:
green
= pickler
.dumps(orig
, proto
)
derived
= unpickler
.loads(green
)
self
.failUnless(type(derived
) is tzinfo
)
def test_pickling_subclass(self
):
# Make sure we can pickle/unpickle an instance of a subclass.
offset
= timedelta(minutes
=-300)
orig
= PicklableFixedOffset(offset
, 'cookie')
self
.failUnless(isinstance(orig
, tzinfo
))
self
.failUnless(type(orig
) is PicklableFixedOffset
)
self
.assertEqual(orig
.utcoffset(None), offset
)
self
.assertEqual(orig
.tzname(None), 'cookie')
for pickler
, unpickler
, proto
in pickle_choices
:
green
= pickler
.dumps(orig
, proto
)
derived
= unpickler
.loads(green
)
self
.failUnless(isinstance(derived
, tzinfo
))
self
.failUnless(type(derived
) is PicklableFixedOffset
)
self
.assertEqual(derived
.utcoffset(None), offset
)
self
.assertEqual(derived
.tzname(None), 'cookie')
#############################################################################
# Base clase for testing a particular aspect of timedelta, time, date and
class HarmlessMixedComparison(unittest
.TestCase
):
# Test that __eq__ and __ne__ don't complain for mixed-type comparisons.
# Subclasses must define 'theclass', and theclass(1, 1, 1) must be a
def test_harmless_mixed_comparison(self
):
me
= self
.theclass(1, 1, 1)
self
.failUnless(me
!= ())
self
.failUnless(() != me
)
self
.failUnless(me
in [1, 20L, [], me
])
self
.failIf(me
not in [1, 20L, [], me
])
self
.failUnless([] in [me
, 1, 20L, []])
self
.failIf([] not in [me
, 1, 20L, []])
def test_harmful_mixed_comparison(self
):
me
= self
.theclass(1, 1, 1)
self
.assertRaises(TypeError, lambda: me
< ())
self
.assertRaises(TypeError, lambda: me
<= ())
self
.assertRaises(TypeError, lambda: me
> ())
self
.assertRaises(TypeError, lambda: me
>= ())
self
.assertRaises(TypeError, lambda: () < me
)
self
.assertRaises(TypeError, lambda: () <= me
)
self
.assertRaises(TypeError, lambda: () > me
)
self
.assertRaises(TypeError, lambda: () >= me
)
self
.assertRaises(TypeError, cmp, (), me
)
self
.assertRaises(TypeError, cmp, me
, ())
#############################################################################
class TestTimeDelta(HarmlessMixedComparison
):
def test_constructor(self
):
# Check keyword args to constructor
eq(td(), td(weeks
=0, days
=0, hours
=0, minutes
=0, seconds
=0,
milliseconds
=0, microseconds
=0))
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
):
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(-b
, td(-1, 24*3600 - 60))
eq(-c
, td(-1, 24*3600 - 1, 999000))
eq(td(0, 0, 60*1000000), b
)
eq(c
*10, td(0, 0, 10000))
eq(10*c
, td(0, 0, 10000))
eq(c
*10L, td(0, 0, 10000))
eq(a
//10, td(0, 7*24*360))
eq(a
//3600000, td(0, 0, 7*24*1000))
def test_disallowed_computations(self
):
# Add/sub ints, longs, floats should be illegal
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.
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.
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
)
microseconds
=(3*60 - 12) * 1e6
+ 1)
t2
= timedelta(microseconds
=1)
def test_hash_equality(self
):
microseconds
=(3*60 - 12) * 1000000)
self
.assertEqual(hash(t1
), hash(t2
))
t2
+= timedelta(days
=7*7)
self
.assertEqual(hash(t1
), hash(t2
))
self
.assertEqual(len(d
), 1)
self
.assertEqual(d
[t1
], 2)
for pickler
, unpickler
, proto
in pickle_choices
:
green
= pickler
.dumps(orig
, proto
)
derived
= unpickler
.loads(green
)
self
.assertEqual(orig
, derived
)
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(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 OTHERSTUFF
:
self
.assertEqual(t1
== badarg
, False)
self
.assertEqual(t1
!= badarg
, True)
self
.assertEqual(badarg
== t1
, False)
self
.assertEqual(badarg
!= t1
, True)
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
)
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)),
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,
"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.
self
.failUnless(s
.startswith('datetime.'))
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))
tiny
= timedelta
.resolution
td
= timedelta
.min + tiny
self
.assertRaises(OverflowError, td
.__sub
__, tiny
)
self
.assertRaises(OverflowError, td
.__add
__, -tiny
)
td
= timedelta
.max - tiny
self
.assertRaises(OverflowError, td
.__add
__, tiny
)
self
.assertRaises(OverflowError, td
.__sub
__, -tiny
)
self
.assertRaises(OverflowError, lambda: -timedelta
.max)
def test_microsecond_rounding(self
):
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_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
),
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))
def test_subclass_timedelta(self
):
return T(td
.days
, td
.seconds
, td
.microseconds
)
from_td
= staticmethod(from_td
)
self
.microseconds
/ 3600e6
)
self
.assert_(type(t1
) is T
)
self
.assertEqual(t1
.as_hours(), 24)
t2
= T(days
=-1, seconds
=-3600)
self
.assert_(type(t2
) is T
)
self
.assertEqual(t2
.as_hours(), -25)
self
.assert_(type(t3
) is timedelta
)
self
.assert_(type(t4
) is T
)
self
.assertEqual(t3
.days
, t4
.days
)
self
.assertEqual(t3
.seconds
, t4
.seconds
)
self
.assertEqual(t3
.microseconds
, t4
.microseconds
)
self
.assertEqual(str(t3
), str(t4
))
self
.assertEqual(t4
.as_hours(), -1)
#############################################################################
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
):
delta
= timedelta(days
=1, hours
=2, minutes
=3, seconds
=4,
days
= timedelta(delta
.days
)
self
.assertEqual(days
, timedelta(1))
self
.assertEqual(dt2
, dt
+ days
)
self
.assertEqual(dt2
, dt
+ days
)
self
.assertEqual(dt2
, dt
- days
)
days
= timedelta(delta
.days
)
self
.assertEqual(days
, timedelta(-2))
self
.assertEqual(dt2
, dt
+ days
)
self
.assertEqual(dt2
, dt
+ days
)
self
.assertEqual(dt2
, dt
- days
)
class SubclassDate(date
):
class TestDate(HarmlessMixedComparison
):
# Tests here should pass for both dates and datetimes, except for a
# few tests that TestDateTime overrides.
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),
# Verify dt -> string -> date identity.
self
.failUnless(s
.startswith('datetime.'))
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
# first example from "Calendrical Calculations"
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 spottily across the whole
# range of years supported.
for year
in xrange(MINYEAR
, MAXYEAR
+1, 7):
# Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
d
= self
.theclass(year
, 1, 1)
d2
= self
.theclass
.fromordinal(n
)
# Verify that moving back a day gets to the end of year-1.
d
= self
.theclass
.fromordinal(n
-1)
d2
= self
.theclass(year
-1, 12, 31)
self
.assertEqual(d2
.toordinal(), n
-1)
# 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
:
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
))
def test_extreme_ordinals(self
):
a
= self
.theclass(a
.year
, a
.month
, a
.day
) # get rid of time parts
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(a
.year
, a
.month
, a
.day
) # get rid of time parts
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
):
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)
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)
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)
e
= self
.theclass(2000, 12, 31)
self
.assertEqual(hash(d
), hash(e
))
self
.assertEqual(len(dic
), 1)
self
.assertEqual(dic
[d
], 2)
self
.assertEqual(dic
[e
], 2)
d
= self
.theclass(2001, 1, 1)
e
= self
.theclass(2001, 1, 1)
self
.assertEqual(hash(d
), hash(e
))
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)
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)
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
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
)
tiny
= self
.theclass
.resolution
dt
= self
.theclass
.min + tiny
self
.assertRaises(OverflowError, dt
.__sub
__, tiny
)
self
.assertRaises(OverflowError, dt
.__add
__, -tiny
)
dt
= self
.theclass
.max - tiny
self
.assertRaises(OverflowError, dt
.__add
__, tiny
)
self
.assertRaises(OverflowError, dt
.__sub
__, -tiny
)
def test_fromtimestamp(self
):
# 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_insane_fromtimestamp(self
):
# It's possible that some platform maps time_t to double,
# and that this test will fail there. This test should
# exempt such platforms (provided they return reasonable
for insane
in -1e200
, 1e200
:
self
.assertRaises(ValueError, self
.theclass
.fromtimestamp
,
# We claim that today() is like fromtimestamp(time.time()), so
today
= self
.theclass
.today()
todayagain
= self
.theclass
.fromtimestamp(ts
)
# There are several legit reasons that could fail:
# 1. It recently became midnight, between the today() and the
# 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
# 4. The system clock got fiddled between calls.
# In any case, wait a little while and try again.
# 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))
# 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
):
# http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
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
= """
iso_long_years
= map(int, ISO_LONG_YEARS_TABLE
.split())
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:
self
.assertEqual(L
, iso_long_years
)
def test_isoformat(self
):
t
= self
.theclass(2, 3, 2)
self
.assertEqual(t
.isoformat(), "0002-03-02")
t
= self
.theclass(2002, 3, 2)
self
.assertEqual(t
.ctime(), "Sat Mar 2 00:00:00 2002")
t
= self
.theclass(2005, 3, 2)
self
.assertEqual(t
.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
self
.assertEqual(t
.strftime(""), "") # SF bug #761337
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
):
# January 2, 1956 is a Monday (0)
d
= self
.theclass(1956, 1, 2+i
)
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
)
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
d
= self
.theclass(1956, 3, 1+i
)
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)
orig
= self
.theclass(*args
)
for pickler
, unpickler
, proto
in pickle_choices
:
green
= pickler
.dumps(orig
, proto
)
derived
= unpickler
.loads(green
)
self
.assertEqual(orig
, derived
)
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(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 OTHERSTUFF
:
self
.assertEqual(t1
== badarg
, False)
self
.assertEqual(t1
!= badarg
, True)
self
.assertEqual(badarg
== t1
, False)
self
.assertEqual(badarg
!= t1
, True)
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
)
def test_mixed_compare(self
):
our
= self
.theclass(2000, 4, 5)
self
.assertRaises(TypeError, cmp, our
, 1)
self
.assertRaises(TypeError, cmp, 1, our
)
class AnotherDateTimeClass(object):
def __cmp__(self
, other
):
# Return "equal" so calling this can't be confused with
# compare-by-address (which never says "equal" for distinct
# This still errors, because date and datetime comparison raise
# TypeError instead of NotImplemented when they don't know what to
# do, in order to stop comparison from falling back to the default
their
= AnotherDateTimeClass()
self
.assertRaises(TypeError, cmp, our
, their
)
# Oops: The next stab raises TypeError in the C implementation,
# but not in the Python implementation of datetime. The difference
# is due to that the Python implementation defines __cmp__ but
# the C implementation defines tp_richcompare. This is more pain
# to fix than it's worth, so commenting out the test.
# self.assertEqual(cmp(their, our), 0)
# But date and datetime comparison return NotImplemented instead if the
# other object has a timetuple attr. This gives the other object a
# chance to do the comparison.
class Comparable(AnotherDateTimeClass
):
self
.assertEqual(cmp(our
, their
), 0)
self
.assertEqual(cmp(their
, our
), 0)
self
.failUnless(our
== their
)
self
.failUnless(their
== our
)
# 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.
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")
self
.assertEqual(base
, base
.replace())
for name
, newval
in (("year", 2),
got
= base
.replace(**{name
: newval
})
self
.assertEqual(expected
, got
)
self
.assertRaises(ValueError, base
.replace
, year
=2001)
def test_subclass_date(self
):
def __new__(cls
, *args
, **kws
):
extra
= temp
.pop('extra')
result
= self
.theclass
.__new
__(cls
, *args
, **temp
)
def newmeth(self
, start
):
return start
+ self
.year
+ self
.month
dt1
= self
.theclass(*args
)
dt2
= C(*args
, **{'extra': 7})
self
.assertEqual(dt2
.__class__
, C
)
self
.assertEqual(dt2
.theAnswer
, 42)
self
.assertEqual(dt2
.extra
, 7)
self
.assertEqual(dt1
.toordinal(), dt2
.toordinal())
self
.assertEqual(dt2
.newmeth(-7), dt1
.year
+ dt1
.month
- 7)
def test_pickling_subclass_date(self
):
orig
= SubclassDate(*args
)
for pickler
, unpickler
, proto
in pickle_choices
:
green
= pickler
.dumps(orig
, proto
)
derived
= unpickler
.loads(green
)
self
.assertEqual(orig
, derived
)
def test_backdoor_resistance(self
):
# For fast unpickling, the constructor accepts a pickle string.
# This is a low-overhead backdoor. A user can (by intent or
# mistake) pass a string directly, which (if it's the right length)
# will get treated like a pickle, and bypass the normal sanity
# checks in the constructor. This can create insane objects.
# The constructor doesn't want to burn the time to validate all
# fields, but does check the month field. This stops, e.g.,
# datetime.datetime('1995-03-25') from yielding an insane object.
if not issubclass(self
.theclass
, datetime
):
for month_byte
in '9', chr(0), chr(13), '\xff':
self
.assertRaises(TypeError, self
.theclass
,
base
[:2] + month_byte
+ base
[3:])
for ord_byte
in range(1, 13):
# This shouldn't blow up because of the month byte alone. If
# the implementation changes to do more-careful checking, it may
# blow up because other fields are insane.
self
.theclass(base
[:2] + chr(ord_byte
) + base
[3:])
#############################################################################
class SubclassDatetime(datetime
):
class TestDateTime(TestDate
):
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),
# Verify dt -> string -> datetime identity.
self
.failUnless(s
.startswith('datetime.'))
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
,
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.
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
)
# 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
dt1
= self
.theclass(MAXYEAR
, 12, 31, 23, 59, 59, 999998)
us
= timedelta(microseconds
=1)
self
.assertEqual(dt2
- dt1
, us
)
def test_bad_constructor_arguments(self
):
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)
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)
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)
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)
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)
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)
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
,
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(hash(d
), hash(e
))
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(hash(d
), hash(e
))
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)
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)
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
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
)
args
= 6, 7, 23, 20, 59, 1, 64**2
orig
= self
.theclass(*args
)
for pickler
, unpickler
, proto
in pickle_choices
:
green
= pickler
.dumps(orig
, proto
)
derived
= unpickler
.loads(green
)
self
.assertEqual(orig
, derived
)
def test_more_pickling(self
):
a
= self
.theclass(2003, 2, 7, 16, 48, 37, 444116)
self
.assertEqual(b
.year
, 2003)
self
.assertEqual(b
.month
, 2)
self
.assertEqual(b
.day
, 7)
def test_pickling_subclass_datetime(self
):
args
= 6, 7, 23, 20, 59, 1, 64**2
orig
= SubclassDatetime(*args
)
for pickler
, unpickler
, proto
in pickle_choices
:
green
= pickler
.dumps(orig
, proto
)
derived
= unpickler
.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
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
)):
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(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
):
expected
= time
.localtime(ts
)
got
= self
.theclass
.fromtimestamp(ts
)
self
.verify_field_equality(expected
, got
)
def test_utcfromtimestamp(self
):
expected
= time
.gmtime(ts
)
got
= self
.theclass
.utcfromtimestamp(ts
)
self
.verify_field_equality(expected
, got
)
def test_insane_fromtimestamp(self
):
# It's possible that some platform maps time_t to double,
# and that this test will fail there. This test should
# exempt such platforms (provided they return reasonable
for insane
in -1e200
, 1e200
:
self
.assertRaises(ValueError, self
.theclass
.fromtimestamp
,
def test_insane_utcfromtimestamp(self
):
# It's possible that some platform maps time_t to double,
# and that this test will fail there. This test should
# exempt such platforms (provided they return reasonable
for insane
in -1e200
, 1e200
:
self
.assertRaises(ValueError, self
.theclass
.utcfromtimestamp
,
# Call it a success if utcnow() and utcfromtimestamp() are within
# a second of each other.
tolerance
= timedelta(seconds
=1)
from_now
= self
.theclass
.utcnow()
from_timestamp
= self
.theclass
.utcfromtimestamp(time
.time())
if abs(from_timestamp
- from_now
) <= tolerance
:
# 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
.hour
, t
.minute
, t
.second
,
t
.toordinal() - date(t
.year
, 1, 1).toordinal() + 1,
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"),
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))
t
= time(18, 45, 3, 1234)
expected
= self
.theclass(2002, 3, 4, 18, 45, 3, 1234)
combine
= self
.theclass
.combine
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
args
= [1, 2, 3, 4, 5, 6, 7]
self
.assertEqual(base
, base
.replace())
for name
, newval
in (("year", 2),
got
= base
.replace(**{name
: newval
})
self
.assertEqual(expected
, got
)
self
.assertRaises(ValueError, base
.replace
, year
=2001)
def test_astimezone(self
):
# Pretty boring! The TZ test is more interesting here. astimezone()
# simply can't be applied to a naive object.
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
self
.assertRaises(ValueError, dt
.astimezone
, f
) # naive
self
.assertRaises(ValueError, dt
.astimezone
, tz
=f
) # naive
def utcoffset(self
, dt
): return None
def dst(self
, dt
): return timedelta(0)
self
.assertRaises(ValueError, dt
.astimezone
, bog
) # naive
def utcoffset(self
, dt
): return timedelta(0)
def dst(self
, dt
): return None
self
.assertRaises(ValueError, dt
.astimezone
, alsobog
) # also naive
def test_subclass_datetime(self
):
def __new__(cls
, *args
, **kws
):
extra
= temp
.pop('extra')
result
= self
.theclass
.__new
__(cls
, *args
, **temp
)
def newmeth(self
, start
):
return start
+ self
.year
+ self
.month
+ self
.second
args
= 2003, 4, 14, 12, 13, 41
dt1
= self
.theclass(*args
)
dt2
= C(*args
, **{'extra': 7})
self
.assertEqual(dt2
.__class__
, C
)
self
.assertEqual(dt2
.theAnswer
, 42)
self
.assertEqual(dt2
.extra
, 7)
self
.assertEqual(dt1
.toordinal(), dt2
.toordinal())
self
.assertEqual(dt2
.newmeth(-7), dt1
.year
+ dt1
.month
+
class SubclassTime(time
):
class TestTime(HarmlessMixedComparison
):
def test_basic_attributes(self
):
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.
self
.failUnless(s
.startswith('datetime.'))
# Verify identity via reconstructing from pieces.
t2
= self
.theclass(t
.hour
, t
.minute
, t
.second
,
def test_comparing(self
):
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
)):
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(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 OTHERSTUFF
:
self
.assertEqual(t1
== badarg
, False)
self
.assertEqual(t1
!= badarg
, True)
self
.assertEqual(badarg
== t1
, False)
self
.assertEqual(badarg
!= t1
, True)
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
)
def test_bad_constructor_arguments(self
):
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)
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)
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)
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(hash(d
), hash(e
))
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(hash(d
), hash(e
))
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
))
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
))
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'"), "'' ''")
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")
name
= 'datetime.' + self
.theclass
.__name
__
self
.assertEqual(repr(self
.theclass(1, 2, 3, 4)),
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)),
self
.assertEqual(repr(self
.theclass(23, 15, 0, 0)),
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)
orig
= self
.theclass(*args
)
for pickler
, unpickler
, proto
in pickle_choices
:
green
= pickler
.dumps(orig
, proto
)
derived
= unpickler
.loads(green
)
self
.assertEqual(orig
, derived
)
def test_pickling_subclass_time(self
):
orig
= SubclassTime(*args
)
for pickler
, unpickler
, proto
in pickle_choices
:
green
= pickler
.dumps(orig
, proto
)
derived
= unpickler
.loads(green
)
self
.assertEqual(orig
, derived
)
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())
self
.assertEqual(base
, base
.replace())
for name
, newval
in (("hour", 5),
got
= base
.replace(**{name
: newval
})
self
.assertEqual(expected
, got
)
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)
def test_subclass_time(self
):
def __new__(cls
, *args
, **kws
):
extra
= temp
.pop('extra')
result
= self
.theclass
.__new
__(cls
, *args
, **temp
)
def newmeth(self
, start
):
return start
+ self
.hour
+ self
.second
dt1
= self
.theclass(*args
)
dt2
= C(*args
, **{'extra': 7})
self
.assertEqual(dt2
.__class__
, C
)
self
.assertEqual(dt2
.theAnswer
, 42)
self
.assertEqual(dt2
.extra
, 7)
self
.assertEqual(dt1
.isoformat(), dt2
.isoformat())
self
.assertEqual(dt2
.newmeth(-7), dt1
.hour
+ dt1
.second
- 7)
# 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 time and datetime).
class TZInfoBase(unittest
.TestCase
):
def test_argument_passing(self
):
# A datetime passes itself on, a time passes None.
class introspective(tzinfo
):
def tzname(self
, dt
): return dt
and "real" or "none"
return timedelta(minutes
= dt
and 42 or -42)
obj
= cls(1, 2, 3, tzinfo
=introspective())
expected
= cls
is time
and "none" or "real"
self
.assertEqual(obj
.tzname(), expected
)
expected
= timedelta(minutes
=(cls
is time
and -42 or 42))
self
.assertEqual(obj
.utcoffset(), expected
)
self
.assertEqual(obj
.dst(), expected
)
def test_bad_tzinfo_classes(self
):
self
.assertRaises(TypeError, cls
, 1, 1, 1, tzinfo
=12)
def utcoffset(self
, dt
): pass
self
.assertRaises(TypeError, cls
, 1, 1, 1, tzinfo
=NiceTry
)
def utcoffset(self
, dt
): pass
t
= cls(1, 1, 1, tzinfo
=b
)
self
.failUnless(t
.tzinfo
is b
)
def test_utc_offset_out_of_bounds(self
):
def __init__(self
, offset
):
self
.offset
= timedelta(minutes
=offset
)
for offset
, legit
in ((-1440, False),
t
= cls(1, 2, 3, tzinfo
=Edgy(offset
))
t
= cls(6, 6, 6, 1, 2, 3, tzinfo
=Edgy(offset
))
tag
= "%c%02d:%02d" % (offset
< 0 and '-' or '+', h
, m
)
if isinstance(t
, datetime
):
self
.assertEqual(str(t
), "01:02:03" + tag
)
self
.assertRaises(ValueError, str, t
)
def test_tzinfo_classes(self
):
def utcoffset(self
, dt
): return None
def dst(self
, dt
): return None
def tzname(self
, dt
): return None
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)
def utcoffset(self
, dt
): return timedelta(minutes
=-1439)
def dst(self
, dt
): return timedelta(minutes
=1439)
def tzname(self
, dt
): return "aname"
t
= 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")
def utcoffset(self
, dt
): return "aname"
def dst(self
, dt
): return 7
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
)
def utcoffset(self
, dt
): return timedelta(hours
=-24)
def dst(self
, dt
): return timedelta(hours
=24)
t
= cls(1, 1, 1, tzinfo
=C6())
self
.assertRaises(ValueError, t
.utcoffset
)
self
.assertRaises(ValueError, t
.dst
)
# Not a whole number of minutes.
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
):
# Ensure that utcoffset() gets ignored if the comparands have
# the same tzinfo member.
class OperandDependentOffset(tzinfo
):
# d0 and d1 equal after adjustment
return timedelta(minutes
=t
.minute
)
return timedelta(minutes
=59)
base
= cls(8, 9, 10, tzinfo
=OperandDependentOffset())
d0
= base
.replace(minute
=3)
d1
= base
.replace(minute
=9)
d2
= base
.replace(minute
=11)
expected
= cmp(x
.minute
, y
.minute
)
self
.assertEqual(got
, expected
)
# However, if they're different members, uctoffset is not ignored.
# Note that a time can't actually have an operand-depedent offset,
# though (and time.utcoffset() passes None to tzinfo.utcoffset()),
# so skip this test for time.
d0
= base
.replace(minute
=3, tzinfo
=OperandDependentOffset())
d1
= base
.replace(minute
=9, tzinfo
=OperandDependentOffset())
d2
= base
.replace(minute
=11, tzinfo
=OperandDependentOffset())
if (x
is d0
or x
is d1
) and (y
is d0
or y
is d1
):
self
.assertEqual(got
, expected
)
# Testing time objects with a non-None tzinfo.
class TestTimeTZ(TestTime
, TZInfoBase
):
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)
est
= FixedOffset(-300, "EST", 1)
utc
= FixedOffset(0, "UTC", -2)
met
= FixedOffset(60, "MET", 3)
t1
= time( 7, 47, tzinfo
=est
)
t2
= time(12, 47, tzinfo
=utc
)
t3
= time(13, 47, tzinfo
=met
)
t4
= time(microsecond
=40)
t5
= time(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
.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")
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
= time(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.
def tzname(self
, dt
): return 42
t
= time(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
))
# Try one without a tzinfo.
orig
= self
.theclass(*args
)
for pickler
, unpickler
, proto
in pickle_choices
:
green
= pickler
.dumps(orig
, proto
)
derived
= unpickler
.loads(green
)
self
.assertEqual(orig
, derived
)
tinfo
= PicklableFixedOffset(-300, 'cookie')
orig
= self
.theclass(5, 6, 7, tzinfo
=tinfo
)
for pickler
, unpickler
, proto
in pickle_choices
:
green
= pickler
.dumps(orig
, proto
)
derived
= unpickler
.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.
t
= cls(0, tzinfo
=FixedOffset(-300, ""))
t
= cls(5, tzinfo
=FixedOffset(-300, ""))
t
= cls(5, tzinfo
=FixedOffset(300, ""))
t
= cls(23, 59, tzinfo
=FixedOffset(23*60 + 59, ""))
# Mostly ensuring this doesn't overflow internally.
t
= cls(0, tzinfo
=FixedOffset(23*60 + 59, ""))
# But this should yield a value error -- the utcoffset is bogus.
t
= cls(0, tzinfo
=FixedOffset(24*60, ""))
self
.assertRaises(ValueError, lambda: bool(t
))
t
= cls(0, tzinfo
=FixedOffset(-24*60, ""))
self
.assertRaises(ValueError, lambda: bool(t
))
z100
= FixedOffset(100, "+100")
zm200
= FixedOffset(timedelta(minutes
=-200), "-200")
args
= [1, 2, 3, 4, z100
]
self
.assertEqual(base
, base
.replace())
for name
, newval
in (("hour", 5),
got
= base
.replace(**{name
: newval
})
self
.assertEqual(expected
, got
)
# 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)
base3
= base2
.replace(tzinfo
=z100
)
self
.assertEqual(base
, base3
)
self
.failUnless(base
.tzinfo
is base3
.tzinfo
)
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)
def test_mixed_compare(self
):
t2
= t2
.replace(tzinfo
=None)
t2
= t2
.replace(tzinfo
=FixedOffset(None, ""))
t2
= t2
.replace(tzinfo
=FixedOffset(0, ""))
self
.assertRaises(TypeError, lambda: t1
== t2
)
# In time w/ identical tzinfo objects, utcoffset is ignored.
self
.offset
= timedelta(minutes
=22)
self
.offset
+= timedelta(minutes
=1)
t1
= t2
.replace(tzinfo
=v
)
t2
= t2
.replace(tzinfo
=v
)
self
.assertEqual(t1
.utcoffset(), timedelta(minutes
=23))
self
.assertEqual(t2
.utcoffset(), timedelta(minutes
=24))
# But if they're not identical, it isn't ignored.
t2
= t2
.replace(tzinfo
=Varies())
self
.failUnless(t1
< t2
) # t1's offset counter still going up
def test_subclass_timetz(self
):
def __new__(cls
, *args
, **kws
):
extra
= temp
.pop('extra')
result
= self
.theclass
.__new
__(cls
, *args
, **temp
)
def newmeth(self
, start
):
return start
+ self
.hour
+ self
.second
args
= 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
dt1
= self
.theclass(*args
)
dt2
= C(*args
, **{'extra': 7})
self
.assertEqual(dt2
.__class__
, C
)
self
.assertEqual(dt2
.theAnswer
, 42)
self
.assertEqual(dt2
.extra
, 7)
self
.assertEqual(dt1
.utcoffset(), dt2
.utcoffset())
self
.assertEqual(dt2
.newmeth(-7), dt1
.hour
+ dt1
.second
- 7)
# Testing datetime objects with a non-None tzinfo.
class TestDateTimeTZ(TestDateTime
, TZInfoBase
):
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
== t1
)
self
.failUnless(t2
== t2
)
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, ""))
# Change t1 not to subtract a minute, and t1 should be larger.
t1
= self
.theclass(1, 12, 31, 23, 59, tzinfo
=FixedOffset(0, ""))
# Change t1 to subtract 2 minutes, and t1 should be smaller.
t1
= self
.theclass(1, 12, 31, 23, 59, tzinfo
=FixedOffset(2, ""))
# Back to the original t1, but make seconds resolve it.
t1
= self
.theclass(1, 12, 31, 23, 59, tzinfo
=FixedOffset(1, ""),
# Likewise, but make microseconds resolve it.
t1
= self
.theclass(1, 12, 31, 23, 59, tzinfo
=FixedOffset(1, ""),
# Make t2 naive and it should fail.
self
.assertRaises(TypeError, lambda: t1
== t2
)
# It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
def utcoffset(self
, dt
): return None
t2
= self
.theclass(5, 6, 7, tzinfo
=Naive())
self
.assertRaises(TypeError, lambda: t1
== t2
)
# OTOH, it's OK to compare two of these mixing the two ways of being
t1
= self
.theclass(5, 6, 7)
return timedelta(minutes
=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
== t2
)
# Try one without a tzinfo.
args
= 6, 7, 23, 20, 59, 1, 64**2
orig
= self
.theclass(*args
)
for pickler
, unpickler
, proto
in pickle_choices
:
green
= pickler
.dumps(orig
, proto
)
derived
= unpickler
.loads(green
)
self
.assertEqual(orig
, derived
)
tinfo
= PicklableFixedOffset(-300, 'cookie')
orig
= self
.theclass(*args
, **{'tzinfo': tinfo
})
derived
= self
.theclass(1, 1, 1, tzinfo
=FixedOffset(0, "", 0))
for pickler
, unpickler
, proto
in pickle_choices
:
green
= pickler
.dumps(orig
, proto
)
derived
= unpickler
.loads(green
)
self
.assertEqual(orig
, derived
)
self
.failUnless(isinstance(derived
.tzinfo
,
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, ""))
t
= self
.theclass(MAXYEAR
, 12, 31, 23, 59, 59, 999999,
tzinfo
=FixedOffset(-1439, ""))
# OTOH, an OOB offset should blow up.
t
= self
.theclass(5, 5, 5, tzinfo
=FixedOffset(-1440, ""))
self
.assertRaises(ValueError, hash, t
)
est
= FixedOffset(-300, "EST")
utc
= FixedOffset(0, "UTC")
met
= FixedOffset(60, "MET")
t1
= datetime(2002, 3, 19, 7, 47, tzinfo
=est
)
t2
= datetime(2002, 3, 19, 12, 47, tzinfo
=utc
)
t3
= datetime(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(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.datetime(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)")
met
= FixedOffset(60, "MET")
tz
= time(18, 45, 3, 1234, tzinfo
=met
)
dt
= datetime
.combine(d
, tz
)
self
.assertEqual(dt
, datetime(2002, 3, 4, 18, 45, 3, 1234,
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(), time(18, 45, 3, 1234, tzinfo
=met
))
def test_tz_aware_arithmetic(self
):
now
= self
.theclass
.now()
tz55
= FixedOffset(-330, "west 5:30")
timeaware
= now
.time().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 datetime'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"))
self
.assertEqual(maxdiff
, self
.theclass
.max - self
.theclass
.min +
timedelta(minutes
=2*1439))
def test_tzinfo_now(self
):
# Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
# Try with and without naming the keyword.
off42
= FixedOffset(42, "42")
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)
self
.assertRaises(TypeError, meth
, tinfo
=off42
)
self
.assertRaises(TypeError, meth
, off42
, off42
)
# We don't know which time zone we're in, and don't have a tzinfo
# class to represent it, so seeing whether a tz argument actually
# does a conversion is tricky.
weirdtz
= FixedOffset(timedelta(hours
=15, minutes
=58), "weirdtz", 0)
utc
= FixedOffset(0, "utc", 0)
now
= datetime
.now(weirdtz
)
self
.failUnless(now
.tzinfo
is weirdtz
)
utcnow
= datetime
.utcnow().replace(tzinfo
=utc
)
now2
= utcnow
.astimezone(weirdtz
)
if abs(now
- now2
) < timedelta(seconds
=30):
# Else the code is broken, or more than 30 seconds passed between
# calls; assuming the latter, just try again.
# Three strikes and we're out.
self
.fail("utcnow(), now(tz), or astimezone() may be broken")
def test_tzinfo_fromtimestamp(self
):
meth
= self
.theclass
.fromtimestamp
# Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
# Try with and without naming the keyword.
off42
= FixedOffset(42, "42")
another
= meth(ts
, off42
)
again
= meth(ts
, tz
=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)
self
.assertRaises(TypeError, meth
, ts
, tinfo
=off42
)
self
.assertRaises(TypeError, meth
, ts
, off42
, off42
)
self
.assertRaises(TypeError, meth
)
# Try to make sure tz= actually does some conversion.
utcdatetime
= datetime
.utcfromtimestamp(timestamp
)
# In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
# But on some flavor of Mac, it's nowhere near that. So we can't have
# any idea here what time that actually is, we can only test that
# relative changes match.
utcoffset
= timedelta(hours
=-15, minutes
=39) # arbitrary, but not zero
tz
= FixedOffset(utcoffset
, "tz", 0)
expected
= utcdatetime
+ utcoffset
got
= datetime
.fromtimestamp(timestamp
, tz
)
self
.assertEqual(expected
, got
.replace(tzinfo
=None))
def test_tzinfo_utcnow(self
):
meth
= self
.theclass
.utcnow
# Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
# 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
):
meth
= self
.theclass
.utcfromtimestamp
# Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
# 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. datetime adds a twist to the
def __init__(self
, dstvalue
):
if isinstance(dstvalue
, int):
dstvalue
= timedelta(minutes
=dstvalue
)
for dstvalue
, flag
in (-33, 1), (33, 1), (0, 0), (None, -1):
d
= cls(1, 1, 1, 10, 20, 30, 40, tzinfo
=DST(dstvalue
))
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
)
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)
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
):
def __init__(self
, dstvalue
):
if isinstance(dstvalue
, int):
dstvalue
= timedelta(minutes
=dstvalue
)
# This can't work: DST didn't implement utcoffset.
self
.assertRaises(NotImplementedError,
cls(1, 1, 1, tzinfo
=DST(0)).utcoffset
)
def __init__(self
, uofs
, dofs
=None):
self
.uofs
= timedelta(minutes
=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
))
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,
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
tiny
= cls(MINYEAR
, 1, 1, 0, 0, 37, tzinfo
=UOFS(1439))
# That goes back 1 minute less than a full day.
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.
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, "")
for ofs
in None, zero
, plus
, minus
, unknown
:
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
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
)
z100
= FixedOffset(100, "+100")
zm200
= FixedOffset(timedelta(minutes
=-200), "-200")
args
= [1, 2, 3, 4, 5, 6, 7, z100
]
self
.assertEqual(base
, base
.replace())
for name
, newval
in (("year", 2),
got
= base
.replace(**{name
: newval
})
self
.assertEqual(expected
, got
)
# 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)
base3
= base2
.replace(tzinfo
=z100
)
self
.assertEqual(base
, base3
)
self
.failUnless(base
.tzinfo
is base3
.tzinfo
)
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(tz
=f44m
)
self
.failUnless(dt
.tzinfo
is f44m
)
# Replacing with degenerate tzinfo raises an exception.
self
.assertRaises(ValueError, dt
.astimezone
, fnone
)
self
.assertRaises(TypeError, dt
.astimezone
, None)
# Replacing with same tzinfo makes no change.
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
):
# Ensure that utcoffset() is ignored when the operands have the
class OperandDependentOffset(tzinfo
):
# d0 and d1 equal after adjustment
return timedelta(minutes
=t
.minute
)
return timedelta(minutes
=59)
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)
expected
= timedelta(minutes
=x
.minute
- y
.minute
)
self
.assertEqual(got
, expected
)
# OTOH, if the tzinfo members are distinct, utcoffsets aren't
base
= cls(8, 9, 10, 11, 12, 13, 14)
d0
= base
.replace(minute
=3, tzinfo
=OperandDependentOffset())
d1
= base
.replace(minute
=9, tzinfo
=OperandDependentOffset())
d2
= base
.replace(minute
=11, tzinfo
=OperandDependentOffset())
if (x
is d0
or x
is d1
) and (y
is d0
or y
is d1
):
expected
= timedelta(minutes
=(11-59)-0)
expected
= timedelta(minutes
=0-(11-59))
self
.assertEqual(got
, expected
)
def test_mixed_compare(self
):
t1
= datetime(1, 2, 3, 4, 5, 6, 7)
t2
= datetime(1, 2, 3, 4, 5, 6, 7)
t2
= t2
.replace(tzinfo
=None)
t2
= t2
.replace(tzinfo
=FixedOffset(None, ""))
t2
= t2
.replace(tzinfo
=FixedOffset(0, ""))
self
.assertRaises(TypeError, lambda: t1
== t2
)
# In datetime w/ identical tzinfo objects, utcoffset is ignored.
self
.offset
= timedelta(minutes
=22)
self
.offset
+= timedelta(minutes
=1)
t1
= t2
.replace(tzinfo
=v
)
t2
= t2
.replace(tzinfo
=v
)
self
.assertEqual(t1
.utcoffset(), timedelta(minutes
=23))
self
.assertEqual(t2
.utcoffset(), timedelta(minutes
=24))
# But if they're not identical, it isn't ignored.
t2
= t2
.replace(tzinfo
=Varies())
self
.failUnless(t1
< t2
) # t1's offset counter still going up
def test_subclass_datetimetz(self
):
def __new__(cls
, *args
, **kws
):
extra
= temp
.pop('extra')
result
= self
.theclass
.__new
__(cls
, *args
, **temp
)
def newmeth(self
, start
):
return start
+ self
.hour
+ self
.year
args
= 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
dt1
= self
.theclass(*args
)
dt2
= C(*args
, **{'extra': 7})
self
.assertEqual(dt2
.__class__
, C
)
self
.assertEqual(dt2
.theAnswer
, 42)
self
.assertEqual(dt2
.extra
, 7)
self
.assertEqual(dt1
.utcoffset(), dt2
.utcoffset())
self
.assertEqual(dt2
.newmeth(-7), dt1
.hour
+ dt1
.year
- 7)
# Pain to set up DST-aware tzinfo classes.
def first_sunday_on_or_after(dt
):
days_to_go
= 6 - dt
.weekday()
dt
+= timedelta(days_to_go
)
HOUR
= timedelta(hours
=1)
# In the US, DST starts at 2am (standard time) on the first Sunday in April.
DSTSTART
= datetime(1, 4, 1, 2)
# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
# which is the first Sunday on or after Oct 25. Because we view 1:MM as
# being standard time on that day, there is no spelling in local time of
# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
DSTEND
= datetime(1, 10, 25, 1)
class USTimeZone(tzinfo
):
def __init__(self
, hours
, reprname
, stdname
, dstname
):
self
.stdoffset
= timedelta(hours
=hours
)
return self
.stdoffset
+ self
.dst(dt
)
if dt
is None or dt
.tzinfo
is None:
# An exception instead may be sensible here, in one or more of
# Find first Sunday in April.
start
= first_sunday_on_or_after(DSTSTART
.replace(year
=dt
.year
))
assert start
.weekday() == 6 and start
.month
== 4 and start
.day
<= 7
# Find last Sunday in October.
end
= first_sunday_on_or_after(DSTEND
.replace(year
=dt
.year
))
assert end
.weekday() == 6 and end
.month
== 10 and end
.day
>= 25
# Can't compare naive to aware objects, so strip the timezone from
if start
<= dt
.replace(tzinfo
=None) < end
:
Eastern
= USTimeZone(-5, "Eastern", "EST", "EDT")
Central
= USTimeZone(-6, "Central", "CST", "CDT")
Mountain
= USTimeZone(-7, "Mountain", "MST", "MDT")
Pacific
= USTimeZone(-8, "Pacific", "PST", "PDT")
utc_real
= FixedOffset(0, "UTC", 0)
# For better test coverage, we want another flavor of UTC that's west of
# the Eastern and Pacific timezones.
utc_fake
= FixedOffset(-12*60, "UTCfake", 0)
class TestTimezoneConversions(unittest
.TestCase
):
# The DST switch times for 2002, in std time.
dston
= datetime(2002, 4, 7, 2)
dstoff
= datetime(2002, 10, 27, 1)
# Check a time that's inside DST.
def checkinside(self
, dt
, tz
, utc
, dston
, dstoff
):
self
.assertEqual(dt
.dst(), HOUR
)
# Conversion to our own timezone is always an identity.
self
.assertEqual(dt
.astimezone(tz
), dt
)
asutc
= dt
.astimezone(utc
)
there_and_back
= asutc
.astimezone(tz
)
# Conversion to UTC and back isn't always an identity here,
# because there are redundant spellings (in local time) of
# UTC time when DST begins: the clock jumps from 1:59:59
# to 3:00:00, and a local time of 2:MM:SS doesn't really
# make sense then. The classes above treat 2:MM:SS as
# daylight time then (it's "after 2am"), really an alias
# for 1:MM:SS standard time. The latter form is what
# conversion back from UTC produces.
if dt
.date() == dston
.date() and dt
.hour
== 2:
# We're in the redundant hour, and coming back from
# UTC gives the 1:MM:SS standard-time spelling.
self
.assertEqual(there_and_back
+ HOUR
, dt
)
# Although during was considered to be in daylight
# time, there_and_back is not.
self
.assertEqual(there_and_back
.dst(), ZERO
)
# They're the same times in UTC.
self
.assertEqual(there_and_back
.astimezone(utc
),
# We're not in the redundant hour.
self
.assertEqual(dt
, there_and_back
)
# Because we have a redundant spelling when DST begins, there is
# (unforunately) an hour when DST ends that can't be spelled at all in
# local time. When DST ends, the clock jumps from 1:59 back to 1:00
# again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
# standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
# daylight time. The hour 1:MM daylight == 0:MM standard can't be
# expressed in local time. Nevertheless, we want conversion back
# from UTC to mimic the local clock's "repeat an hour" behavior.
nexthour_utc
= asutc
+ HOUR
nexthour_tz
= nexthour_utc
.astimezone(tz
)
if dt
.date() == dstoff
.date() and dt
.hour
== 0:
# We're in the hour before the last DST hour. The last DST hour
# is ineffable. We want the conversion back to repeat 1:MM.
self
.assertEqual(nexthour_tz
, dt
.replace(hour
=1))
nexthour_tz
= nexthour_utc
.astimezone(tz
)
self
.assertEqual(nexthour_tz
, dt
.replace(hour
=1))
self
.assertEqual(nexthour_tz
- dt
, HOUR
)
# Check a time that's outside DST.
def checkoutside(self
, dt
, tz
, utc
):
self
.assertEqual(dt
.dst(), ZERO
)
# Conversion to our own timezone is always an identity.
self
.assertEqual(dt
.astimezone(tz
), dt
)
# Converting to UTC and back is an identity too.
asutc
= dt
.astimezone(utc
)
there_and_back
= asutc
.astimezone(tz
)
self
.assertEqual(dt
, there_and_back
)
def convert_between_tz_and_utc(self
, tz
, utc
):
dston
= self
.dston
.replace(tzinfo
=tz
)
# Because 1:MM on the day DST ends is taken as being standard time,
# there is no spelling in tz for the last hour of daylight time.
# For purposes of the test, the last hour of DST is 0:MM, which is
# taken as being daylight time (and 1:MM is taken as being standard
dstoff
= self
.dstoff
.replace(tzinfo
=tz
)
for delta
in (timedelta(weeks
=13),
timedelta(microseconds
=1)):
self
.checkinside(dston
, tz
, utc
, dston
, dstoff
)
for during
in dston
+ delta
, dstoff
- delta
:
self
.checkinside(during
, tz
, utc
, dston
, dstoff
)
self
.checkoutside(dstoff
, tz
, utc
)
for outside
in dston
- delta
, dstoff
+ delta
:
self
.checkoutside(outside
, tz
, utc
)
# Despite the name of this test, the endcases are excruciating.
self
.convert_between_tz_and_utc(Eastern
, utc_real
)
self
.convert_between_tz_and_utc(Pacific
, utc_real
)
self
.convert_between_tz_and_utc(Eastern
, utc_fake
)
self
.convert_between_tz_and_utc(Pacific
, utc_fake
)
# The next is really dancing near the edge. It works because
# Pacific and Eastern are far enough apart that their "problem
self
.convert_between_tz_and_utc(Eastern
, Pacific
)
self
.convert_between_tz_and_utc(Pacific
, Eastern
)
# OTOH, these fail! Don't enable them. The difficulty is that
# the edge case tests assume that every hour is representable in
# the "utc" class. This is always true for a fixed-offset tzinfo
# class (lke utc_real and utc_fake), but not for Eastern or Central.
# For these adjacent DST-aware time zones, the range of time offsets
# tested ends up creating hours in the one that aren't representable
# in the other. For the same reason, we would see failures in the
# Eastern vs Pacific tests too if we added 3*HOUR to the list of
# offset deltas in convert_between_tz_and_utc().
# self.convert_between_tz_and_utc(Eastern, Central) # can't work
# self.convert_between_tz_and_utc(Central, Eastern) # can't work
# 22:00 on day before daylight starts.
fourback
= self
.dston
- timedelta(hours
=4)
ninewest
= FixedOffset(-9*60, "-0900", 0)
fourback
= fourback
.replace(tzinfo
=ninewest
)
# 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
# 2", we should get the 3 spelling.
# If we plug 22:00 the day before into Eastern, it "looks like std
# time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
# to 22:00 lands on 2:00, which makes no sense in local time (the
# local clock jumps from 1 to 3). The point here is to make sure we
expected
= self
.dston
.replace(hour
=3)
got
= fourback
.astimezone(Eastern
).replace(tzinfo
=None)
self
.assertEqual(expected
, got
)
# Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
# case we want the 1:00 spelling.
sixutc
= self
.dston
.replace(hour
=6, tzinfo
=utc_real
)
# Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
# and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
expected
= self
.dston
.replace(hour
=1)
got
= sixutc
.astimezone(Eastern
).replace(tzinfo
=None)
self
.assertEqual(expected
, got
)
# Now on the day DST ends, we want "repeat an hour" behavior.
# UTC 4:MM 5:MM 6:MM 7:MM checking these
# EST 23:MM 0:MM 1:MM 2:MM
# EDT 0:MM 1:MM 2:MM 3:MM
# wall 0:MM 1:MM 1:MM 2:MM against these
for utc
in utc_real
, utc_fake
:
for tz
in Eastern
, Pacific
:
first_std_hour
= self
.dstoff
- timedelta(hours
=2) # 23:MM
first_std_hour
-= tz
.utcoffset(None)
# Adjust for possibly fake UTC.
asutc
= first_std_hour
+ utc
.utcoffset(None)
# First UTC hour to convert; this is 4:00 when utc=utc_real &
asutcbase
= asutc
.replace(tzinfo
=utc
)
for tzhour
in (0, 1, 1, 2):
expectedbase
= self
.dstoff
.replace(hour
=tzhour
)
expected
= expectedbase
.replace(minute
=minute
)
asutc
= asutcbase
.replace(minute
=minute
)
astz
= asutc
.astimezone(tz
)
self
.assertEqual(astz
.replace(tzinfo
=None), expected
)
def test_bogus_dst(self
):
def utcoffset(self
, dt
): return HOUR
def dst(self
, dt
): return HOUR
now
= self
.theclass
.now().replace(tzinfo
=utc_real
)
def dst(self
, dt
): return None
self
.assertRaises(ValueError, now
.astimezone
, notok())
self
.assertRaises(TypeError, Eastern
.fromutc
) # not enough args
now
= datetime
.utcnow().replace(tzinfo
=utc_real
)
self
.assertRaises(ValueError, Eastern
.fromutc
, now
) # wrong tzinfo
now
= now
.replace(tzinfo
=Eastern
) # insert correct tzinfo
enow
= Eastern
.fromutc(now
) # doesn't blow up
self
.assertEqual(enow
.tzinfo
, Eastern
) # has right tzinfo member
self
.assertRaises(TypeError, Eastern
.fromutc
, now
, now
) # too many args
self
.assertRaises(TypeError, Eastern
.fromutc
, date
.today()) # wrong type
# Always converts UTC to standard time.
class FauxUSTimeZone(USTimeZone
):
return dt
+ self
.stdoffset
FEastern
= FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
# UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
# EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
# EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
# Check around DST start.
start
= self
.dston
.replace(hour
=4, tzinfo
=Eastern
)
fstart
= start
.replace(tzinfo
=FEastern
)
for wall
in 23, 0, 1, 3, 4, 5:
expected
= start
.replace(hour
=wall
)
expected
-= timedelta(days
=1)
got
= Eastern
.fromutc(start
)
self
.assertEqual(expected
, got
)
expected
= fstart
+ FEastern
.stdoffset
got
= FEastern
.fromutc(fstart
)
self
.assertEqual(expected
, got
)
# Ensure astimezone() calls fromutc() too.
got
= fstart
.replace(tzinfo
=utc_real
).astimezone(FEastern
)
self
.assertEqual(expected
, got
)
start
= self
.dstoff
.replace(hour
=4, tzinfo
=Eastern
)
fstart
= start
.replace(tzinfo
=FEastern
)
for wall
in 0, 1, 1, 2, 3, 4:
expected
= start
.replace(hour
=wall
)
got
= Eastern
.fromutc(start
)
self
.assertEqual(expected
, got
)
expected
= fstart
+ FEastern
.stdoffset
got
= FEastern
.fromutc(fstart
)
self
.assertEqual(expected
, got
)
# Ensure astimezone() calls fromutc() too.
got
= fstart
.replace(tzinfo
=utc_real
).astimezone(FEastern
)
self
.assertEqual(expected
, got
)
#############################################################################
class Oddballs(unittest
.TestCase
):
def test_bug_1028306(self
):
# Trying to compare a date to a datetime should act like a mixed-
# type comparison, despite that datetime is a subclass of date.
as_datetime
= datetime
.combine(as_date
, time())
self
.assert_(as_date
!= as_datetime
)
self
.assert_(as_datetime
!= as_date
)
self
.assert_(not as_date
== as_datetime
)
self
.assert_(not as_datetime
== as_date
)
self
.assertRaises(TypeError, lambda: as_date
< as_datetime
)
self
.assertRaises(TypeError, lambda: as_datetime
< as_date
)
self
.assertRaises(TypeError, lambda: as_date
<= as_datetime
)
self
.assertRaises(TypeError, lambda: as_datetime
<= as_date
)
self
.assertRaises(TypeError, lambda: as_date
> as_datetime
)
self
.assertRaises(TypeError, lambda: as_datetime
> as_date
)
self
.assertRaises(TypeError, lambda: as_date
>= as_datetime
)
self
.assertRaises(TypeError, lambda: as_datetime
>= as_date
)
# Neverthelss, comparison should work with the base-class (date)
# projection if use of a date method is forced.
self
.assert_(as_date
.__eq
__(as_datetime
))
different_day
= (as_date
.day
+ 1) % 20 + 1
self
.assert_(not as_date
.__eq
__(as_datetime
.replace(day
=
# And date should compare with other subclasses of date. If a
# subclass wants to stop this, it's up to the subclass to do so.
date_sc
= SubclassDate(as_date
.year
, as_date
.month
, as_date
.day
)
self
.assertEqual(as_date
, date_sc
)
self
.assertEqual(date_sc
, as_date
)
datetime_sc
= SubclassDatetime(as_datetime
.year
, as_datetime
.month
,
self
.assertEqual(as_datetime
, datetime_sc
)
self
.assertEqual(datetime_sc
, as_datetime
)
allsuites
= [unittest
.makeSuite(klass
, 'test')
for klass
in (TestModule
,
return unittest
.TestSuite(allsuites
)
test_support
.run_suite(thesuite
)
if 1: # change to 0, under a debug build, for some leak detection
raise SystemError("gc.garbage not empty after test run: %r" %
if hasattr(sys
, 'gettotalrefcount'):
thisrc
= sys
.gettotalrefcount()
print >> sys
.stderr
, '*' * 10, 'total refs:', thisrc
,
print >> sys
.stderr
, 'delta:', thisrc
- lastrc
if __name__
== "__main__":