blob: 40eb3ab92dde5e7c2d4c7729d1c3492551a8df12 [file] [log] [blame]
Tim Peters0bf60bd2003-01-08 20:40:01 +00001"""Test date/time type.
2
3See http://www.zope.org/Members/fdrake/DateTimeWiki/TestCases
4"""
Tim Peters2a799bf2002-12-16 20:18:38 +00005
Georg Brandl0c4f3fd2007-03-07 16:12:05 +00006import os
Tim Peters2a799bf2002-12-16 20:18:38 +00007import sys
Guido van Rossum177e41a2003-01-30 22:06:23 +00008import pickle
9import cPickle
Tim Peters2a799bf2002-12-16 20:18:38 +000010import unittest
11
12from test import test_support
13
14from datetime import MINYEAR, MAXYEAR
15from datetime import timedelta
16from datetime import tzinfo
Tim Peters0bf60bd2003-01-08 20:40:01 +000017from datetime import time
18from datetime import date, datetime
19
Tim Peters35ad6412003-02-05 04:08:07 +000020pickle_choices = [(pickler, unpickler, proto)
21 for pickler in pickle, cPickle
22 for unpickler in pickle, cPickle
23 for proto in range(3)]
24assert len(pickle_choices) == 2*2*3
Guido van Rossum177e41a2003-01-30 22:06:23 +000025
Tim Peters68124bb2003-02-08 03:46:31 +000026# An arbitrary collection of objects of non-datetime types, for testing
27# mixed-type comparisons.
28OTHERSTUFF = (10, 10L, 34.5, "abc", {}, [], ())
Tim Peters0bf60bd2003-01-08 20:40:01 +000029
Tim Peters2a799bf2002-12-16 20:18:38 +000030
31#############################################################################
32# module tests
33
34class TestModule(unittest.TestCase):
35
36 def test_constants(self):
37 import datetime
38 self.assertEqual(datetime.MINYEAR, 1)
39 self.assertEqual(datetime.MAXYEAR, 9999)
40
41#############################################################################
42# tzinfo tests
43
44class FixedOffset(tzinfo):
45 def __init__(self, offset, name, dstoffset=42):
Tim Peters397301e2003-01-02 21:28:08 +000046 if isinstance(offset, int):
47 offset = timedelta(minutes=offset)
48 if isinstance(dstoffset, int):
49 dstoffset = timedelta(minutes=dstoffset)
Tim Peters2a799bf2002-12-16 20:18:38 +000050 self.__offset = offset
51 self.__name = name
52 self.__dstoffset = dstoffset
53 def __repr__(self):
54 return self.__name.lower()
55 def utcoffset(self, dt):
56 return self.__offset
57 def tzname(self, dt):
58 return self.__name
59 def dst(self, dt):
60 return self.__dstoffset
61
Tim Petersfb8472c2002-12-21 05:04:42 +000062class PicklableFixedOffset(FixedOffset):
63 def __init__(self, offset=None, name=None, dstoffset=None):
64 FixedOffset.__init__(self, offset, name, dstoffset)
65
Tim Peters2a799bf2002-12-16 20:18:38 +000066class TestTZInfo(unittest.TestCase):
67
68 def test_non_abstractness(self):
69 # In order to allow subclasses to get pickled, the C implementation
70 # wasn't able to get away with having __init__ raise
71 # NotImplementedError.
72 useless = tzinfo()
73 dt = datetime.max
74 self.assertRaises(NotImplementedError, useless.tzname, dt)
75 self.assertRaises(NotImplementedError, useless.utcoffset, dt)
76 self.assertRaises(NotImplementedError, useless.dst, dt)
77
78 def test_subclass_must_override(self):
79 class NotEnough(tzinfo):
80 def __init__(self, offset, name):
81 self.__offset = offset
82 self.__name = name
83 self.failUnless(issubclass(NotEnough, tzinfo))
84 ne = NotEnough(3, "NotByALongShot")
85 self.failUnless(isinstance(ne, tzinfo))
86
87 dt = datetime.now()
88 self.assertRaises(NotImplementedError, ne.tzname, dt)
89 self.assertRaises(NotImplementedError, ne.utcoffset, dt)
90 self.assertRaises(NotImplementedError, ne.dst, dt)
91
92 def test_normal(self):
93 fo = FixedOffset(3, "Three")
94 self.failUnless(isinstance(fo, tzinfo))
95 for dt in datetime.now(), None:
Tim Peters397301e2003-01-02 21:28:08 +000096 self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +000097 self.assertEqual(fo.tzname(dt), "Three")
Tim Peters397301e2003-01-02 21:28:08 +000098 self.assertEqual(fo.dst(dt), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +000099
100 def test_pickling_base(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000101 # There's no point to pickling tzinfo objects on their own (they
102 # carry no data), but they need to be picklable anyway else
103 # concrete subclasses can't be pickled.
104 orig = tzinfo.__new__(tzinfo)
105 self.failUnless(type(orig) is tzinfo)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000106 for pickler, unpickler, proto in pickle_choices:
Tim Petersf2715e02003-02-19 02:35:07 +0000107 green = pickler.dumps(orig, proto)
108 derived = unpickler.loads(green)
109 self.failUnless(type(derived) is tzinfo)
Tim Peters2a799bf2002-12-16 20:18:38 +0000110
111 def test_pickling_subclass(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000112 # Make sure we can pickle/unpickle an instance of a subclass.
Tim Peters397301e2003-01-02 21:28:08 +0000113 offset = timedelta(minutes=-300)
114 orig = PicklableFixedOffset(offset, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +0000115 self.failUnless(isinstance(orig, tzinfo))
Tim Petersfb8472c2002-12-21 05:04:42 +0000116 self.failUnless(type(orig) is PicklableFixedOffset)
Tim Peters397301e2003-01-02 21:28:08 +0000117 self.assertEqual(orig.utcoffset(None), offset)
Tim Peters2a799bf2002-12-16 20:18:38 +0000118 self.assertEqual(orig.tzname(None), 'cookie')
Guido van Rossum177e41a2003-01-30 22:06:23 +0000119 for pickler, unpickler, proto in pickle_choices:
Tim Petersf2715e02003-02-19 02:35:07 +0000120 green = pickler.dumps(orig, proto)
121 derived = unpickler.loads(green)
122 self.failUnless(isinstance(derived, tzinfo))
123 self.failUnless(type(derived) is PicklableFixedOffset)
124 self.assertEqual(derived.utcoffset(None), offset)
125 self.assertEqual(derived.tzname(None), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +0000126
127#############################################################################
Tim Peters07534a62003-02-07 22:50:28 +0000128# Base clase for testing a particular aspect of timedelta, time, date and
129# datetime comparisons.
130
131class HarmlessMixedComparison(unittest.TestCase):
132 # Test that __eq__ and __ne__ don't complain for mixed-type comparisons.
133
134 # Subclasses must define 'theclass', and theclass(1, 1, 1) must be a
135 # legit constructor.
136
137 def test_harmless_mixed_comparison(self):
138 me = self.theclass(1, 1, 1)
139
140 self.failIf(me == ())
141 self.failUnless(me != ())
142 self.failIf(() == me)
143 self.failUnless(() != me)
144
145 self.failUnless(me in [1, 20L, [], me])
146 self.failIf(me not in [1, 20L, [], me])
147
148 self.failUnless([] in [me, 1, 20L, []])
149 self.failIf([] not in [me, 1, 20L, []])
150
151 def test_harmful_mixed_comparison(self):
152 me = self.theclass(1, 1, 1)
153
154 self.assertRaises(TypeError, lambda: me < ())
155 self.assertRaises(TypeError, lambda: me <= ())
156 self.assertRaises(TypeError, lambda: me > ())
157 self.assertRaises(TypeError, lambda: me >= ())
158
159 self.assertRaises(TypeError, lambda: () < me)
160 self.assertRaises(TypeError, lambda: () <= me)
161 self.assertRaises(TypeError, lambda: () > me)
162 self.assertRaises(TypeError, lambda: () >= me)
163
164 self.assertRaises(TypeError, cmp, (), me)
165 self.assertRaises(TypeError, cmp, me, ())
166
167#############################################################################
Tim Peters2a799bf2002-12-16 20:18:38 +0000168# timedelta tests
169
Tim Peters07534a62003-02-07 22:50:28 +0000170class TestTimeDelta(HarmlessMixedComparison):
171
172 theclass = timedelta
Tim Peters2a799bf2002-12-16 20:18:38 +0000173
174 def test_constructor(self):
175 eq = self.assertEqual
176 td = timedelta
177
178 # Check keyword args to constructor
179 eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
180 milliseconds=0, microseconds=0))
181 eq(td(1), td(days=1))
182 eq(td(0, 1), td(seconds=1))
183 eq(td(0, 0, 1), td(microseconds=1))
184 eq(td(weeks=1), td(days=7))
185 eq(td(days=1), td(hours=24))
186 eq(td(hours=1), td(minutes=60))
187 eq(td(minutes=1), td(seconds=60))
188 eq(td(seconds=1), td(milliseconds=1000))
189 eq(td(milliseconds=1), td(microseconds=1000))
190
191 # Check float args to constructor
192 eq(td(weeks=1.0/7), td(days=1))
193 eq(td(days=1.0/24), td(hours=1))
194 eq(td(hours=1.0/60), td(minutes=1))
195 eq(td(minutes=1.0/60), td(seconds=1))
196 eq(td(seconds=0.001), td(milliseconds=1))
197 eq(td(milliseconds=0.001), td(microseconds=1))
198
199 def test_computations(self):
200 eq = self.assertEqual
201 td = timedelta
202
203 a = td(7) # One week
204 b = td(0, 60) # One minute
205 c = td(0, 0, 1000) # One millisecond
206 eq(a+b+c, td(7, 60, 1000))
207 eq(a-b, td(6, 24*3600 - 60))
208 eq(-a, td(-7))
209 eq(+a, td(7))
210 eq(-b, td(-1, 24*3600 - 60))
211 eq(-c, td(-1, 24*3600 - 1, 999000))
212 eq(abs(a), a)
213 eq(abs(-a), a)
214 eq(td(6, 24*3600), a)
215 eq(td(0, 0, 60*1000000), b)
216 eq(a*10, td(70))
217 eq(a*10, 10*a)
218 eq(a*10L, 10*a)
219 eq(b*10, td(0, 600))
220 eq(10*b, td(0, 600))
221 eq(b*10L, td(0, 600))
222 eq(c*10, td(0, 0, 10000))
223 eq(10*c, td(0, 0, 10000))
224 eq(c*10L, td(0, 0, 10000))
225 eq(a*-1, -a)
226 eq(b*-2, -b-b)
227 eq(c*-2, -c+-c)
228 eq(b*(60*24), (b*60)*24)
229 eq(b*(60*24), (60*b)*24)
230 eq(c*1000, td(0, 1))
231 eq(1000*c, td(0, 1))
232 eq(a//7, td(1))
233 eq(b//10, td(0, 6))
234 eq(c//1000, td(0, 0, 1))
235 eq(a//10, td(0, 7*24*360))
236 eq(a//3600000, td(0, 0, 7*24*1000))
237
238 def test_disallowed_computations(self):
239 a = timedelta(42)
240
241 # Add/sub ints, longs, floats should be illegal
242 for i in 1, 1L, 1.0:
243 self.assertRaises(TypeError, lambda: a+i)
244 self.assertRaises(TypeError, lambda: a-i)
245 self.assertRaises(TypeError, lambda: i+a)
246 self.assertRaises(TypeError, lambda: i-a)
247
248 # Mul/div by float isn't supported.
249 x = 2.3
250 self.assertRaises(TypeError, lambda: a*x)
251 self.assertRaises(TypeError, lambda: x*a)
252 self.assertRaises(TypeError, lambda: a/x)
253 self.assertRaises(TypeError, lambda: x/a)
254 self.assertRaises(TypeError, lambda: a // x)
255 self.assertRaises(TypeError, lambda: x // a)
256
257 # Divison of int by timedelta doesn't make sense.
258 # Division by zero doesn't make sense.
259 for zero in 0, 0L:
260 self.assertRaises(TypeError, lambda: zero // a)
261 self.assertRaises(ZeroDivisionError, lambda: a // zero)
262
263 def test_basic_attributes(self):
264 days, seconds, us = 1, 7, 31
265 td = timedelta(days, seconds, us)
266 self.assertEqual(td.days, days)
267 self.assertEqual(td.seconds, seconds)
268 self.assertEqual(td.microseconds, us)
269
270 def test_carries(self):
271 t1 = timedelta(days=100,
272 weeks=-7,
273 hours=-24*(100-49),
274 minutes=-3,
275 seconds=12,
276 microseconds=(3*60 - 12) * 1e6 + 1)
277 t2 = timedelta(microseconds=1)
278 self.assertEqual(t1, t2)
279
280 def test_hash_equality(self):
281 t1 = timedelta(days=100,
282 weeks=-7,
283 hours=-24*(100-49),
284 minutes=-3,
285 seconds=12,
286 microseconds=(3*60 - 12) * 1000000)
287 t2 = timedelta()
288 self.assertEqual(hash(t1), hash(t2))
289
290 t1 += timedelta(weeks=7)
291 t2 += timedelta(days=7*7)
292 self.assertEqual(t1, t2)
293 self.assertEqual(hash(t1), hash(t2))
294
295 d = {t1: 1}
296 d[t2] = 2
297 self.assertEqual(len(d), 1)
298 self.assertEqual(d[t1], 2)
299
300 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000301 args = 12, 34, 56
302 orig = timedelta(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000303 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +0000304 green = pickler.dumps(orig, proto)
305 derived = unpickler.loads(green)
306 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +0000307
308 def test_compare(self):
309 t1 = timedelta(2, 3, 4)
310 t2 = timedelta(2, 3, 4)
311 self.failUnless(t1 == t2)
312 self.failUnless(t1 <= t2)
313 self.failUnless(t1 >= t2)
314 self.failUnless(not t1 != t2)
315 self.failUnless(not t1 < t2)
316 self.failUnless(not t1 > t2)
317 self.assertEqual(cmp(t1, t2), 0)
318 self.assertEqual(cmp(t2, t1), 0)
319
320 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
321 t2 = timedelta(*args) # this is larger than t1
322 self.failUnless(t1 < t2)
323 self.failUnless(t2 > t1)
324 self.failUnless(t1 <= t2)
325 self.failUnless(t2 >= t1)
326 self.failUnless(t1 != t2)
327 self.failUnless(t2 != t1)
328 self.failUnless(not t1 == t2)
329 self.failUnless(not t2 == t1)
330 self.failUnless(not t1 > t2)
331 self.failUnless(not t2 < t1)
332 self.failUnless(not t1 >= t2)
333 self.failUnless(not t2 <= t1)
334 self.assertEqual(cmp(t1, t2), -1)
335 self.assertEqual(cmp(t2, t1), 1)
336
Tim Peters68124bb2003-02-08 03:46:31 +0000337 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +0000338 self.assertEqual(t1 == badarg, False)
339 self.assertEqual(t1 != badarg, True)
340 self.assertEqual(badarg == t1, False)
341 self.assertEqual(badarg != t1, True)
342
Tim Peters2a799bf2002-12-16 20:18:38 +0000343 self.assertRaises(TypeError, lambda: t1 <= badarg)
344 self.assertRaises(TypeError, lambda: t1 < badarg)
345 self.assertRaises(TypeError, lambda: t1 > badarg)
346 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +0000347 self.assertRaises(TypeError, lambda: badarg <= t1)
348 self.assertRaises(TypeError, lambda: badarg < t1)
349 self.assertRaises(TypeError, lambda: badarg > t1)
350 self.assertRaises(TypeError, lambda: badarg >= t1)
351
352 def test_str(self):
353 td = timedelta
354 eq = self.assertEqual
355
356 eq(str(td(1)), "1 day, 0:00:00")
357 eq(str(td(-1)), "-1 day, 0:00:00")
358 eq(str(td(2)), "2 days, 0:00:00")
359 eq(str(td(-2)), "-2 days, 0:00:00")
360
361 eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
362 eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
363 eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
364 "-210 days, 23:12:34")
365
366 eq(str(td(milliseconds=1)), "0:00:00.001000")
367 eq(str(td(microseconds=3)), "0:00:00.000003")
368
369 eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
370 microseconds=999999)),
371 "999999999 days, 23:59:59.999999")
372
373 def test_roundtrip(self):
374 for td in (timedelta(days=999999999, hours=23, minutes=59,
375 seconds=59, microseconds=999999),
376 timedelta(days=-999999999),
377 timedelta(days=1, seconds=2, microseconds=3)):
378
379 # Verify td -> string -> td identity.
380 s = repr(td)
381 self.failUnless(s.startswith('datetime.'))
382 s = s[9:]
383 td2 = eval(s)
384 self.assertEqual(td, td2)
385
386 # Verify identity via reconstructing from pieces.
387 td2 = timedelta(td.days, td.seconds, td.microseconds)
388 self.assertEqual(td, td2)
389
390 def test_resolution_info(self):
391 self.assert_(isinstance(timedelta.min, timedelta))
392 self.assert_(isinstance(timedelta.max, timedelta))
393 self.assert_(isinstance(timedelta.resolution, timedelta))
394 self.assert_(timedelta.max > timedelta.min)
395 self.assertEqual(timedelta.min, timedelta(-999999999))
396 self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
397 self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
398
399 def test_overflow(self):
400 tiny = timedelta.resolution
401
402 td = timedelta.min + tiny
403 td -= tiny # no problem
404 self.assertRaises(OverflowError, td.__sub__, tiny)
405 self.assertRaises(OverflowError, td.__add__, -tiny)
406
407 td = timedelta.max - tiny
408 td += tiny # no problem
409 self.assertRaises(OverflowError, td.__add__, tiny)
410 self.assertRaises(OverflowError, td.__sub__, -tiny)
411
412 self.assertRaises(OverflowError, lambda: -timedelta.max)
413
414 def test_microsecond_rounding(self):
415 td = timedelta
416 eq = self.assertEqual
417
418 # Single-field rounding.
419 eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
420 eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
421 eq(td(milliseconds=0.6/1000), td(microseconds=1))
422 eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
423
424 # Rounding due to contributions from more than one field.
425 us_per_hour = 3600e6
426 us_per_day = us_per_hour * 24
427 eq(td(days=.4/us_per_day), td(0))
428 eq(td(hours=.2/us_per_hour), td(0))
429 eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
430
431 eq(td(days=-.4/us_per_day), td(0))
432 eq(td(hours=-.2/us_per_hour), td(0))
433 eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
434
435 def test_massive_normalization(self):
436 td = timedelta(microseconds=-1)
437 self.assertEqual((td.days, td.seconds, td.microseconds),
438 (-1, 24*3600-1, 999999))
439
440 def test_bool(self):
441 self.failUnless(timedelta(1))
442 self.failUnless(timedelta(0, 1))
443 self.failUnless(timedelta(0, 0, 1))
444 self.failUnless(timedelta(microseconds=1))
445 self.failUnless(not timedelta(0))
446
Tim Petersb0c854d2003-05-17 15:57:00 +0000447 def test_subclass_timedelta(self):
448
449 class T(timedelta):
Guido van Rossum5a8a0372005-01-16 00:25:31 +0000450 @staticmethod
Tim Petersb0c854d2003-05-17 15:57:00 +0000451 def from_td(td):
452 return T(td.days, td.seconds, td.microseconds)
Tim Petersb0c854d2003-05-17 15:57:00 +0000453
454 def as_hours(self):
455 sum = (self.days * 24 +
456 self.seconds / 3600.0 +
457 self.microseconds / 3600e6)
458 return round(sum)
459
460 t1 = T(days=1)
461 self.assert_(type(t1) is T)
462 self.assertEqual(t1.as_hours(), 24)
463
464 t2 = T(days=-1, seconds=-3600)
465 self.assert_(type(t2) is T)
466 self.assertEqual(t2.as_hours(), -25)
467
468 t3 = t1 + t2
469 self.assert_(type(t3) is timedelta)
470 t4 = T.from_td(t3)
471 self.assert_(type(t4) is T)
472 self.assertEqual(t3.days, t4.days)
473 self.assertEqual(t3.seconds, t4.seconds)
474 self.assertEqual(t3.microseconds, t4.microseconds)
475 self.assertEqual(str(t3), str(t4))
476 self.assertEqual(t4.as_hours(), -1)
477
Tim Peters2a799bf2002-12-16 20:18:38 +0000478#############################################################################
479# date tests
480
481class TestDateOnly(unittest.TestCase):
482 # Tests here won't pass if also run on datetime objects, so don't
483 # subclass this to test datetimes too.
484
485 def test_delta_non_days_ignored(self):
486 dt = date(2000, 1, 2)
487 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
488 microseconds=5)
489 days = timedelta(delta.days)
490 self.assertEqual(days, timedelta(1))
491
492 dt2 = dt + delta
493 self.assertEqual(dt2, dt + days)
494
495 dt2 = delta + dt
496 self.assertEqual(dt2, dt + days)
497
498 dt2 = dt - delta
499 self.assertEqual(dt2, dt - days)
500
501 delta = -delta
502 days = timedelta(delta.days)
503 self.assertEqual(days, timedelta(-2))
504
505 dt2 = dt + delta
506 self.assertEqual(dt2, dt + days)
507
508 dt2 = delta + dt
509 self.assertEqual(dt2, dt + days)
510
511 dt2 = dt - delta
512 self.assertEqual(dt2, dt - days)
513
Tim Peters604c0132004-06-07 23:04:33 +0000514class SubclassDate(date):
515 sub_var = 1
516
Tim Peters07534a62003-02-07 22:50:28 +0000517class TestDate(HarmlessMixedComparison):
Tim Peters2a799bf2002-12-16 20:18:38 +0000518 # Tests here should pass for both dates and datetimes, except for a
519 # few tests that TestDateTime overrides.
520
521 theclass = date
522
523 def test_basic_attributes(self):
524 dt = self.theclass(2002, 3, 1)
525 self.assertEqual(dt.year, 2002)
526 self.assertEqual(dt.month, 3)
527 self.assertEqual(dt.day, 1)
528
529 def test_roundtrip(self):
530 for dt in (self.theclass(1, 2, 3),
531 self.theclass.today()):
532 # Verify dt -> string -> date identity.
533 s = repr(dt)
534 self.failUnless(s.startswith('datetime.'))
535 s = s[9:]
536 dt2 = eval(s)
537 self.assertEqual(dt, dt2)
538
539 # Verify identity via reconstructing from pieces.
540 dt2 = self.theclass(dt.year, dt.month, dt.day)
541 self.assertEqual(dt, dt2)
542
543 def test_ordinal_conversions(self):
544 # Check some fixed values.
545 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
546 (1, 12, 31, 365),
547 (2, 1, 1, 366),
548 # first example from "Calendrical Calculations"
549 (1945, 11, 12, 710347)]:
550 d = self.theclass(y, m, d)
551 self.assertEqual(n, d.toordinal())
552 fromord = self.theclass.fromordinal(n)
553 self.assertEqual(d, fromord)
554 if hasattr(fromord, "hour"):
Tim Petersf2715e02003-02-19 02:35:07 +0000555 # if we're checking something fancier than a date, verify
556 # the extra fields have been zeroed out
Tim Peters2a799bf2002-12-16 20:18:38 +0000557 self.assertEqual(fromord.hour, 0)
558 self.assertEqual(fromord.minute, 0)
559 self.assertEqual(fromord.second, 0)
560 self.assertEqual(fromord.microsecond, 0)
561
Tim Peters0bf60bd2003-01-08 20:40:01 +0000562 # Check first and last days of year spottily across the whole
563 # range of years supported.
564 for year in xrange(MINYEAR, MAXYEAR+1, 7):
Tim Peters2a799bf2002-12-16 20:18:38 +0000565 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
566 d = self.theclass(year, 1, 1)
567 n = d.toordinal()
568 d2 = self.theclass.fromordinal(n)
569 self.assertEqual(d, d2)
Tim Peters0bf60bd2003-01-08 20:40:01 +0000570 # Verify that moving back a day gets to the end of year-1.
571 if year > 1:
572 d = self.theclass.fromordinal(n-1)
573 d2 = self.theclass(year-1, 12, 31)
574 self.assertEqual(d, d2)
575 self.assertEqual(d2.toordinal(), n-1)
Tim Peters2a799bf2002-12-16 20:18:38 +0000576
577 # Test every day in a leap-year and a non-leap year.
578 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
579 for year, isleap in (2000, True), (2002, False):
580 n = self.theclass(year, 1, 1).toordinal()
581 for month, maxday in zip(range(1, 13), dim):
582 if month == 2 and isleap:
583 maxday += 1
584 for day in range(1, maxday+1):
585 d = self.theclass(year, month, day)
586 self.assertEqual(d.toordinal(), n)
587 self.assertEqual(d, self.theclass.fromordinal(n))
588 n += 1
589
590 def test_extreme_ordinals(self):
591 a = self.theclass.min
592 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
593 aord = a.toordinal()
594 b = a.fromordinal(aord)
595 self.assertEqual(a, b)
596
597 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
598
599 b = a + timedelta(days=1)
600 self.assertEqual(b.toordinal(), aord + 1)
601 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
602
603 a = self.theclass.max
604 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
605 aord = a.toordinal()
606 b = a.fromordinal(aord)
607 self.assertEqual(a, b)
608
609 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
610
611 b = a - timedelta(days=1)
612 self.assertEqual(b.toordinal(), aord - 1)
613 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
614
615 def test_bad_constructor_arguments(self):
616 # bad years
617 self.theclass(MINYEAR, 1, 1) # no exception
618 self.theclass(MAXYEAR, 1, 1) # no exception
619 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
620 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
621 # bad months
622 self.theclass(2000, 1, 1) # no exception
623 self.theclass(2000, 12, 1) # no exception
624 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
625 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
626 # bad days
627 self.theclass(2000, 2, 29) # no exception
628 self.theclass(2004, 2, 29) # no exception
629 self.theclass(2400, 2, 29) # no exception
630 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
631 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
632 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
633 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
634 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
635 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
636
637 def test_hash_equality(self):
638 d = self.theclass(2000, 12, 31)
639 # same thing
640 e = self.theclass(2000, 12, 31)
641 self.assertEqual(d, e)
642 self.assertEqual(hash(d), hash(e))
643
644 dic = {d: 1}
645 dic[e] = 2
646 self.assertEqual(len(dic), 1)
647 self.assertEqual(dic[d], 2)
648 self.assertEqual(dic[e], 2)
649
650 d = self.theclass(2001, 1, 1)
651 # same thing
652 e = self.theclass(2001, 1, 1)
653 self.assertEqual(d, e)
654 self.assertEqual(hash(d), hash(e))
655
656 dic = {d: 1}
657 dic[e] = 2
658 self.assertEqual(len(dic), 1)
659 self.assertEqual(dic[d], 2)
660 self.assertEqual(dic[e], 2)
661
662 def test_computations(self):
663 a = self.theclass(2002, 1, 31)
664 b = self.theclass(1956, 1, 31)
665
666 diff = a-b
667 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
668 self.assertEqual(diff.seconds, 0)
669 self.assertEqual(diff.microseconds, 0)
670
671 day = timedelta(1)
672 week = timedelta(7)
673 a = self.theclass(2002, 3, 2)
674 self.assertEqual(a + day, self.theclass(2002, 3, 3))
675 self.assertEqual(day + a, self.theclass(2002, 3, 3))
676 self.assertEqual(a - day, self.theclass(2002, 3, 1))
677 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
678 self.assertEqual(a + week, self.theclass(2002, 3, 9))
679 self.assertEqual(a - week, self.theclass(2002, 2, 23))
680 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
681 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
682 self.assertEqual((a + week) - a, week)
683 self.assertEqual((a + day) - a, day)
684 self.assertEqual((a - week) - a, -week)
685 self.assertEqual((a - day) - a, -day)
686 self.assertEqual(a - (a + week), -week)
687 self.assertEqual(a - (a + day), -day)
688 self.assertEqual(a - (a - week), week)
689 self.assertEqual(a - (a - day), day)
690
691 # Add/sub ints, longs, floats should be illegal
692 for i in 1, 1L, 1.0:
693 self.assertRaises(TypeError, lambda: a+i)
694 self.assertRaises(TypeError, lambda: a-i)
695 self.assertRaises(TypeError, lambda: i+a)
696 self.assertRaises(TypeError, lambda: i-a)
697
698 # delta - date is senseless.
699 self.assertRaises(TypeError, lambda: day - a)
700 # mixing date and (delta or date) via * or // is senseless
701 self.assertRaises(TypeError, lambda: day * a)
702 self.assertRaises(TypeError, lambda: a * day)
703 self.assertRaises(TypeError, lambda: day // a)
704 self.assertRaises(TypeError, lambda: a // day)
705 self.assertRaises(TypeError, lambda: a * a)
706 self.assertRaises(TypeError, lambda: a // a)
707 # date + date is senseless
708 self.assertRaises(TypeError, lambda: a + a)
709
710 def test_overflow(self):
711 tiny = self.theclass.resolution
712
713 dt = self.theclass.min + tiny
714 dt -= tiny # no problem
715 self.assertRaises(OverflowError, dt.__sub__, tiny)
716 self.assertRaises(OverflowError, dt.__add__, -tiny)
717
718 dt = self.theclass.max - tiny
719 dt += tiny # no problem
720 self.assertRaises(OverflowError, dt.__add__, tiny)
721 self.assertRaises(OverflowError, dt.__sub__, -tiny)
722
723 def test_fromtimestamp(self):
724 import time
725
726 # Try an arbitrary fixed value.
727 year, month, day = 1999, 9, 19
728 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
729 d = self.theclass.fromtimestamp(ts)
730 self.assertEqual(d.year, year)
731 self.assertEqual(d.month, month)
732 self.assertEqual(d.day, day)
733
Tim Peters1b6f7a92004-06-20 02:50:16 +0000734 def test_insane_fromtimestamp(self):
735 # It's possible that some platform maps time_t to double,
736 # and that this test will fail there. This test should
737 # exempt such platforms (provided they return reasonable
738 # results!).
739 for insane in -1e200, 1e200:
740 self.assertRaises(ValueError, self.theclass.fromtimestamp,
741 insane)
742
Tim Peters2a799bf2002-12-16 20:18:38 +0000743 def test_today(self):
744 import time
745
746 # We claim that today() is like fromtimestamp(time.time()), so
747 # prove it.
748 for dummy in range(3):
749 today = self.theclass.today()
750 ts = time.time()
751 todayagain = self.theclass.fromtimestamp(ts)
752 if today == todayagain:
753 break
754 # There are several legit reasons that could fail:
755 # 1. It recently became midnight, between the today() and the
756 # time() calls.
757 # 2. The platform time() has such fine resolution that we'll
758 # never get the same value twice.
759 # 3. The platform time() has poor resolution, and we just
760 # happened to call today() right before a resolution quantum
761 # boundary.
762 # 4. The system clock got fiddled between calls.
763 # In any case, wait a little while and try again.
764 time.sleep(0.1)
765
766 # It worked or it didn't. If it didn't, assume it's reason #2, and
767 # let the test pass if they're within half a second of each other.
768 self.failUnless(today == todayagain or
769 abs(todayagain - today) < timedelta(seconds=0.5))
770
771 def test_weekday(self):
772 for i in range(7):
773 # March 4, 2002 is a Monday
774 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
775 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
776 # January 2, 1956 is a Monday
777 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
778 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
779
780 def test_isocalendar(self):
781 # Check examples from
782 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
783 for i in range(7):
784 d = self.theclass(2003, 12, 22+i)
785 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
786 d = self.theclass(2003, 12, 29) + timedelta(i)
787 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
788 d = self.theclass(2004, 1, 5+i)
789 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
790 d = self.theclass(2009, 12, 21+i)
791 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
792 d = self.theclass(2009, 12, 28) + timedelta(i)
793 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
794 d = self.theclass(2010, 1, 4+i)
795 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
796
797 def test_iso_long_years(self):
798 # Calculate long ISO years and compare to table from
799 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
800 ISO_LONG_YEARS_TABLE = """
801 4 32 60 88
802 9 37 65 93
803 15 43 71 99
804 20 48 76
805 26 54 82
806
807 105 133 161 189
808 111 139 167 195
809 116 144 172
810 122 150 178
811 128 156 184
812
813 201 229 257 285
814 207 235 263 291
815 212 240 268 296
816 218 246 274
817 224 252 280
818
819 303 331 359 387
820 308 336 364 392
821 314 342 370 398
822 320 348 376
823 325 353 381
824 """
825 iso_long_years = map(int, ISO_LONG_YEARS_TABLE.split())
826 iso_long_years.sort()
827 L = []
828 for i in range(400):
829 d = self.theclass(2000+i, 12, 31)
830 d1 = self.theclass(1600+i, 12, 31)
831 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
832 if d.isocalendar()[1] == 53:
833 L.append(i)
834 self.assertEqual(L, iso_long_years)
835
836 def test_isoformat(self):
837 t = self.theclass(2, 3, 2)
838 self.assertEqual(t.isoformat(), "0002-03-02")
839
840 def test_ctime(self):
841 t = self.theclass(2002, 3, 2)
842 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
843
844 def test_strftime(self):
845 t = self.theclass(2005, 3, 2)
846 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
Raymond Hettingerf69d9f62003-06-27 08:14:17 +0000847 self.assertEqual(t.strftime(""), "") # SF bug #761337
Georg Brandl6d7c3632006-09-30 11:17:43 +0000848 self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784
Tim Peters2a799bf2002-12-16 20:18:38 +0000849
850 self.assertRaises(TypeError, t.strftime) # needs an arg
851 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
852 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
853
854 # A naive object replaces %z and %Z w/ empty strings.
855 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
856
857 def test_resolution_info(self):
858 self.assert_(isinstance(self.theclass.min, self.theclass))
859 self.assert_(isinstance(self.theclass.max, self.theclass))
860 self.assert_(isinstance(self.theclass.resolution, timedelta))
861 self.assert_(self.theclass.max > self.theclass.min)
862
863 def test_extreme_timedelta(self):
864 big = self.theclass.max - self.theclass.min
865 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
866 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
867 # n == 315537897599999999 ~= 2**58.13
868 justasbig = timedelta(0, 0, n)
869 self.assertEqual(big, justasbig)
870 self.assertEqual(self.theclass.min + big, self.theclass.max)
871 self.assertEqual(self.theclass.max - big, self.theclass.min)
872
873 def test_timetuple(self):
874 for i in range(7):
875 # January 2, 1956 is a Monday (0)
876 d = self.theclass(1956, 1, 2+i)
877 t = d.timetuple()
878 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
879 # February 1, 1956 is a Wednesday (2)
880 d = self.theclass(1956, 2, 1+i)
881 t = d.timetuple()
882 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
883 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
884 # of the year.
885 d = self.theclass(1956, 3, 1+i)
886 t = d.timetuple()
887 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
888 self.assertEqual(t.tm_year, 1956)
889 self.assertEqual(t.tm_mon, 3)
890 self.assertEqual(t.tm_mday, 1+i)
891 self.assertEqual(t.tm_hour, 0)
892 self.assertEqual(t.tm_min, 0)
893 self.assertEqual(t.tm_sec, 0)
894 self.assertEqual(t.tm_wday, (3+i)%7)
895 self.assertEqual(t.tm_yday, 61+i)
896 self.assertEqual(t.tm_isdst, -1)
897
898 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000899 args = 6, 7, 23
900 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000901 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +0000902 green = pickler.dumps(orig, proto)
903 derived = unpickler.loads(green)
904 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +0000905
906 def test_compare(self):
907 t1 = self.theclass(2, 3, 4)
908 t2 = self.theclass(2, 3, 4)
909 self.failUnless(t1 == t2)
910 self.failUnless(t1 <= t2)
911 self.failUnless(t1 >= t2)
912 self.failUnless(not t1 != t2)
913 self.failUnless(not t1 < t2)
914 self.failUnless(not t1 > t2)
915 self.assertEqual(cmp(t1, t2), 0)
916 self.assertEqual(cmp(t2, t1), 0)
917
918 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
919 t2 = self.theclass(*args) # this is larger than t1
920 self.failUnless(t1 < t2)
921 self.failUnless(t2 > t1)
922 self.failUnless(t1 <= t2)
923 self.failUnless(t2 >= t1)
924 self.failUnless(t1 != t2)
925 self.failUnless(t2 != t1)
926 self.failUnless(not t1 == t2)
927 self.failUnless(not t2 == t1)
928 self.failUnless(not t1 > t2)
929 self.failUnless(not t2 < t1)
930 self.failUnless(not t1 >= t2)
931 self.failUnless(not t2 <= t1)
932 self.assertEqual(cmp(t1, t2), -1)
933 self.assertEqual(cmp(t2, t1), 1)
934
Tim Peters68124bb2003-02-08 03:46:31 +0000935 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +0000936 self.assertEqual(t1 == badarg, False)
937 self.assertEqual(t1 != badarg, True)
938 self.assertEqual(badarg == t1, False)
939 self.assertEqual(badarg != t1, True)
940
Tim Peters2a799bf2002-12-16 20:18:38 +0000941 self.assertRaises(TypeError, lambda: t1 < badarg)
942 self.assertRaises(TypeError, lambda: t1 > badarg)
943 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +0000944 self.assertRaises(TypeError, lambda: badarg <= t1)
945 self.assertRaises(TypeError, lambda: badarg < t1)
946 self.assertRaises(TypeError, lambda: badarg > t1)
947 self.assertRaises(TypeError, lambda: badarg >= t1)
948
Tim Peters8d81a012003-01-24 22:36:34 +0000949 def test_mixed_compare(self):
950 our = self.theclass(2000, 4, 5)
951 self.assertRaises(TypeError, cmp, our, 1)
952 self.assertRaises(TypeError, cmp, 1, our)
953
954 class AnotherDateTimeClass(object):
955 def __cmp__(self, other):
956 # Return "equal" so calling this can't be confused with
957 # compare-by-address (which never says "equal" for distinct
958 # objects).
959 return 0
960
961 # This still errors, because date and datetime comparison raise
962 # TypeError instead of NotImplemented when they don't know what to
963 # do, in order to stop comparison from falling back to the default
964 # compare-by-address.
965 their = AnotherDateTimeClass()
966 self.assertRaises(TypeError, cmp, our, their)
967 # Oops: The next stab raises TypeError in the C implementation,
968 # but not in the Python implementation of datetime. The difference
969 # is due to that the Python implementation defines __cmp__ but
970 # the C implementation defines tp_richcompare. This is more pain
971 # to fix than it's worth, so commenting out the test.
972 # self.assertEqual(cmp(their, our), 0)
973
974 # But date and datetime comparison return NotImplemented instead if the
975 # other object has a timetuple attr. This gives the other object a
976 # chance to do the comparison.
977 class Comparable(AnotherDateTimeClass):
978 def timetuple(self):
979 return ()
980
981 their = Comparable()
982 self.assertEqual(cmp(our, their), 0)
983 self.assertEqual(cmp(their, our), 0)
984 self.failUnless(our == their)
985 self.failUnless(their == our)
986
Tim Peters2a799bf2002-12-16 20:18:38 +0000987 def test_bool(self):
988 # All dates are considered true.
989 self.failUnless(self.theclass.min)
990 self.failUnless(self.theclass.max)
991
Tim Petersd6844152002-12-22 20:58:42 +0000992 def test_srftime_out_of_range(self):
993 # For nasty technical reasons, we can't handle years before 1900.
994 cls = self.theclass
995 self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900")
996 for y in 1, 49, 51, 99, 100, 1000, 1899:
997 self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y")
Tim Peters12bf3392002-12-24 05:41:27 +0000998
999 def test_replace(self):
1000 cls = self.theclass
1001 args = [1, 2, 3]
1002 base = cls(*args)
1003 self.assertEqual(base, base.replace())
1004
1005 i = 0
1006 for name, newval in (("year", 2),
1007 ("month", 3),
1008 ("day", 4)):
1009 newargs = args[:]
1010 newargs[i] = newval
1011 expected = cls(*newargs)
1012 got = base.replace(**{name: newval})
1013 self.assertEqual(expected, got)
1014 i += 1
1015
1016 # Out of bounds.
1017 base = cls(2000, 2, 29)
1018 self.assertRaises(ValueError, base.replace, year=2001)
1019
Tim Petersa98924a2003-05-17 05:55:19 +00001020 def test_subclass_date(self):
1021
1022 class C(self.theclass):
1023 theAnswer = 42
1024
1025 def __new__(cls, *args, **kws):
1026 temp = kws.copy()
1027 extra = temp.pop('extra')
1028 result = self.theclass.__new__(cls, *args, **temp)
1029 result.extra = extra
1030 return result
1031
1032 def newmeth(self, start):
1033 return start + self.year + self.month
1034
1035 args = 2003, 4, 14
1036
1037 dt1 = self.theclass(*args)
1038 dt2 = C(*args, **{'extra': 7})
1039
1040 self.assertEqual(dt2.__class__, C)
1041 self.assertEqual(dt2.theAnswer, 42)
1042 self.assertEqual(dt2.extra, 7)
1043 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1044 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1045
Tim Peters604c0132004-06-07 23:04:33 +00001046 def test_pickling_subclass_date(self):
1047
1048 args = 6, 7, 23
1049 orig = SubclassDate(*args)
1050 for pickler, unpickler, proto in pickle_choices:
1051 green = pickler.dumps(orig, proto)
1052 derived = unpickler.loads(green)
1053 self.assertEqual(orig, derived)
1054
Tim Peters3f606292004-03-21 23:38:41 +00001055 def test_backdoor_resistance(self):
1056 # For fast unpickling, the constructor accepts a pickle string.
1057 # This is a low-overhead backdoor. A user can (by intent or
1058 # mistake) pass a string directly, which (if it's the right length)
1059 # will get treated like a pickle, and bypass the normal sanity
1060 # checks in the constructor. This can create insane objects.
1061 # The constructor doesn't want to burn the time to validate all
1062 # fields, but does check the month field. This stops, e.g.,
1063 # datetime.datetime('1995-03-25') from yielding an insane object.
1064 base = '1995-03-25'
1065 if not issubclass(self.theclass, datetime):
1066 base = base[:4]
1067 for month_byte in '9', chr(0), chr(13), '\xff':
1068 self.assertRaises(TypeError, self.theclass,
1069 base[:2] + month_byte + base[3:])
1070 for ord_byte in range(1, 13):
1071 # This shouldn't blow up because of the month byte alone. If
1072 # the implementation changes to do more-careful checking, it may
1073 # blow up because other fields are insane.
1074 self.theclass(base[:2] + chr(ord_byte) + base[3:])
Tim Peterseb1a4962003-05-17 02:25:20 +00001075
Tim Peters2a799bf2002-12-16 20:18:38 +00001076#############################################################################
1077# datetime tests
1078
Tim Peters604c0132004-06-07 23:04:33 +00001079class SubclassDatetime(datetime):
1080 sub_var = 1
1081
Tim Peters2a799bf2002-12-16 20:18:38 +00001082class TestDateTime(TestDate):
1083
1084 theclass = datetime
1085
1086 def test_basic_attributes(self):
1087 dt = self.theclass(2002, 3, 1, 12, 0)
1088 self.assertEqual(dt.year, 2002)
1089 self.assertEqual(dt.month, 3)
1090 self.assertEqual(dt.day, 1)
1091 self.assertEqual(dt.hour, 12)
1092 self.assertEqual(dt.minute, 0)
1093 self.assertEqual(dt.second, 0)
1094 self.assertEqual(dt.microsecond, 0)
1095
1096 def test_basic_attributes_nonzero(self):
1097 # Make sure all attributes are non-zero so bugs in
1098 # bit-shifting access show up.
1099 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1100 self.assertEqual(dt.year, 2002)
1101 self.assertEqual(dt.month, 3)
1102 self.assertEqual(dt.day, 1)
1103 self.assertEqual(dt.hour, 12)
1104 self.assertEqual(dt.minute, 59)
1105 self.assertEqual(dt.second, 59)
1106 self.assertEqual(dt.microsecond, 8000)
1107
1108 def test_roundtrip(self):
1109 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1110 self.theclass.now()):
1111 # Verify dt -> string -> datetime identity.
1112 s = repr(dt)
1113 self.failUnless(s.startswith('datetime.'))
1114 s = s[9:]
1115 dt2 = eval(s)
1116 self.assertEqual(dt, dt2)
1117
1118 # Verify identity via reconstructing from pieces.
1119 dt2 = self.theclass(dt.year, dt.month, dt.day,
1120 dt.hour, dt.minute, dt.second,
1121 dt.microsecond)
1122 self.assertEqual(dt, dt2)
1123
1124 def test_isoformat(self):
1125 t = self.theclass(2, 3, 2, 4, 5, 1, 123)
1126 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123")
1127 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
1128 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
1129 # str is ISO format with the separator forced to a blank.
1130 self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
1131
1132 t = self.theclass(2, 3, 2)
1133 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1134 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1135 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1136 # str is ISO format with the separator forced to a blank.
1137 self.assertEqual(str(t), "0002-03-02 00:00:00")
1138
1139 def test_more_ctime(self):
1140 # Test fields that TestDate doesn't touch.
1141 import time
1142
1143 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1144 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1145 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1146 # out. The difference is that t.ctime() produces " 2" for the day,
1147 # but platform ctime() produces "02" for the day. According to
1148 # C99, t.ctime() is correct here.
1149 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1150
1151 # So test a case where that difference doesn't matter.
1152 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1153 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1154
1155 def test_tz_independent_comparing(self):
1156 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1157 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1158 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1159 self.assertEqual(dt1, dt3)
1160 self.assert_(dt2 > dt3)
1161
1162 # Make sure comparison doesn't forget microseconds, and isn't done
1163 # via comparing a float timestamp (an IEEE double doesn't have enough
1164 # precision to span microsecond resolution across years 1 thru 9999,
1165 # so comparing via timestamp necessarily calls some distinct values
1166 # equal).
1167 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1168 us = timedelta(microseconds=1)
1169 dt2 = dt1 + us
1170 self.assertEqual(dt2 - dt1, us)
1171 self.assert_(dt1 < dt2)
1172
Neal Norwitzd5b0c9b2006-03-20 01:58:39 +00001173 def test_strftime_with_bad_tzname_replace(self):
1174 # verify ok if tzinfo.tzname().replace() returns a non-string
1175 class MyTzInfo(FixedOffset):
1176 def tzname(self, dt):
1177 class MyStr(str):
1178 def replace(self, *args):
1179 return None
1180 return MyStr('name')
1181 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
1182 self.assertRaises(TypeError, t.strftime, '%Z')
1183
Tim Peters2a799bf2002-12-16 20:18:38 +00001184 def test_bad_constructor_arguments(self):
1185 # bad years
1186 self.theclass(MINYEAR, 1, 1) # no exception
1187 self.theclass(MAXYEAR, 1, 1) # no exception
1188 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1189 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1190 # bad months
1191 self.theclass(2000, 1, 1) # no exception
1192 self.theclass(2000, 12, 1) # no exception
1193 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1194 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1195 # bad days
1196 self.theclass(2000, 2, 29) # no exception
1197 self.theclass(2004, 2, 29) # no exception
1198 self.theclass(2400, 2, 29) # no exception
1199 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1200 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1201 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1202 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1203 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1204 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1205 # bad hours
1206 self.theclass(2000, 1, 31, 0) # no exception
1207 self.theclass(2000, 1, 31, 23) # no exception
1208 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1209 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1210 # bad minutes
1211 self.theclass(2000, 1, 31, 23, 0) # no exception
1212 self.theclass(2000, 1, 31, 23, 59) # no exception
1213 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1214 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1215 # bad seconds
1216 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1217 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1218 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1219 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1220 # bad microseconds
1221 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1222 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1223 self.assertRaises(ValueError, self.theclass,
1224 2000, 1, 31, 23, 59, 59, -1)
1225 self.assertRaises(ValueError, self.theclass,
1226 2000, 1, 31, 23, 59, 59,
1227 1000000)
1228
1229 def test_hash_equality(self):
1230 d = self.theclass(2000, 12, 31, 23, 30, 17)
1231 e = self.theclass(2000, 12, 31, 23, 30, 17)
1232 self.assertEqual(d, e)
1233 self.assertEqual(hash(d), hash(e))
1234
1235 dic = {d: 1}
1236 dic[e] = 2
1237 self.assertEqual(len(dic), 1)
1238 self.assertEqual(dic[d], 2)
1239 self.assertEqual(dic[e], 2)
1240
1241 d = self.theclass(2001, 1, 1, 0, 5, 17)
1242 e = self.theclass(2001, 1, 1, 0, 5, 17)
1243 self.assertEqual(d, e)
1244 self.assertEqual(hash(d), hash(e))
1245
1246 dic = {d: 1}
1247 dic[e] = 2
1248 self.assertEqual(len(dic), 1)
1249 self.assertEqual(dic[d], 2)
1250 self.assertEqual(dic[e], 2)
1251
1252 def test_computations(self):
1253 a = self.theclass(2002, 1, 31)
1254 b = self.theclass(1956, 1, 31)
1255 diff = a-b
1256 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1257 self.assertEqual(diff.seconds, 0)
1258 self.assertEqual(diff.microseconds, 0)
1259 a = self.theclass(2002, 3, 2, 17, 6)
1260 millisec = timedelta(0, 0, 1000)
1261 hour = timedelta(0, 3600)
1262 day = timedelta(1)
1263 week = timedelta(7)
1264 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1265 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1266 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1267 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1268 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1269 self.assertEqual(a - hour, a + -hour)
1270 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1271 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1272 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1273 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1274 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1275 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1276 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1277 self.assertEqual((a + week) - a, week)
1278 self.assertEqual((a + day) - a, day)
1279 self.assertEqual((a + hour) - a, hour)
1280 self.assertEqual((a + millisec) - a, millisec)
1281 self.assertEqual((a - week) - a, -week)
1282 self.assertEqual((a - day) - a, -day)
1283 self.assertEqual((a - hour) - a, -hour)
1284 self.assertEqual((a - millisec) - a, -millisec)
1285 self.assertEqual(a - (a + week), -week)
1286 self.assertEqual(a - (a + day), -day)
1287 self.assertEqual(a - (a + hour), -hour)
1288 self.assertEqual(a - (a + millisec), -millisec)
1289 self.assertEqual(a - (a - week), week)
1290 self.assertEqual(a - (a - day), day)
1291 self.assertEqual(a - (a - hour), hour)
1292 self.assertEqual(a - (a - millisec), millisec)
1293 self.assertEqual(a + (week + day + hour + millisec),
1294 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1295 self.assertEqual(a + (week + day + hour + millisec),
1296 (((a + week) + day) + hour) + millisec)
1297 self.assertEqual(a - (week + day + hour + millisec),
1298 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1299 self.assertEqual(a - (week + day + hour + millisec),
1300 (((a - week) - day) - hour) - millisec)
1301 # Add/sub ints, longs, floats should be illegal
1302 for i in 1, 1L, 1.0:
1303 self.assertRaises(TypeError, lambda: a+i)
1304 self.assertRaises(TypeError, lambda: a-i)
1305 self.assertRaises(TypeError, lambda: i+a)
1306 self.assertRaises(TypeError, lambda: i-a)
1307
1308 # delta - datetime is senseless.
1309 self.assertRaises(TypeError, lambda: day - a)
1310 # mixing datetime and (delta or datetime) via * or // is senseless
1311 self.assertRaises(TypeError, lambda: day * a)
1312 self.assertRaises(TypeError, lambda: a * day)
1313 self.assertRaises(TypeError, lambda: day // a)
1314 self.assertRaises(TypeError, lambda: a // day)
1315 self.assertRaises(TypeError, lambda: a * a)
1316 self.assertRaises(TypeError, lambda: a // a)
1317 # datetime + datetime is senseless
1318 self.assertRaises(TypeError, lambda: a + a)
1319
1320 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001321 args = 6, 7, 23, 20, 59, 1, 64**2
1322 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001323 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001324 green = pickler.dumps(orig, proto)
1325 derived = unpickler.loads(green)
1326 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001327
Guido van Rossum275666f2003-02-07 21:49:01 +00001328 def test_more_pickling(self):
1329 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
1330 s = pickle.dumps(a)
1331 b = pickle.loads(s)
1332 self.assertEqual(b.year, 2003)
1333 self.assertEqual(b.month, 2)
1334 self.assertEqual(b.day, 7)
1335
Tim Peters604c0132004-06-07 23:04:33 +00001336 def test_pickling_subclass_datetime(self):
1337 args = 6, 7, 23, 20, 59, 1, 64**2
1338 orig = SubclassDatetime(*args)
1339 for pickler, unpickler, proto in pickle_choices:
1340 green = pickler.dumps(orig, proto)
1341 derived = unpickler.loads(green)
1342 self.assertEqual(orig, derived)
1343
Tim Peters2a799bf2002-12-16 20:18:38 +00001344 def test_more_compare(self):
1345 # The test_compare() inherited from TestDate covers the error cases.
1346 # We just want to test lexicographic ordering on the members datetime
1347 # has that date lacks.
1348 args = [2000, 11, 29, 20, 58, 16, 999998]
1349 t1 = self.theclass(*args)
1350 t2 = self.theclass(*args)
1351 self.failUnless(t1 == t2)
1352 self.failUnless(t1 <= t2)
1353 self.failUnless(t1 >= t2)
1354 self.failUnless(not t1 != t2)
1355 self.failUnless(not t1 < t2)
1356 self.failUnless(not t1 > t2)
1357 self.assertEqual(cmp(t1, t2), 0)
1358 self.assertEqual(cmp(t2, t1), 0)
1359
1360 for i in range(len(args)):
1361 newargs = args[:]
1362 newargs[i] = args[i] + 1
1363 t2 = self.theclass(*newargs) # this is larger than t1
1364 self.failUnless(t1 < t2)
1365 self.failUnless(t2 > t1)
1366 self.failUnless(t1 <= t2)
1367 self.failUnless(t2 >= t1)
1368 self.failUnless(t1 != t2)
1369 self.failUnless(t2 != t1)
1370 self.failUnless(not t1 == t2)
1371 self.failUnless(not t2 == t1)
1372 self.failUnless(not t1 > t2)
1373 self.failUnless(not t2 < t1)
1374 self.failUnless(not t1 >= t2)
1375 self.failUnless(not t2 <= t1)
1376 self.assertEqual(cmp(t1, t2), -1)
1377 self.assertEqual(cmp(t2, t1), 1)
1378
1379
1380 # A helper for timestamp constructor tests.
1381 def verify_field_equality(self, expected, got):
1382 self.assertEqual(expected.tm_year, got.year)
1383 self.assertEqual(expected.tm_mon, got.month)
1384 self.assertEqual(expected.tm_mday, got.day)
1385 self.assertEqual(expected.tm_hour, got.hour)
1386 self.assertEqual(expected.tm_min, got.minute)
1387 self.assertEqual(expected.tm_sec, got.second)
1388
1389 def test_fromtimestamp(self):
1390 import time
1391
1392 ts = time.time()
1393 expected = time.localtime(ts)
1394 got = self.theclass.fromtimestamp(ts)
1395 self.verify_field_equality(expected, got)
1396
1397 def test_utcfromtimestamp(self):
1398 import time
1399
1400 ts = time.time()
1401 expected = time.gmtime(ts)
1402 got = self.theclass.utcfromtimestamp(ts)
1403 self.verify_field_equality(expected, got)
1404
Georg Brandl6d78a582006-04-28 19:09:24 +00001405 def test_microsecond_rounding(self):
1406 # Test whether fromtimestamp "rounds up" floats that are less
1407 # than one microsecond smaller than an integer.
1408 self.assertEquals(self.theclass.fromtimestamp(0.9999999),
1409 self.theclass.fromtimestamp(1))
1410
Tim Peters1b6f7a92004-06-20 02:50:16 +00001411 def test_insane_fromtimestamp(self):
1412 # It's possible that some platform maps time_t to double,
1413 # and that this test will fail there. This test should
1414 # exempt such platforms (provided they return reasonable
1415 # results!).
1416 for insane in -1e200, 1e200:
1417 self.assertRaises(ValueError, self.theclass.fromtimestamp,
1418 insane)
1419
1420 def test_insane_utcfromtimestamp(self):
1421 # It's possible that some platform maps time_t to double,
1422 # and that this test will fail there. This test should
1423 # exempt such platforms (provided they return reasonable
1424 # results!).
1425 for insane in -1e200, 1e200:
1426 self.assertRaises(ValueError, self.theclass.utcfromtimestamp,
1427 insane)
1428
Georg Brandl02d7cff2007-03-06 17:46:17 +00001429 def test_negative_float_fromtimestamp(self):
Georg Brandl0c4f3fd2007-03-07 16:12:05 +00001430 # Windows doesn't accept negative timestamps
1431 if os.name == "nt":
1432 return
Georg Brandl02d7cff2007-03-06 17:46:17 +00001433 # The result is tz-dependent; at least test that this doesn't
1434 # fail (like it did before bug 1646728 was fixed).
1435 self.theclass.fromtimestamp(-1.05)
1436
1437 def test_negative_float_utcfromtimestamp(self):
Georg Brandl0c4f3fd2007-03-07 16:12:05 +00001438 # Windows doesn't accept negative timestamps
1439 if os.name == "nt":
1440 return
Georg Brandl02d7cff2007-03-06 17:46:17 +00001441 d = self.theclass.utcfromtimestamp(-1.05)
1442 self.assertEquals(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
1443
Tim Peters2a799bf2002-12-16 20:18:38 +00001444 def test_utcnow(self):
1445 import time
1446
1447 # Call it a success if utcnow() and utcfromtimestamp() are within
1448 # a second of each other.
1449 tolerance = timedelta(seconds=1)
1450 for dummy in range(3):
1451 from_now = self.theclass.utcnow()
1452 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1453 if abs(from_timestamp - from_now) <= tolerance:
1454 break
1455 # Else try again a few times.
1456 self.failUnless(abs(from_timestamp - from_now) <= tolerance)
1457
Skip Montanaro0af3ade2005-01-13 04:12:31 +00001458 def test_strptime(self):
1459 import time
1460
1461 string = '2004-12-01 13:02:47'
1462 format = '%Y-%m-%d %H:%M:%S'
1463 expected = self.theclass(*(time.strptime(string, format)[0:6]))
1464 got = self.theclass.strptime(string, format)
1465 self.assertEqual(expected, got)
1466
Tim Peters2a799bf2002-12-16 20:18:38 +00001467 def test_more_timetuple(self):
1468 # This tests fields beyond those tested by the TestDate.test_timetuple.
1469 t = self.theclass(2004, 12, 31, 6, 22, 33)
1470 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1471 self.assertEqual(t.timetuple(),
1472 (t.year, t.month, t.day,
1473 t.hour, t.minute, t.second,
1474 t.weekday(),
1475 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1476 -1))
1477 tt = t.timetuple()
1478 self.assertEqual(tt.tm_year, t.year)
1479 self.assertEqual(tt.tm_mon, t.month)
1480 self.assertEqual(tt.tm_mday, t.day)
1481 self.assertEqual(tt.tm_hour, t.hour)
1482 self.assertEqual(tt.tm_min, t.minute)
1483 self.assertEqual(tt.tm_sec, t.second)
1484 self.assertEqual(tt.tm_wday, t.weekday())
1485 self.assertEqual(tt.tm_yday, t.toordinal() -
1486 date(t.year, 1, 1).toordinal() + 1)
1487 self.assertEqual(tt.tm_isdst, -1)
1488
1489 def test_more_strftime(self):
1490 # This tests fields beyond those tested by the TestDate.test_strftime.
1491 t = self.theclass(2004, 12, 31, 6, 22, 33)
1492 self.assertEqual(t.strftime("%m %d %y %S %M %H %j"),
1493 "12 31 04 33 22 06 366")
1494
1495 def test_extract(self):
1496 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1497 self.assertEqual(dt.date(), date(2002, 3, 4))
1498 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1499
1500 def test_combine(self):
1501 d = date(2002, 3, 4)
1502 t = time(18, 45, 3, 1234)
1503 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1504 combine = self.theclass.combine
1505 dt = combine(d, t)
1506 self.assertEqual(dt, expected)
1507
1508 dt = combine(time=t, date=d)
1509 self.assertEqual(dt, expected)
1510
1511 self.assertEqual(d, dt.date())
1512 self.assertEqual(t, dt.time())
1513 self.assertEqual(dt, combine(dt.date(), dt.time()))
1514
1515 self.assertRaises(TypeError, combine) # need an arg
1516 self.assertRaises(TypeError, combine, d) # need two args
1517 self.assertRaises(TypeError, combine, t, d) # args reversed
1518 self.assertRaises(TypeError, combine, d, t, 1) # too many args
1519 self.assertRaises(TypeError, combine, "date", "time") # wrong types
1520
Tim Peters12bf3392002-12-24 05:41:27 +00001521 def test_replace(self):
1522 cls = self.theclass
1523 args = [1, 2, 3, 4, 5, 6, 7]
1524 base = cls(*args)
1525 self.assertEqual(base, base.replace())
1526
1527 i = 0
1528 for name, newval in (("year", 2),
1529 ("month", 3),
1530 ("day", 4),
1531 ("hour", 5),
1532 ("minute", 6),
1533 ("second", 7),
1534 ("microsecond", 8)):
1535 newargs = args[:]
1536 newargs[i] = newval
1537 expected = cls(*newargs)
1538 got = base.replace(**{name: newval})
1539 self.assertEqual(expected, got)
1540 i += 1
1541
1542 # Out of bounds.
1543 base = cls(2000, 2, 29)
1544 self.assertRaises(ValueError, base.replace, year=2001)
1545
Tim Peters80475bb2002-12-25 07:40:55 +00001546 def test_astimezone(self):
Tim Peters52dcce22003-01-23 16:36:11 +00001547 # Pretty boring! The TZ test is more interesting here. astimezone()
1548 # simply can't be applied to a naive object.
Tim Peters80475bb2002-12-25 07:40:55 +00001549 dt = self.theclass.now()
1550 f = FixedOffset(44, "")
Tim Peters80475bb2002-12-25 07:40:55 +00001551 self.assertRaises(TypeError, dt.astimezone) # not enough args
1552 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
1553 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Tim Peters52dcce22003-01-23 16:36:11 +00001554 self.assertRaises(ValueError, dt.astimezone, f) # naive
1555 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
Tim Peters80475bb2002-12-25 07:40:55 +00001556
Tim Peters52dcce22003-01-23 16:36:11 +00001557 class Bogus(tzinfo):
1558 def utcoffset(self, dt): return None
1559 def dst(self, dt): return timedelta(0)
1560 bog = Bogus()
1561 self.assertRaises(ValueError, dt.astimezone, bog) # naive
1562
1563 class AlsoBogus(tzinfo):
1564 def utcoffset(self, dt): return timedelta(0)
1565 def dst(self, dt): return None
1566 alsobog = AlsoBogus()
1567 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
Tim Peters12bf3392002-12-24 05:41:27 +00001568
Tim Petersa98924a2003-05-17 05:55:19 +00001569 def test_subclass_datetime(self):
1570
1571 class C(self.theclass):
1572 theAnswer = 42
1573
1574 def __new__(cls, *args, **kws):
1575 temp = kws.copy()
1576 extra = temp.pop('extra')
1577 result = self.theclass.__new__(cls, *args, **temp)
1578 result.extra = extra
1579 return result
1580
1581 def newmeth(self, start):
1582 return start + self.year + self.month + self.second
1583
1584 args = 2003, 4, 14, 12, 13, 41
1585
1586 dt1 = self.theclass(*args)
1587 dt2 = C(*args, **{'extra': 7})
1588
1589 self.assertEqual(dt2.__class__, C)
1590 self.assertEqual(dt2.theAnswer, 42)
1591 self.assertEqual(dt2.extra, 7)
1592 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1593 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
1594 dt1.second - 7)
1595
Tim Peters604c0132004-06-07 23:04:33 +00001596class SubclassTime(time):
1597 sub_var = 1
1598
Tim Peters07534a62003-02-07 22:50:28 +00001599class TestTime(HarmlessMixedComparison):
Tim Peters2a799bf2002-12-16 20:18:38 +00001600
1601 theclass = time
1602
1603 def test_basic_attributes(self):
1604 t = self.theclass(12, 0)
1605 self.assertEqual(t.hour, 12)
1606 self.assertEqual(t.minute, 0)
1607 self.assertEqual(t.second, 0)
1608 self.assertEqual(t.microsecond, 0)
1609
1610 def test_basic_attributes_nonzero(self):
1611 # Make sure all attributes are non-zero so bugs in
1612 # bit-shifting access show up.
1613 t = self.theclass(12, 59, 59, 8000)
1614 self.assertEqual(t.hour, 12)
1615 self.assertEqual(t.minute, 59)
1616 self.assertEqual(t.second, 59)
1617 self.assertEqual(t.microsecond, 8000)
1618
1619 def test_roundtrip(self):
1620 t = self.theclass(1, 2, 3, 4)
1621
1622 # Verify t -> string -> time identity.
1623 s = repr(t)
1624 self.failUnless(s.startswith('datetime.'))
1625 s = s[9:]
1626 t2 = eval(s)
1627 self.assertEqual(t, t2)
1628
1629 # Verify identity via reconstructing from pieces.
1630 t2 = self.theclass(t.hour, t.minute, t.second,
1631 t.microsecond)
1632 self.assertEqual(t, t2)
1633
1634 def test_comparing(self):
1635 args = [1, 2, 3, 4]
1636 t1 = self.theclass(*args)
1637 t2 = self.theclass(*args)
1638 self.failUnless(t1 == t2)
1639 self.failUnless(t1 <= t2)
1640 self.failUnless(t1 >= t2)
1641 self.failUnless(not t1 != t2)
1642 self.failUnless(not t1 < t2)
1643 self.failUnless(not t1 > t2)
1644 self.assertEqual(cmp(t1, t2), 0)
1645 self.assertEqual(cmp(t2, t1), 0)
1646
1647 for i in range(len(args)):
1648 newargs = args[:]
1649 newargs[i] = args[i] + 1
1650 t2 = self.theclass(*newargs) # this is larger than t1
1651 self.failUnless(t1 < t2)
1652 self.failUnless(t2 > t1)
1653 self.failUnless(t1 <= t2)
1654 self.failUnless(t2 >= t1)
1655 self.failUnless(t1 != t2)
1656 self.failUnless(t2 != t1)
1657 self.failUnless(not t1 == t2)
1658 self.failUnless(not t2 == t1)
1659 self.failUnless(not t1 > t2)
1660 self.failUnless(not t2 < t1)
1661 self.failUnless(not t1 >= t2)
1662 self.failUnless(not t2 <= t1)
1663 self.assertEqual(cmp(t1, t2), -1)
1664 self.assertEqual(cmp(t2, t1), 1)
1665
Tim Peters68124bb2003-02-08 03:46:31 +00001666 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +00001667 self.assertEqual(t1 == badarg, False)
1668 self.assertEqual(t1 != badarg, True)
1669 self.assertEqual(badarg == t1, False)
1670 self.assertEqual(badarg != t1, True)
1671
Tim Peters2a799bf2002-12-16 20:18:38 +00001672 self.assertRaises(TypeError, lambda: t1 <= badarg)
1673 self.assertRaises(TypeError, lambda: t1 < badarg)
1674 self.assertRaises(TypeError, lambda: t1 > badarg)
1675 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +00001676 self.assertRaises(TypeError, lambda: badarg <= t1)
1677 self.assertRaises(TypeError, lambda: badarg < t1)
1678 self.assertRaises(TypeError, lambda: badarg > t1)
1679 self.assertRaises(TypeError, lambda: badarg >= t1)
1680
1681 def test_bad_constructor_arguments(self):
1682 # bad hours
1683 self.theclass(0, 0) # no exception
1684 self.theclass(23, 0) # no exception
1685 self.assertRaises(ValueError, self.theclass, -1, 0)
1686 self.assertRaises(ValueError, self.theclass, 24, 0)
1687 # bad minutes
1688 self.theclass(23, 0) # no exception
1689 self.theclass(23, 59) # no exception
1690 self.assertRaises(ValueError, self.theclass, 23, -1)
1691 self.assertRaises(ValueError, self.theclass, 23, 60)
1692 # bad seconds
1693 self.theclass(23, 59, 0) # no exception
1694 self.theclass(23, 59, 59) # no exception
1695 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
1696 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
1697 # bad microseconds
1698 self.theclass(23, 59, 59, 0) # no exception
1699 self.theclass(23, 59, 59, 999999) # no exception
1700 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
1701 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
1702
1703 def test_hash_equality(self):
1704 d = self.theclass(23, 30, 17)
1705 e = self.theclass(23, 30, 17)
1706 self.assertEqual(d, e)
1707 self.assertEqual(hash(d), hash(e))
1708
1709 dic = {d: 1}
1710 dic[e] = 2
1711 self.assertEqual(len(dic), 1)
1712 self.assertEqual(dic[d], 2)
1713 self.assertEqual(dic[e], 2)
1714
1715 d = self.theclass(0, 5, 17)
1716 e = self.theclass(0, 5, 17)
1717 self.assertEqual(d, e)
1718 self.assertEqual(hash(d), hash(e))
1719
1720 dic = {d: 1}
1721 dic[e] = 2
1722 self.assertEqual(len(dic), 1)
1723 self.assertEqual(dic[d], 2)
1724 self.assertEqual(dic[e], 2)
1725
1726 def test_isoformat(self):
1727 t = self.theclass(4, 5, 1, 123)
1728 self.assertEqual(t.isoformat(), "04:05:01.000123")
1729 self.assertEqual(t.isoformat(), str(t))
1730
1731 t = self.theclass()
1732 self.assertEqual(t.isoformat(), "00:00:00")
1733 self.assertEqual(t.isoformat(), str(t))
1734
1735 t = self.theclass(microsecond=1)
1736 self.assertEqual(t.isoformat(), "00:00:00.000001")
1737 self.assertEqual(t.isoformat(), str(t))
1738
1739 t = self.theclass(microsecond=10)
1740 self.assertEqual(t.isoformat(), "00:00:00.000010")
1741 self.assertEqual(t.isoformat(), str(t))
1742
1743 t = self.theclass(microsecond=100)
1744 self.assertEqual(t.isoformat(), "00:00:00.000100")
1745 self.assertEqual(t.isoformat(), str(t))
1746
1747 t = self.theclass(microsecond=1000)
1748 self.assertEqual(t.isoformat(), "00:00:00.001000")
1749 self.assertEqual(t.isoformat(), str(t))
1750
1751 t = self.theclass(microsecond=10000)
1752 self.assertEqual(t.isoformat(), "00:00:00.010000")
1753 self.assertEqual(t.isoformat(), str(t))
1754
1755 t = self.theclass(microsecond=100000)
1756 self.assertEqual(t.isoformat(), "00:00:00.100000")
1757 self.assertEqual(t.isoformat(), str(t))
1758
1759 def test_strftime(self):
1760 t = self.theclass(1, 2, 3, 4)
1761 self.assertEqual(t.strftime('%H %M %S'), "01 02 03")
1762 # A naive object replaces %z and %Z with empty strings.
1763 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1764
1765 def test_str(self):
1766 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
1767 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
1768 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
1769 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
1770 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
1771
1772 def test_repr(self):
1773 name = 'datetime.' + self.theclass.__name__
1774 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
1775 "%s(1, 2, 3, 4)" % name)
1776 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
1777 "%s(10, 2, 3, 4000)" % name)
1778 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
1779 "%s(0, 2, 3, 400000)" % name)
1780 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
1781 "%s(12, 2, 3)" % name)
1782 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
1783 "%s(23, 15)" % name)
1784
1785 def test_resolution_info(self):
1786 self.assert_(isinstance(self.theclass.min, self.theclass))
1787 self.assert_(isinstance(self.theclass.max, self.theclass))
1788 self.assert_(isinstance(self.theclass.resolution, timedelta))
1789 self.assert_(self.theclass.max > self.theclass.min)
1790
1791 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001792 args = 20, 59, 16, 64**2
1793 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001794 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001795 green = pickler.dumps(orig, proto)
1796 derived = unpickler.loads(green)
1797 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001798
Tim Peters604c0132004-06-07 23:04:33 +00001799 def test_pickling_subclass_time(self):
1800 args = 20, 59, 16, 64**2
1801 orig = SubclassTime(*args)
1802 for pickler, unpickler, proto in pickle_choices:
1803 green = pickler.dumps(orig, proto)
1804 derived = unpickler.loads(green)
1805 self.assertEqual(orig, derived)
1806
Tim Peters2a799bf2002-12-16 20:18:38 +00001807 def test_bool(self):
1808 cls = self.theclass
1809 self.failUnless(cls(1))
1810 self.failUnless(cls(0, 1))
1811 self.failUnless(cls(0, 0, 1))
1812 self.failUnless(cls(0, 0, 0, 1))
1813 self.failUnless(not cls(0))
1814 self.failUnless(not cls())
1815
Tim Peters12bf3392002-12-24 05:41:27 +00001816 def test_replace(self):
1817 cls = self.theclass
1818 args = [1, 2, 3, 4]
1819 base = cls(*args)
1820 self.assertEqual(base, base.replace())
1821
1822 i = 0
1823 for name, newval in (("hour", 5),
1824 ("minute", 6),
1825 ("second", 7),
1826 ("microsecond", 8)):
1827 newargs = args[:]
1828 newargs[i] = newval
1829 expected = cls(*newargs)
1830 got = base.replace(**{name: newval})
1831 self.assertEqual(expected, got)
1832 i += 1
1833
1834 # Out of bounds.
1835 base = cls(1)
1836 self.assertRaises(ValueError, base.replace, hour=24)
1837 self.assertRaises(ValueError, base.replace, minute=-1)
1838 self.assertRaises(ValueError, base.replace, second=100)
1839 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1840
Tim Petersa98924a2003-05-17 05:55:19 +00001841 def test_subclass_time(self):
1842
1843 class C(self.theclass):
1844 theAnswer = 42
1845
1846 def __new__(cls, *args, **kws):
1847 temp = kws.copy()
1848 extra = temp.pop('extra')
1849 result = self.theclass.__new__(cls, *args, **temp)
1850 result.extra = extra
1851 return result
1852
1853 def newmeth(self, start):
1854 return start + self.hour + self.second
1855
1856 args = 4, 5, 6
1857
1858 dt1 = self.theclass(*args)
1859 dt2 = C(*args, **{'extra': 7})
1860
1861 self.assertEqual(dt2.__class__, C)
1862 self.assertEqual(dt2.theAnswer, 42)
1863 self.assertEqual(dt2.extra, 7)
1864 self.assertEqual(dt1.isoformat(), dt2.isoformat())
1865 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
1866
Armin Rigof4afb212005-11-07 07:15:48 +00001867 def test_backdoor_resistance(self):
1868 # see TestDate.test_backdoor_resistance().
1869 base = '2:59.0'
1870 for hour_byte in ' ', '9', chr(24), '\xff':
1871 self.assertRaises(TypeError, self.theclass,
1872 hour_byte + base[1:])
1873
Tim Peters855fe882002-12-22 03:43:39 +00001874# A mixin for classes with a tzinfo= argument. Subclasses must define
1875# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001876# must be legit (which is true for time and datetime).
Tim Peters855fe882002-12-22 03:43:39 +00001877class TZInfoBase(unittest.TestCase):
Tim Peters2a799bf2002-12-16 20:18:38 +00001878
Tim Petersbad8ff02002-12-30 20:52:32 +00001879 def test_argument_passing(self):
1880 cls = self.theclass
Tim Peters0bf60bd2003-01-08 20:40:01 +00001881 # A datetime passes itself on, a time passes None.
Tim Petersbad8ff02002-12-30 20:52:32 +00001882 class introspective(tzinfo):
1883 def tzname(self, dt): return dt and "real" or "none"
Tim Peters397301e2003-01-02 21:28:08 +00001884 def utcoffset(self, dt):
1885 return timedelta(minutes = dt and 42 or -42)
Tim Petersbad8ff02002-12-30 20:52:32 +00001886 dst = utcoffset
1887
1888 obj = cls(1, 2, 3, tzinfo=introspective())
1889
Tim Peters0bf60bd2003-01-08 20:40:01 +00001890 expected = cls is time and "none" or "real"
Tim Petersbad8ff02002-12-30 20:52:32 +00001891 self.assertEqual(obj.tzname(), expected)
1892
Tim Peters0bf60bd2003-01-08 20:40:01 +00001893 expected = timedelta(minutes=(cls is time and -42 or 42))
Tim Petersbad8ff02002-12-30 20:52:32 +00001894 self.assertEqual(obj.utcoffset(), expected)
1895 self.assertEqual(obj.dst(), expected)
1896
Tim Peters855fe882002-12-22 03:43:39 +00001897 def test_bad_tzinfo_classes(self):
1898 cls = self.theclass
1899 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
Tim Peters2a799bf2002-12-16 20:18:38 +00001900
Tim Peters855fe882002-12-22 03:43:39 +00001901 class NiceTry(object):
1902 def __init__(self): pass
1903 def utcoffset(self, dt): pass
1904 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
1905
1906 class BetterTry(tzinfo):
1907 def __init__(self): pass
1908 def utcoffset(self, dt): pass
1909 b = BetterTry()
1910 t = cls(1, 1, 1, tzinfo=b)
1911 self.failUnless(t.tzinfo is b)
1912
1913 def test_utc_offset_out_of_bounds(self):
1914 class Edgy(tzinfo):
1915 def __init__(self, offset):
Tim Peters397301e2003-01-02 21:28:08 +00001916 self.offset = timedelta(minutes=offset)
Tim Peters855fe882002-12-22 03:43:39 +00001917 def utcoffset(self, dt):
1918 return self.offset
1919
1920 cls = self.theclass
1921 for offset, legit in ((-1440, False),
1922 (-1439, True),
1923 (1439, True),
1924 (1440, False)):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001925 if cls is time:
Tim Peters855fe882002-12-22 03:43:39 +00001926 t = cls(1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001927 elif cls is datetime:
Tim Peters855fe882002-12-22 03:43:39 +00001928 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001929 else:
1930 assert 0, "impossible"
Tim Peters855fe882002-12-22 03:43:39 +00001931 if legit:
1932 aofs = abs(offset)
1933 h, m = divmod(aofs, 60)
1934 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001935 if isinstance(t, datetime):
Tim Peters855fe882002-12-22 03:43:39 +00001936 t = t.timetz()
1937 self.assertEqual(str(t), "01:02:03" + tag)
1938 else:
1939 self.assertRaises(ValueError, str, t)
1940
1941 def test_tzinfo_classes(self):
1942 cls = self.theclass
1943 class C1(tzinfo):
1944 def utcoffset(self, dt): return None
1945 def dst(self, dt): return None
1946 def tzname(self, dt): return None
1947 for t in (cls(1, 1, 1),
1948 cls(1, 1, 1, tzinfo=None),
1949 cls(1, 1, 1, tzinfo=C1())):
1950 self.failUnless(t.utcoffset() is None)
1951 self.failUnless(t.dst() is None)
1952 self.failUnless(t.tzname() is None)
1953
Tim Peters855fe882002-12-22 03:43:39 +00001954 class C3(tzinfo):
1955 def utcoffset(self, dt): return timedelta(minutes=-1439)
1956 def dst(self, dt): return timedelta(minutes=1439)
1957 def tzname(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001958 t = cls(1, 1, 1, tzinfo=C3())
1959 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
1960 self.assertEqual(t.dst(), timedelta(minutes=1439))
1961 self.assertEqual(t.tzname(), "aname")
Tim Peters855fe882002-12-22 03:43:39 +00001962
1963 # Wrong types.
1964 class C4(tzinfo):
1965 def utcoffset(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001966 def dst(self, dt): return 7
Tim Peters855fe882002-12-22 03:43:39 +00001967 def tzname(self, dt): return 0
1968 t = cls(1, 1, 1, tzinfo=C4())
1969 self.assertRaises(TypeError, t.utcoffset)
1970 self.assertRaises(TypeError, t.dst)
1971 self.assertRaises(TypeError, t.tzname)
1972
1973 # Offset out of range.
Tim Peters855fe882002-12-22 03:43:39 +00001974 class C6(tzinfo):
1975 def utcoffset(self, dt): return timedelta(hours=-24)
1976 def dst(self, dt): return timedelta(hours=24)
Tim Peters397301e2003-01-02 21:28:08 +00001977 t = cls(1, 1, 1, tzinfo=C6())
1978 self.assertRaises(ValueError, t.utcoffset)
1979 self.assertRaises(ValueError, t.dst)
Tim Peters855fe882002-12-22 03:43:39 +00001980
1981 # Not a whole number of minutes.
1982 class C7(tzinfo):
1983 def utcoffset(self, dt): return timedelta(seconds=61)
1984 def dst(self, dt): return timedelta(microseconds=-81)
1985 t = cls(1, 1, 1, tzinfo=C7())
1986 self.assertRaises(ValueError, t.utcoffset)
1987 self.assertRaises(ValueError, t.dst)
1988
Tim Peters4c0db782002-12-26 05:01:19 +00001989 def test_aware_compare(self):
1990 cls = self.theclass
1991
Tim Peters60c76e42002-12-27 00:41:11 +00001992 # Ensure that utcoffset() gets ignored if the comparands have
1993 # the same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00001994 class OperandDependentOffset(tzinfo):
1995 def utcoffset(self, t):
1996 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00001997 # d0 and d1 equal after adjustment
1998 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00001999 else:
Tim Peters397301e2003-01-02 21:28:08 +00002000 # d2 off in the weeds
2001 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00002002
2003 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
2004 d0 = base.replace(minute=3)
2005 d1 = base.replace(minute=9)
2006 d2 = base.replace(minute=11)
2007 for x in d0, d1, d2:
2008 for y in d0, d1, d2:
2009 got = cmp(x, y)
Tim Peters60c76e42002-12-27 00:41:11 +00002010 expected = cmp(x.minute, y.minute)
2011 self.assertEqual(got, expected)
2012
2013 # However, if they're different members, uctoffset is not ignored.
Tim Peters0bf60bd2003-01-08 20:40:01 +00002014 # Note that a time can't actually have an operand-depedent offset,
2015 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
2016 # so skip this test for time.
2017 if cls is not time:
Tim Petersbad8ff02002-12-30 20:52:32 +00002018 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2019 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2020 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2021 for x in d0, d1, d2:
2022 for y in d0, d1, d2:
2023 got = cmp(x, y)
2024 if (x is d0 or x is d1) and (y is d0 or y is d1):
2025 expected = 0
2026 elif x is y is d2:
2027 expected = 0
2028 elif x is d2:
2029 expected = -1
2030 else:
2031 assert y is d2
2032 expected = 1
2033 self.assertEqual(got, expected)
Tim Peters4c0db782002-12-26 05:01:19 +00002034
Tim Peters855fe882002-12-22 03:43:39 +00002035
Tim Peters0bf60bd2003-01-08 20:40:01 +00002036# Testing time objects with a non-None tzinfo.
Tim Peters855fe882002-12-22 03:43:39 +00002037class TestTimeTZ(TestTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002038 theclass = time
Tim Peters2a799bf2002-12-16 20:18:38 +00002039
2040 def test_empty(self):
2041 t = self.theclass()
2042 self.assertEqual(t.hour, 0)
2043 self.assertEqual(t.minute, 0)
2044 self.assertEqual(t.second, 0)
2045 self.assertEqual(t.microsecond, 0)
2046 self.failUnless(t.tzinfo is None)
2047
Tim Peters2a799bf2002-12-16 20:18:38 +00002048 def test_zones(self):
2049 est = FixedOffset(-300, "EST", 1)
2050 utc = FixedOffset(0, "UTC", -2)
2051 met = FixedOffset(60, "MET", 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002052 t1 = time( 7, 47, tzinfo=est)
2053 t2 = time(12, 47, tzinfo=utc)
2054 t3 = time(13, 47, tzinfo=met)
2055 t4 = time(microsecond=40)
2056 t5 = time(microsecond=40, tzinfo=utc)
Tim Peters2a799bf2002-12-16 20:18:38 +00002057
2058 self.assertEqual(t1.tzinfo, est)
2059 self.assertEqual(t2.tzinfo, utc)
2060 self.assertEqual(t3.tzinfo, met)
2061 self.failUnless(t4.tzinfo is None)
2062 self.assertEqual(t5.tzinfo, utc)
2063
Tim Peters855fe882002-12-22 03:43:39 +00002064 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2065 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2066 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002067 self.failUnless(t4.utcoffset() is None)
2068 self.assertRaises(TypeError, t1.utcoffset, "no args")
2069
2070 self.assertEqual(t1.tzname(), "EST")
2071 self.assertEqual(t2.tzname(), "UTC")
2072 self.assertEqual(t3.tzname(), "MET")
2073 self.failUnless(t4.tzname() is None)
2074 self.assertRaises(TypeError, t1.tzname, "no args")
2075
Tim Peters855fe882002-12-22 03:43:39 +00002076 self.assertEqual(t1.dst(), timedelta(minutes=1))
2077 self.assertEqual(t2.dst(), timedelta(minutes=-2))
2078 self.assertEqual(t3.dst(), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +00002079 self.failUnless(t4.dst() is None)
2080 self.assertRaises(TypeError, t1.dst, "no args")
2081
2082 self.assertEqual(hash(t1), hash(t2))
2083 self.assertEqual(hash(t1), hash(t3))
2084 self.assertEqual(hash(t2), hash(t3))
2085
2086 self.assertEqual(t1, t2)
2087 self.assertEqual(t1, t3)
2088 self.assertEqual(t2, t3)
2089 self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
2090 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
2091 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
2092
2093 self.assertEqual(str(t1), "07:47:00-05:00")
2094 self.assertEqual(str(t2), "12:47:00+00:00")
2095 self.assertEqual(str(t3), "13:47:00+01:00")
2096 self.assertEqual(str(t4), "00:00:00.000040")
2097 self.assertEqual(str(t5), "00:00:00.000040+00:00")
2098
2099 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
2100 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
2101 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
2102 self.assertEqual(t4.isoformat(), "00:00:00.000040")
2103 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
2104
Tim Peters0bf60bd2003-01-08 20:40:01 +00002105 d = 'datetime.time'
Tim Peters2a799bf2002-12-16 20:18:38 +00002106 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
2107 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
2108 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
2109 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
2110 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2111
2112 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2113 "07:47:00 %Z=EST %z=-0500")
2114 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2115 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2116
2117 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002118 t1 = time(23, 59, tzinfo=yuck)
Tim Peters2a799bf2002-12-16 20:18:38 +00002119 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2120 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2121
Tim Petersb92bb712002-12-21 17:44:07 +00002122 # Check that an invalid tzname result raises an exception.
2123 class Badtzname(tzinfo):
2124 def tzname(self, dt): return 42
Tim Peters0bf60bd2003-01-08 20:40:01 +00002125 t = time(2, 3, 4, tzinfo=Badtzname())
Tim Petersb92bb712002-12-21 17:44:07 +00002126 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2127 self.assertRaises(TypeError, t.strftime, "%Z")
Tim Peters2a799bf2002-12-16 20:18:38 +00002128
2129 def test_hash_edge_cases(self):
2130 # Offsets that overflow a basic time.
2131 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2132 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2133 self.assertEqual(hash(t1), hash(t2))
2134
2135 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2136 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2137 self.assertEqual(hash(t1), hash(t2))
2138
Tim Peters2a799bf2002-12-16 20:18:38 +00002139 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002140 # Try one without a tzinfo.
2141 args = 20, 59, 16, 64**2
2142 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002143 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002144 green = pickler.dumps(orig, proto)
2145 derived = unpickler.loads(green)
2146 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002147
2148 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002149 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002150 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002151 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002152 green = pickler.dumps(orig, proto)
2153 derived = unpickler.loads(green)
2154 self.assertEqual(orig, derived)
2155 self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
2156 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2157 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002158
2159 def test_more_bool(self):
2160 # Test cases with non-None tzinfo.
2161 cls = self.theclass
2162
2163 t = cls(0, tzinfo=FixedOffset(-300, ""))
2164 self.failUnless(t)
2165
2166 t = cls(5, tzinfo=FixedOffset(-300, ""))
2167 self.failUnless(t)
2168
2169 t = cls(5, tzinfo=FixedOffset(300, ""))
2170 self.failUnless(not t)
2171
2172 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2173 self.failUnless(not t)
2174
2175 # Mostly ensuring this doesn't overflow internally.
2176 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
2177 self.failUnless(t)
2178
2179 # But this should yield a value error -- the utcoffset is bogus.
2180 t = cls(0, tzinfo=FixedOffset(24*60, ""))
2181 self.assertRaises(ValueError, lambda: bool(t))
2182
2183 # Likewise.
2184 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
2185 self.assertRaises(ValueError, lambda: bool(t))
2186
Tim Peters12bf3392002-12-24 05:41:27 +00002187 def test_replace(self):
2188 cls = self.theclass
2189 z100 = FixedOffset(100, "+100")
2190 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2191 args = [1, 2, 3, 4, z100]
2192 base = cls(*args)
2193 self.assertEqual(base, base.replace())
2194
2195 i = 0
2196 for name, newval in (("hour", 5),
2197 ("minute", 6),
2198 ("second", 7),
2199 ("microsecond", 8),
2200 ("tzinfo", zm200)):
2201 newargs = args[:]
2202 newargs[i] = newval
2203 expected = cls(*newargs)
2204 got = base.replace(**{name: newval})
2205 self.assertEqual(expected, got)
2206 i += 1
2207
2208 # Ensure we can get rid of a tzinfo.
2209 self.assertEqual(base.tzname(), "+100")
2210 base2 = base.replace(tzinfo=None)
2211 self.failUnless(base2.tzinfo is None)
2212 self.failUnless(base2.tzname() is None)
2213
2214 # Ensure we can add one.
2215 base3 = base2.replace(tzinfo=z100)
2216 self.assertEqual(base, base3)
2217 self.failUnless(base.tzinfo is base3.tzinfo)
2218
2219 # Out of bounds.
2220 base = cls(1)
2221 self.assertRaises(ValueError, base.replace, hour=24)
2222 self.assertRaises(ValueError, base.replace, minute=-1)
2223 self.assertRaises(ValueError, base.replace, second=100)
2224 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2225
Tim Peters60c76e42002-12-27 00:41:11 +00002226 def test_mixed_compare(self):
2227 t1 = time(1, 2, 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002228 t2 = time(1, 2, 3)
Tim Peters60c76e42002-12-27 00:41:11 +00002229 self.assertEqual(t1, t2)
2230 t2 = t2.replace(tzinfo=None)
2231 self.assertEqual(t1, t2)
2232 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2233 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002234 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2235 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002236
Tim Peters0bf60bd2003-01-08 20:40:01 +00002237 # In time w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002238 class Varies(tzinfo):
2239 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002240 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002241 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002242 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002243 return self.offset
2244
2245 v = Varies()
2246 t1 = t2.replace(tzinfo=v)
2247 t2 = t2.replace(tzinfo=v)
2248 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2249 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2250 self.assertEqual(t1, t2)
2251
2252 # But if they're not identical, it isn't ignored.
2253 t2 = t2.replace(tzinfo=Varies())
2254 self.failUnless(t1 < t2) # t1's offset counter still going up
2255
Tim Petersa98924a2003-05-17 05:55:19 +00002256 def test_subclass_timetz(self):
2257
2258 class C(self.theclass):
2259 theAnswer = 42
2260
2261 def __new__(cls, *args, **kws):
2262 temp = kws.copy()
2263 extra = temp.pop('extra')
2264 result = self.theclass.__new__(cls, *args, **temp)
2265 result.extra = extra
2266 return result
2267
2268 def newmeth(self, start):
2269 return start + self.hour + self.second
2270
2271 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2272
2273 dt1 = self.theclass(*args)
2274 dt2 = C(*args, **{'extra': 7})
2275
2276 self.assertEqual(dt2.__class__, C)
2277 self.assertEqual(dt2.theAnswer, 42)
2278 self.assertEqual(dt2.extra, 7)
2279 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2280 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2281
Tim Peters4c0db782002-12-26 05:01:19 +00002282
Tim Peters0bf60bd2003-01-08 20:40:01 +00002283# Testing datetime objects with a non-None tzinfo.
2284
Tim Peters855fe882002-12-22 03:43:39 +00002285class TestDateTimeTZ(TestDateTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002286 theclass = datetime
Tim Peters2a799bf2002-12-16 20:18:38 +00002287
2288 def test_trivial(self):
2289 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2290 self.assertEqual(dt.year, 1)
2291 self.assertEqual(dt.month, 2)
2292 self.assertEqual(dt.day, 3)
2293 self.assertEqual(dt.hour, 4)
2294 self.assertEqual(dt.minute, 5)
2295 self.assertEqual(dt.second, 6)
2296 self.assertEqual(dt.microsecond, 7)
2297 self.assertEqual(dt.tzinfo, None)
2298
2299 def test_even_more_compare(self):
2300 # The test_compare() and test_more_compare() inherited from TestDate
2301 # and TestDateTime covered non-tzinfo cases.
2302
2303 # Smallest possible after UTC adjustment.
2304 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2305 # Largest possible after UTC adjustment.
2306 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2307 tzinfo=FixedOffset(-1439, ""))
2308
2309 # Make sure those compare correctly, and w/o overflow.
2310 self.failUnless(t1 < t2)
2311 self.failUnless(t1 != t2)
2312 self.failUnless(t2 > t1)
2313
2314 self.failUnless(t1 == t1)
2315 self.failUnless(t2 == t2)
2316
2317 # Equal afer adjustment.
2318 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2319 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2320 self.assertEqual(t1, t2)
2321
2322 # Change t1 not to subtract a minute, and t1 should be larger.
2323 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2324 self.failUnless(t1 > t2)
2325
2326 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2327 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2328 self.failUnless(t1 < t2)
2329
2330 # Back to the original t1, but make seconds resolve it.
2331 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2332 second=1)
2333 self.failUnless(t1 > t2)
2334
2335 # Likewise, but make microseconds resolve it.
2336 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2337 microsecond=1)
2338 self.failUnless(t1 > t2)
2339
2340 # Make t2 naive and it should fail.
2341 t2 = self.theclass.min
2342 self.assertRaises(TypeError, lambda: t1 == t2)
2343 self.assertEqual(t2, t2)
2344
2345 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2346 class Naive(tzinfo):
2347 def utcoffset(self, dt): return None
2348 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
2349 self.assertRaises(TypeError, lambda: t1 == t2)
2350 self.assertEqual(t2, t2)
2351
2352 # OTOH, it's OK to compare two of these mixing the two ways of being
2353 # naive.
2354 t1 = self.theclass(5, 6, 7)
2355 self.assertEqual(t1, t2)
2356
2357 # Try a bogus uctoffset.
2358 class Bogus(tzinfo):
Tim Peters397301e2003-01-02 21:28:08 +00002359 def utcoffset(self, dt):
2360 return timedelta(minutes=1440) # out of bounds
Tim Peters2a799bf2002-12-16 20:18:38 +00002361 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2362 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
Tim Peters60c76e42002-12-27 00:41:11 +00002363 self.assertRaises(ValueError, lambda: t1 == t2)
Tim Peters2a799bf2002-12-16 20:18:38 +00002364
Tim Peters2a799bf2002-12-16 20:18:38 +00002365 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002366 # Try one without a tzinfo.
2367 args = 6, 7, 23, 20, 59, 1, 64**2
2368 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002369 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002370 green = pickler.dumps(orig, proto)
2371 derived = unpickler.loads(green)
2372 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002373
2374 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002375 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002376 orig = self.theclass(*args, **{'tzinfo': tinfo})
Tim Petersa9bc1682003-01-11 03:39:11 +00002377 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
Guido van Rossum177e41a2003-01-30 22:06:23 +00002378 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002379 green = pickler.dumps(orig, proto)
2380 derived = unpickler.loads(green)
2381 self.assertEqual(orig, derived)
2382 self.failUnless(isinstance(derived.tzinfo,
2383 PicklableFixedOffset))
2384 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2385 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002386
2387 def test_extreme_hashes(self):
2388 # If an attempt is made to hash these via subtracting the offset
2389 # then hashing a datetime object, OverflowError results. The
2390 # Python implementation used to blow up here.
2391 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2392 hash(t)
2393 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2394 tzinfo=FixedOffset(-1439, ""))
2395 hash(t)
2396
2397 # OTOH, an OOB offset should blow up.
2398 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2399 self.assertRaises(ValueError, hash, t)
2400
2401 def test_zones(self):
2402 est = FixedOffset(-300, "EST")
2403 utc = FixedOffset(0, "UTC")
2404 met = FixedOffset(60, "MET")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002405 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2406 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2407 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
Tim Peters2a799bf2002-12-16 20:18:38 +00002408 self.assertEqual(t1.tzinfo, est)
2409 self.assertEqual(t2.tzinfo, utc)
2410 self.assertEqual(t3.tzinfo, met)
Tim Peters855fe882002-12-22 03:43:39 +00002411 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2412 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2413 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002414 self.assertEqual(t1.tzname(), "EST")
2415 self.assertEqual(t2.tzname(), "UTC")
2416 self.assertEqual(t3.tzname(), "MET")
2417 self.assertEqual(hash(t1), hash(t2))
2418 self.assertEqual(hash(t1), hash(t3))
2419 self.assertEqual(hash(t2), hash(t3))
2420 self.assertEqual(t1, t2)
2421 self.assertEqual(t1, t3)
2422 self.assertEqual(t2, t3)
2423 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2424 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2425 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002426 d = 'datetime.datetime(2002, 3, 19, '
Tim Peters2a799bf2002-12-16 20:18:38 +00002427 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2428 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2429 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2430
2431 def test_combine(self):
2432 met = FixedOffset(60, "MET")
2433 d = date(2002, 3, 4)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002434 tz = time(18, 45, 3, 1234, tzinfo=met)
2435 dt = datetime.combine(d, tz)
2436 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
Tim Peters2a799bf2002-12-16 20:18:38 +00002437 tzinfo=met))
2438
2439 def test_extract(self):
2440 met = FixedOffset(60, "MET")
2441 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2442 self.assertEqual(dt.date(), date(2002, 3, 4))
2443 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
Tim Peters0bf60bd2003-01-08 20:40:01 +00002444 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
Tim Peters2a799bf2002-12-16 20:18:38 +00002445
2446 def test_tz_aware_arithmetic(self):
2447 import random
2448
2449 now = self.theclass.now()
2450 tz55 = FixedOffset(-330, "west 5:30")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002451 timeaware = now.time().replace(tzinfo=tz55)
Tim Peters2a799bf2002-12-16 20:18:38 +00002452 nowaware = self.theclass.combine(now.date(), timeaware)
2453 self.failUnless(nowaware.tzinfo is tz55)
2454 self.assertEqual(nowaware.timetz(), timeaware)
2455
2456 # Can't mix aware and non-aware.
2457 self.assertRaises(TypeError, lambda: now - nowaware)
2458 self.assertRaises(TypeError, lambda: nowaware - now)
2459
Tim Peters0bf60bd2003-01-08 20:40:01 +00002460 # And adding datetime's doesn't make sense, aware or not.
Tim Peters2a799bf2002-12-16 20:18:38 +00002461 self.assertRaises(TypeError, lambda: now + nowaware)
2462 self.assertRaises(TypeError, lambda: nowaware + now)
2463 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2464
2465 # Subtracting should yield 0.
2466 self.assertEqual(now - now, timedelta(0))
2467 self.assertEqual(nowaware - nowaware, timedelta(0))
2468
2469 # Adding a delta should preserve tzinfo.
2470 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2471 nowawareplus = nowaware + delta
2472 self.failUnless(nowaware.tzinfo is tz55)
2473 nowawareplus2 = delta + nowaware
2474 self.failUnless(nowawareplus2.tzinfo is tz55)
2475 self.assertEqual(nowawareplus, nowawareplus2)
2476
2477 # that - delta should be what we started with, and that - what we
2478 # started with should be delta.
2479 diff = nowawareplus - delta
2480 self.failUnless(diff.tzinfo is tz55)
2481 self.assertEqual(nowaware, diff)
2482 self.assertRaises(TypeError, lambda: delta - nowawareplus)
2483 self.assertEqual(nowawareplus - nowaware, delta)
2484
2485 # Make up a random timezone.
2486 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
Tim Peters4c0db782002-12-26 05:01:19 +00002487 # Attach it to nowawareplus.
2488 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Tim Peters2a799bf2002-12-16 20:18:38 +00002489 self.failUnless(nowawareplus.tzinfo is tzr)
2490 # Make sure the difference takes the timezone adjustments into account.
2491 got = nowaware - nowawareplus
2492 # Expected: (nowaware base - nowaware offset) -
2493 # (nowawareplus base - nowawareplus offset) =
2494 # (nowaware base - nowawareplus base) +
2495 # (nowawareplus offset - nowaware offset) =
2496 # -delta + nowawareplus offset - nowaware offset
Tim Peters855fe882002-12-22 03:43:39 +00002497 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
Tim Peters2a799bf2002-12-16 20:18:38 +00002498 self.assertEqual(got, expected)
2499
2500 # Try max possible difference.
2501 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2502 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2503 tzinfo=FixedOffset(-1439, "max"))
2504 maxdiff = max - min
2505 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2506 timedelta(minutes=2*1439))
2507
2508 def test_tzinfo_now(self):
2509 meth = self.theclass.now
2510 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2511 base = meth()
2512 # Try with and without naming the keyword.
2513 off42 = FixedOffset(42, "42")
2514 another = meth(off42)
Tim Peters10cadce2003-01-23 19:58:02 +00002515 again = meth(tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002516 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002517 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002518 # Bad argument with and w/o naming the keyword.
2519 self.assertRaises(TypeError, meth, 16)
2520 self.assertRaises(TypeError, meth, tzinfo=16)
2521 # Bad keyword name.
2522 self.assertRaises(TypeError, meth, tinfo=off42)
2523 # Too many args.
2524 self.assertRaises(TypeError, meth, off42, off42)
2525
Tim Peters10cadce2003-01-23 19:58:02 +00002526 # We don't know which time zone we're in, and don't have a tzinfo
2527 # class to represent it, so seeing whether a tz argument actually
2528 # does a conversion is tricky.
2529 weirdtz = FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0)
2530 utc = FixedOffset(0, "utc", 0)
2531 for dummy in range(3):
2532 now = datetime.now(weirdtz)
2533 self.failUnless(now.tzinfo is weirdtz)
2534 utcnow = datetime.utcnow().replace(tzinfo=utc)
2535 now2 = utcnow.astimezone(weirdtz)
2536 if abs(now - now2) < timedelta(seconds=30):
2537 break
2538 # Else the code is broken, or more than 30 seconds passed between
2539 # calls; assuming the latter, just try again.
2540 else:
2541 # Three strikes and we're out.
2542 self.fail("utcnow(), now(tz), or astimezone() may be broken")
2543
Tim Peters2a799bf2002-12-16 20:18:38 +00002544 def test_tzinfo_fromtimestamp(self):
2545 import time
2546 meth = self.theclass.fromtimestamp
2547 ts = time.time()
2548 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2549 base = meth(ts)
2550 # Try with and without naming the keyword.
2551 off42 = FixedOffset(42, "42")
2552 another = meth(ts, off42)
Tim Peters2a44a8d2003-01-23 20:53:10 +00002553 again = meth(ts, tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002554 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002555 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002556 # Bad argument with and w/o naming the keyword.
2557 self.assertRaises(TypeError, meth, ts, 16)
2558 self.assertRaises(TypeError, meth, ts, tzinfo=16)
2559 # Bad keyword name.
2560 self.assertRaises(TypeError, meth, ts, tinfo=off42)
2561 # Too many args.
2562 self.assertRaises(TypeError, meth, ts, off42, off42)
2563 # Too few args.
2564 self.assertRaises(TypeError, meth)
2565
Tim Peters2a44a8d2003-01-23 20:53:10 +00002566 # Try to make sure tz= actually does some conversion.
Tim Peters84407612003-02-06 16:42:14 +00002567 timestamp = 1000000000
2568 utcdatetime = datetime.utcfromtimestamp(timestamp)
2569 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
2570 # But on some flavor of Mac, it's nowhere near that. So we can't have
2571 # any idea here what time that actually is, we can only test that
2572 # relative changes match.
2573 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
2574 tz = FixedOffset(utcoffset, "tz", 0)
2575 expected = utcdatetime + utcoffset
2576 got = datetime.fromtimestamp(timestamp, tz)
2577 self.assertEqual(expected, got.replace(tzinfo=None))
Tim Peters2a44a8d2003-01-23 20:53:10 +00002578
Tim Peters2a799bf2002-12-16 20:18:38 +00002579 def test_tzinfo_utcnow(self):
2580 meth = self.theclass.utcnow
2581 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2582 base = meth()
2583 # Try with and without naming the keyword; for whatever reason,
2584 # utcnow() doesn't accept a tzinfo argument.
2585 off42 = FixedOffset(42, "42")
2586 self.assertRaises(TypeError, meth, off42)
2587 self.assertRaises(TypeError, meth, tzinfo=off42)
2588
2589 def test_tzinfo_utcfromtimestamp(self):
2590 import time
2591 meth = self.theclass.utcfromtimestamp
2592 ts = time.time()
2593 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2594 base = meth(ts)
2595 # Try with and without naming the keyword; for whatever reason,
2596 # utcfromtimestamp() doesn't accept a tzinfo argument.
2597 off42 = FixedOffset(42, "42")
2598 self.assertRaises(TypeError, meth, ts, off42)
2599 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
2600
2601 def test_tzinfo_timetuple(self):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002602 # TestDateTime tested most of this. datetime adds a twist to the
Tim Peters2a799bf2002-12-16 20:18:38 +00002603 # DST flag.
2604 class DST(tzinfo):
2605 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002606 if isinstance(dstvalue, int):
2607 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002608 self.dstvalue = dstvalue
2609 def dst(self, dt):
2610 return self.dstvalue
2611
2612 cls = self.theclass
2613 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
2614 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
2615 t = d.timetuple()
2616 self.assertEqual(1, t.tm_year)
2617 self.assertEqual(1, t.tm_mon)
2618 self.assertEqual(1, t.tm_mday)
2619 self.assertEqual(10, t.tm_hour)
2620 self.assertEqual(20, t.tm_min)
2621 self.assertEqual(30, t.tm_sec)
2622 self.assertEqual(0, t.tm_wday)
2623 self.assertEqual(1, t.tm_yday)
2624 self.assertEqual(flag, t.tm_isdst)
2625
2626 # dst() returns wrong type.
2627 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
2628
2629 # dst() at the edge.
2630 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
2631 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
2632
2633 # dst() out of range.
2634 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
2635 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
2636
2637 def test_utctimetuple(self):
2638 class DST(tzinfo):
2639 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002640 if isinstance(dstvalue, int):
2641 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002642 self.dstvalue = dstvalue
2643 def dst(self, dt):
2644 return self.dstvalue
2645
2646 cls = self.theclass
2647 # This can't work: DST didn't implement utcoffset.
2648 self.assertRaises(NotImplementedError,
2649 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
2650
2651 class UOFS(DST):
2652 def __init__(self, uofs, dofs=None):
2653 DST.__init__(self, dofs)
Tim Peters397301e2003-01-02 21:28:08 +00002654 self.uofs = timedelta(minutes=uofs)
Tim Peters2a799bf2002-12-16 20:18:38 +00002655 def utcoffset(self, dt):
2656 return self.uofs
2657
2658 # Ensure tm_isdst is 0 regardless of what dst() says: DST is never
2659 # in effect for a UTC time.
2660 for dstvalue in -33, 33, 0, None:
2661 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
2662 t = d.utctimetuple()
2663 self.assertEqual(d.year, t.tm_year)
2664 self.assertEqual(d.month, t.tm_mon)
2665 self.assertEqual(d.day, t.tm_mday)
2666 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
2667 self.assertEqual(13, t.tm_min)
2668 self.assertEqual(d.second, t.tm_sec)
2669 self.assertEqual(d.weekday(), t.tm_wday)
2670 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
2671 t.tm_yday)
2672 self.assertEqual(0, t.tm_isdst)
2673
2674 # At the edges, UTC adjustment can normalize into years out-of-range
2675 # for a datetime object. Ensure that a correct timetuple is
2676 # created anyway.
2677 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
2678 # That goes back 1 minute less than a full day.
2679 t = tiny.utctimetuple()
2680 self.assertEqual(t.tm_year, MINYEAR-1)
2681 self.assertEqual(t.tm_mon, 12)
2682 self.assertEqual(t.tm_mday, 31)
2683 self.assertEqual(t.tm_hour, 0)
2684 self.assertEqual(t.tm_min, 1)
2685 self.assertEqual(t.tm_sec, 37)
2686 self.assertEqual(t.tm_yday, 366) # "year 0" is a leap year
2687 self.assertEqual(t.tm_isdst, 0)
2688
2689 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
2690 # That goes forward 1 minute less than a full day.
2691 t = huge.utctimetuple()
2692 self.assertEqual(t.tm_year, MAXYEAR+1)
2693 self.assertEqual(t.tm_mon, 1)
2694 self.assertEqual(t.tm_mday, 1)
2695 self.assertEqual(t.tm_hour, 23)
2696 self.assertEqual(t.tm_min, 58)
2697 self.assertEqual(t.tm_sec, 37)
2698 self.assertEqual(t.tm_yday, 1)
2699 self.assertEqual(t.tm_isdst, 0)
2700
2701 def test_tzinfo_isoformat(self):
2702 zero = FixedOffset(0, "+00:00")
2703 plus = FixedOffset(220, "+03:40")
2704 minus = FixedOffset(-231, "-03:51")
2705 unknown = FixedOffset(None, "")
2706
2707 cls = self.theclass
2708 datestr = '0001-02-03'
2709 for ofs in None, zero, plus, minus, unknown:
Tim Peters6578dc92002-12-24 18:31:27 +00002710 for us in 0, 987001:
Tim Peters2a799bf2002-12-16 20:18:38 +00002711 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
2712 timestr = '04:05:59' + (us and '.987001' or '')
2713 ofsstr = ofs is not None and d.tzname() or ''
2714 tailstr = timestr + ofsstr
2715 iso = d.isoformat()
2716 self.assertEqual(iso, datestr + 'T' + tailstr)
2717 self.assertEqual(iso, d.isoformat('T'))
2718 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
2719 self.assertEqual(str(d), datestr + ' ' + tailstr)
2720
Tim Peters12bf3392002-12-24 05:41:27 +00002721 def test_replace(self):
2722 cls = self.theclass
2723 z100 = FixedOffset(100, "+100")
2724 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2725 args = [1, 2, 3, 4, 5, 6, 7, z100]
2726 base = cls(*args)
2727 self.assertEqual(base, base.replace())
2728
2729 i = 0
2730 for name, newval in (("year", 2),
2731 ("month", 3),
2732 ("day", 4),
2733 ("hour", 5),
2734 ("minute", 6),
2735 ("second", 7),
2736 ("microsecond", 8),
2737 ("tzinfo", zm200)):
2738 newargs = args[:]
2739 newargs[i] = newval
2740 expected = cls(*newargs)
2741 got = base.replace(**{name: newval})
2742 self.assertEqual(expected, got)
2743 i += 1
2744
2745 # Ensure we can get rid of a tzinfo.
2746 self.assertEqual(base.tzname(), "+100")
2747 base2 = base.replace(tzinfo=None)
2748 self.failUnless(base2.tzinfo is None)
2749 self.failUnless(base2.tzname() is None)
2750
2751 # Ensure we can add one.
2752 base3 = base2.replace(tzinfo=z100)
2753 self.assertEqual(base, base3)
2754 self.failUnless(base.tzinfo is base3.tzinfo)
2755
2756 # Out of bounds.
2757 base = cls(2000, 2, 29)
2758 self.assertRaises(ValueError, base.replace, year=2001)
Tim Peters2a799bf2002-12-16 20:18:38 +00002759
Tim Peters80475bb2002-12-25 07:40:55 +00002760 def test_more_astimezone(self):
2761 # The inherited test_astimezone covered some trivial and error cases.
2762 fnone = FixedOffset(None, "None")
2763 f44m = FixedOffset(44, "44")
2764 fm5h = FixedOffset(-timedelta(hours=5), "m300")
2765
Tim Peters10cadce2003-01-23 19:58:02 +00002766 dt = self.theclass.now(tz=f44m)
Tim Peters80475bb2002-12-25 07:40:55 +00002767 self.failUnless(dt.tzinfo is f44m)
Tim Peters52dcce22003-01-23 16:36:11 +00002768 # Replacing with degenerate tzinfo raises an exception.
2769 self.assertRaises(ValueError, dt.astimezone, fnone)
2770 # Ditto with None tz.
2771 self.assertRaises(TypeError, dt.astimezone, None)
2772 # Replacing with same tzinfo makes no change.
Tim Peters80475bb2002-12-25 07:40:55 +00002773 x = dt.astimezone(dt.tzinfo)
2774 self.failUnless(x.tzinfo is f44m)
2775 self.assertEqual(x.date(), dt.date())
2776 self.assertEqual(x.time(), dt.time())
2777
2778 # Replacing with different tzinfo does adjust.
2779 got = dt.astimezone(fm5h)
2780 self.failUnless(got.tzinfo is fm5h)
2781 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
2782 expected = dt - dt.utcoffset() # in effect, convert to UTC
2783 expected += fm5h.utcoffset(dt) # and from there to local time
2784 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
2785 self.assertEqual(got.date(), expected.date())
2786 self.assertEqual(got.time(), expected.time())
2787 self.assertEqual(got.timetz(), expected.timetz())
2788 self.failUnless(got.tzinfo is expected.tzinfo)
2789 self.assertEqual(got, expected)
2790
Tim Peters4c0db782002-12-26 05:01:19 +00002791 def test_aware_subtract(self):
2792 cls = self.theclass
2793
Tim Peters60c76e42002-12-27 00:41:11 +00002794 # Ensure that utcoffset() is ignored when the operands have the
2795 # same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00002796 class OperandDependentOffset(tzinfo):
2797 def utcoffset(self, t):
2798 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00002799 # d0 and d1 equal after adjustment
2800 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00002801 else:
Tim Peters397301e2003-01-02 21:28:08 +00002802 # d2 off in the weeds
2803 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00002804
2805 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
2806 d0 = base.replace(minute=3)
2807 d1 = base.replace(minute=9)
2808 d2 = base.replace(minute=11)
2809 for x in d0, d1, d2:
2810 for y in d0, d1, d2:
2811 got = x - y
Tim Peters60c76e42002-12-27 00:41:11 +00002812 expected = timedelta(minutes=x.minute - y.minute)
2813 self.assertEqual(got, expected)
2814
2815 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
2816 # ignored.
2817 base = cls(8, 9, 10, 11, 12, 13, 14)
2818 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2819 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2820 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2821 for x in d0, d1, d2:
2822 for y in d0, d1, d2:
2823 got = x - y
Tim Peters4c0db782002-12-26 05:01:19 +00002824 if (x is d0 or x is d1) and (y is d0 or y is d1):
2825 expected = timedelta(0)
2826 elif x is y is d2:
2827 expected = timedelta(0)
2828 elif x is d2:
2829 expected = timedelta(minutes=(11-59)-0)
2830 else:
2831 assert y is d2
2832 expected = timedelta(minutes=0-(11-59))
2833 self.assertEqual(got, expected)
2834
Tim Peters60c76e42002-12-27 00:41:11 +00002835 def test_mixed_compare(self):
2836 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002837 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters60c76e42002-12-27 00:41:11 +00002838 self.assertEqual(t1, t2)
2839 t2 = t2.replace(tzinfo=None)
2840 self.assertEqual(t1, t2)
2841 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2842 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002843 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2844 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002845
Tim Peters0bf60bd2003-01-08 20:40:01 +00002846 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002847 class Varies(tzinfo):
2848 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002849 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002850 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002851 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002852 return self.offset
2853
2854 v = Varies()
2855 t1 = t2.replace(tzinfo=v)
2856 t2 = t2.replace(tzinfo=v)
2857 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2858 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2859 self.assertEqual(t1, t2)
2860
2861 # But if they're not identical, it isn't ignored.
2862 t2 = t2.replace(tzinfo=Varies())
2863 self.failUnless(t1 < t2) # t1's offset counter still going up
Tim Peters80475bb2002-12-25 07:40:55 +00002864
Tim Petersa98924a2003-05-17 05:55:19 +00002865 def test_subclass_datetimetz(self):
2866
2867 class C(self.theclass):
2868 theAnswer = 42
2869
2870 def __new__(cls, *args, **kws):
2871 temp = kws.copy()
2872 extra = temp.pop('extra')
2873 result = self.theclass.__new__(cls, *args, **temp)
2874 result.extra = extra
2875 return result
2876
2877 def newmeth(self, start):
2878 return start + self.hour + self.year
2879
2880 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2881
2882 dt1 = self.theclass(*args)
2883 dt2 = C(*args, **{'extra': 7})
2884
2885 self.assertEqual(dt2.__class__, C)
2886 self.assertEqual(dt2.theAnswer, 42)
2887 self.assertEqual(dt2.extra, 7)
2888 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2889 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
2890
Tim Peters621818b2002-12-29 23:44:49 +00002891# Pain to set up DST-aware tzinfo classes.
2892
2893def first_sunday_on_or_after(dt):
2894 days_to_go = 6 - dt.weekday()
2895 if days_to_go:
2896 dt += timedelta(days_to_go)
2897 return dt
2898
2899ZERO = timedelta(0)
2900HOUR = timedelta(hours=1)
2901DAY = timedelta(days=1)
2902# In the US, DST starts at 2am (standard time) on the first Sunday in April.
2903DSTSTART = datetime(1, 4, 1, 2)
2904# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
Tim Peters327098a2003-01-20 22:54:38 +00002905# which is the first Sunday on or after Oct 25. Because we view 1:MM as
2906# being standard time on that day, there is no spelling in local time of
2907# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
2908DSTEND = datetime(1, 10, 25, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002909
2910class USTimeZone(tzinfo):
2911
2912 def __init__(self, hours, reprname, stdname, dstname):
2913 self.stdoffset = timedelta(hours=hours)
2914 self.reprname = reprname
2915 self.stdname = stdname
2916 self.dstname = dstname
2917
2918 def __repr__(self):
2919 return self.reprname
2920
2921 def tzname(self, dt):
2922 if self.dst(dt):
2923 return self.dstname
2924 else:
2925 return self.stdname
2926
2927 def utcoffset(self, dt):
2928 return self.stdoffset + self.dst(dt)
2929
2930 def dst(self, dt):
Tim Petersbad8ff02002-12-30 20:52:32 +00002931 if dt is None or dt.tzinfo is None:
Tim Peters621818b2002-12-29 23:44:49 +00002932 # An exception instead may be sensible here, in one or more of
2933 # the cases.
2934 return ZERO
Tim Peters521fc152002-12-31 17:36:56 +00002935 assert dt.tzinfo is self
Tim Peters621818b2002-12-29 23:44:49 +00002936
2937 # Find first Sunday in April.
2938 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
2939 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
2940
2941 # Find last Sunday in October.
2942 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
2943 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
2944
Tim Peters621818b2002-12-29 23:44:49 +00002945 # Can't compare naive to aware objects, so strip the timezone from
2946 # dt first.
Tim Peters52dcce22003-01-23 16:36:11 +00002947 if start <= dt.replace(tzinfo=None) < end:
Tim Peters621818b2002-12-29 23:44:49 +00002948 return HOUR
2949 else:
2950 return ZERO
2951
Tim Peters521fc152002-12-31 17:36:56 +00002952Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
2953Central = USTimeZone(-6, "Central", "CST", "CDT")
2954Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
2955Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
Tim Peters1024bf82002-12-30 17:09:40 +00002956utc_real = FixedOffset(0, "UTC", 0)
2957# For better test coverage, we want another flavor of UTC that's west of
2958# the Eastern and Pacific timezones.
Tim Petersadf64202003-01-04 06:03:15 +00002959utc_fake = FixedOffset(-12*60, "UTCfake", 0)
Tim Peters621818b2002-12-29 23:44:49 +00002960
2961class TestTimezoneConversions(unittest.TestCase):
Tim Peters327098a2003-01-20 22:54:38 +00002962 # The DST switch times for 2002, in std time.
Tim Peters0bf60bd2003-01-08 20:40:01 +00002963 dston = datetime(2002, 4, 7, 2)
Tim Peters327098a2003-01-20 22:54:38 +00002964 dstoff = datetime(2002, 10, 27, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002965
Tim Peters0bf60bd2003-01-08 20:40:01 +00002966 theclass = datetime
Tim Peters710fb152003-01-02 19:35:54 +00002967
Tim Peters521fc152002-12-31 17:36:56 +00002968 # Check a time that's inside DST.
2969 def checkinside(self, dt, tz, utc, dston, dstoff):
2970 self.assertEqual(dt.dst(), HOUR)
2971
2972 # Conversion to our own timezone is always an identity.
2973 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters521fc152002-12-31 17:36:56 +00002974
2975 asutc = dt.astimezone(utc)
2976 there_and_back = asutc.astimezone(tz)
2977
2978 # Conversion to UTC and back isn't always an identity here,
2979 # because there are redundant spellings (in local time) of
2980 # UTC time when DST begins: the clock jumps from 1:59:59
2981 # to 3:00:00, and a local time of 2:MM:SS doesn't really
2982 # make sense then. The classes above treat 2:MM:SS as
2983 # daylight time then (it's "after 2am"), really an alias
2984 # for 1:MM:SS standard time. The latter form is what
2985 # conversion back from UTC produces.
2986 if dt.date() == dston.date() and dt.hour == 2:
2987 # We're in the redundant hour, and coming back from
2988 # UTC gives the 1:MM:SS standard-time spelling.
2989 self.assertEqual(there_and_back + HOUR, dt)
2990 # Although during was considered to be in daylight
2991 # time, there_and_back is not.
2992 self.assertEqual(there_and_back.dst(), ZERO)
2993 # They're the same times in UTC.
2994 self.assertEqual(there_and_back.astimezone(utc),
2995 dt.astimezone(utc))
2996 else:
2997 # We're not in the redundant hour.
2998 self.assertEqual(dt, there_and_back)
2999
Tim Peters327098a2003-01-20 22:54:38 +00003000 # Because we have a redundant spelling when DST begins, there is
3001 # (unforunately) an hour when DST ends that can't be spelled at all in
3002 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
3003 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
3004 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
3005 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
3006 # expressed in local time. Nevertheless, we want conversion back
3007 # from UTC to mimic the local clock's "repeat an hour" behavior.
Tim Peters521fc152002-12-31 17:36:56 +00003008 nexthour_utc = asutc + HOUR
Tim Petersadf64202003-01-04 06:03:15 +00003009 nexthour_tz = nexthour_utc.astimezone(tz)
Tim Peters327098a2003-01-20 22:54:38 +00003010 if dt.date() == dstoff.date() and dt.hour == 0:
3011 # We're in the hour before the last DST hour. The last DST hour
Tim Petersadf64202003-01-04 06:03:15 +00003012 # is ineffable. We want the conversion back to repeat 1:MM.
Tim Peters327098a2003-01-20 22:54:38 +00003013 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3014 nexthour_utc += HOUR
3015 nexthour_tz = nexthour_utc.astimezone(tz)
3016 self.assertEqual(nexthour_tz, dt.replace(hour=1))
Tim Peters521fc152002-12-31 17:36:56 +00003017 else:
Tim Peters327098a2003-01-20 22:54:38 +00003018 self.assertEqual(nexthour_tz - dt, HOUR)
Tim Peters521fc152002-12-31 17:36:56 +00003019
3020 # Check a time that's outside DST.
3021 def checkoutside(self, dt, tz, utc):
3022 self.assertEqual(dt.dst(), ZERO)
3023
3024 # Conversion to our own timezone is always an identity.
3025 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters52dcce22003-01-23 16:36:11 +00003026
3027 # Converting to UTC and back is an identity too.
3028 asutc = dt.astimezone(utc)
3029 there_and_back = asutc.astimezone(tz)
3030 self.assertEqual(dt, there_and_back)
Tim Peters521fc152002-12-31 17:36:56 +00003031
Tim Peters1024bf82002-12-30 17:09:40 +00003032 def convert_between_tz_and_utc(self, tz, utc):
3033 dston = self.dston.replace(tzinfo=tz)
Tim Peters327098a2003-01-20 22:54:38 +00003034 # Because 1:MM on the day DST ends is taken as being standard time,
3035 # there is no spelling in tz for the last hour of daylight time.
3036 # For purposes of the test, the last hour of DST is 0:MM, which is
3037 # taken as being daylight time (and 1:MM is taken as being standard
3038 # time).
Tim Peters1024bf82002-12-30 17:09:40 +00003039 dstoff = self.dstoff.replace(tzinfo=tz)
3040 for delta in (timedelta(weeks=13),
3041 DAY,
3042 HOUR,
3043 timedelta(minutes=1),
3044 timedelta(microseconds=1)):
3045
Tim Peters521fc152002-12-31 17:36:56 +00003046 self.checkinside(dston, tz, utc, dston, dstoff)
3047 for during in dston + delta, dstoff - delta:
3048 self.checkinside(during, tz, utc, dston, dstoff)
Tim Peters31cc3152002-12-30 17:37:30 +00003049
Tim Peters521fc152002-12-31 17:36:56 +00003050 self.checkoutside(dstoff, tz, utc)
3051 for outside in dston - delta, dstoff + delta:
3052 self.checkoutside(outside, tz, utc)
Tim Peters31cc3152002-12-30 17:37:30 +00003053
Tim Peters621818b2002-12-29 23:44:49 +00003054 def test_easy(self):
3055 # Despite the name of this test, the endcases are excruciating.
Tim Peters1024bf82002-12-30 17:09:40 +00003056 self.convert_between_tz_and_utc(Eastern, utc_real)
3057 self.convert_between_tz_and_utc(Pacific, utc_real)
3058 self.convert_between_tz_and_utc(Eastern, utc_fake)
3059 self.convert_between_tz_and_utc(Pacific, utc_fake)
3060 # The next is really dancing near the edge. It works because
3061 # Pacific and Eastern are far enough apart that their "problem
3062 # hours" don't overlap.
3063 self.convert_between_tz_and_utc(Eastern, Pacific)
3064 self.convert_between_tz_and_utc(Pacific, Eastern)
Tim Peters36087ed2003-01-01 04:18:51 +00003065 # OTOH, these fail! Don't enable them. The difficulty is that
3066 # the edge case tests assume that every hour is representable in
3067 # the "utc" class. This is always true for a fixed-offset tzinfo
3068 # class (lke utc_real and utc_fake), but not for Eastern or Central.
3069 # For these adjacent DST-aware time zones, the range of time offsets
3070 # tested ends up creating hours in the one that aren't representable
3071 # in the other. For the same reason, we would see failures in the
3072 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
3073 # offset deltas in convert_between_tz_and_utc().
3074 #
3075 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
3076 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
Tim Peters621818b2002-12-29 23:44:49 +00003077
Tim Petersf3615152003-01-01 21:51:37 +00003078 def test_tricky(self):
3079 # 22:00 on day before daylight starts.
3080 fourback = self.dston - timedelta(hours=4)
3081 ninewest = FixedOffset(-9*60, "-0900", 0)
Tim Peters52dcce22003-01-23 16:36:11 +00003082 fourback = fourback.replace(tzinfo=ninewest)
Tim Petersf3615152003-01-01 21:51:37 +00003083 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
3084 # 2", we should get the 3 spelling.
3085 # If we plug 22:00 the day before into Eastern, it "looks like std
3086 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
3087 # to 22:00 lands on 2:00, which makes no sense in local time (the
3088 # local clock jumps from 1 to 3). The point here is to make sure we
3089 # get the 3 spelling.
3090 expected = self.dston.replace(hour=3)
Tim Peters52dcce22003-01-23 16:36:11 +00003091 got = fourback.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00003092 self.assertEqual(expected, got)
3093
3094 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
3095 # case we want the 1:00 spelling.
Tim Peters52dcce22003-01-23 16:36:11 +00003096 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
Tim Petersf3615152003-01-01 21:51:37 +00003097 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
3098 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
3099 # spelling.
3100 expected = self.dston.replace(hour=1)
Tim Peters52dcce22003-01-23 16:36:11 +00003101 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00003102 self.assertEqual(expected, got)
Tim Peters621818b2002-12-29 23:44:49 +00003103
Tim Petersadf64202003-01-04 06:03:15 +00003104 # Now on the day DST ends, we want "repeat an hour" behavior.
3105 # UTC 4:MM 5:MM 6:MM 7:MM checking these
3106 # EST 23:MM 0:MM 1:MM 2:MM
3107 # EDT 0:MM 1:MM 2:MM 3:MM
3108 # wall 0:MM 1:MM 1:MM 2:MM against these
3109 for utc in utc_real, utc_fake:
3110 for tz in Eastern, Pacific:
Tim Peters327098a2003-01-20 22:54:38 +00003111 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
Tim Petersadf64202003-01-04 06:03:15 +00003112 # Convert that to UTC.
3113 first_std_hour -= tz.utcoffset(None)
3114 # Adjust for possibly fake UTC.
3115 asutc = first_std_hour + utc.utcoffset(None)
3116 # First UTC hour to convert; this is 4:00 when utc=utc_real &
3117 # tz=Eastern.
3118 asutcbase = asutc.replace(tzinfo=utc)
3119 for tzhour in (0, 1, 1, 2):
3120 expectedbase = self.dstoff.replace(hour=tzhour)
3121 for minute in 0, 30, 59:
3122 expected = expectedbase.replace(minute=minute)
3123 asutc = asutcbase.replace(minute=minute)
3124 astz = asutc.astimezone(tz)
3125 self.assertEqual(astz.replace(tzinfo=None), expected)
3126 asutcbase += HOUR
3127
3128
Tim Peters710fb152003-01-02 19:35:54 +00003129 def test_bogus_dst(self):
3130 class ok(tzinfo):
3131 def utcoffset(self, dt): return HOUR
3132 def dst(self, dt): return HOUR
3133
3134 now = self.theclass.now().replace(tzinfo=utc_real)
3135 # Doesn't blow up.
3136 now.astimezone(ok())
3137
3138 # Does blow up.
3139 class notok(ok):
3140 def dst(self, dt): return None
3141 self.assertRaises(ValueError, now.astimezone, notok())
3142
Tim Peters52dcce22003-01-23 16:36:11 +00003143 def test_fromutc(self):
3144 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
3145 now = datetime.utcnow().replace(tzinfo=utc_real)
3146 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3147 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
3148 enow = Eastern.fromutc(now) # doesn't blow up
3149 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3150 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3151 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3152
3153 # Always converts UTC to standard time.
3154 class FauxUSTimeZone(USTimeZone):
3155 def fromutc(self, dt):
3156 return dt + self.stdoffset
3157 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
3158
3159 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
3160 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
3161 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
3162
3163 # Check around DST start.
3164 start = self.dston.replace(hour=4, tzinfo=Eastern)
3165 fstart = start.replace(tzinfo=FEastern)
3166 for wall in 23, 0, 1, 3, 4, 5:
3167 expected = start.replace(hour=wall)
3168 if wall == 23:
3169 expected -= timedelta(days=1)
3170 got = Eastern.fromutc(start)
3171 self.assertEqual(expected, got)
3172
3173 expected = fstart + FEastern.stdoffset
3174 got = FEastern.fromutc(fstart)
3175 self.assertEqual(expected, got)
3176
3177 # Ensure astimezone() calls fromutc() too.
3178 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3179 self.assertEqual(expected, got)
3180
3181 start += HOUR
3182 fstart += HOUR
3183
3184 # Check around DST end.
3185 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
3186 fstart = start.replace(tzinfo=FEastern)
3187 for wall in 0, 1, 1, 2, 3, 4:
3188 expected = start.replace(hour=wall)
3189 got = Eastern.fromutc(start)
3190 self.assertEqual(expected, got)
3191
3192 expected = fstart + FEastern.stdoffset
3193 got = FEastern.fromutc(fstart)
3194 self.assertEqual(expected, got)
3195
3196 # Ensure astimezone() calls fromutc() too.
3197 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3198 self.assertEqual(expected, got)
3199
3200 start += HOUR
3201 fstart += HOUR
3202
Tim Peters710fb152003-01-02 19:35:54 +00003203
Tim Peters528ca532004-09-16 01:30:50 +00003204#############################################################################
3205# oddballs
3206
3207class Oddballs(unittest.TestCase):
3208
3209 def test_bug_1028306(self):
3210 # Trying to compare a date to a datetime should act like a mixed-
3211 # type comparison, despite that datetime is a subclass of date.
3212 as_date = date.today()
3213 as_datetime = datetime.combine(as_date, time())
3214 self.assert_(as_date != as_datetime)
3215 self.assert_(as_datetime != as_date)
3216 self.assert_(not as_date == as_datetime)
3217 self.assert_(not as_datetime == as_date)
3218 self.assertRaises(TypeError, lambda: as_date < as_datetime)
3219 self.assertRaises(TypeError, lambda: as_datetime < as_date)
3220 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
3221 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
3222 self.assertRaises(TypeError, lambda: as_date > as_datetime)
3223 self.assertRaises(TypeError, lambda: as_datetime > as_date)
3224 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
3225 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
3226
3227 # Neverthelss, comparison should work with the base-class (date)
3228 # projection if use of a date method is forced.
3229 self.assert_(as_date.__eq__(as_datetime))
3230 different_day = (as_date.day + 1) % 20 + 1
3231 self.assert_(not as_date.__eq__(as_datetime.replace(day=
3232 different_day)))
3233
3234 # And date should compare with other subclasses of date. If a
3235 # subclass wants to stop this, it's up to the subclass to do so.
3236 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
3237 self.assertEqual(as_date, date_sc)
3238 self.assertEqual(date_sc, as_date)
3239
3240 # Ditto for datetimes.
3241 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
3242 as_date.day, 0, 0, 0)
3243 self.assertEqual(as_datetime, datetime_sc)
3244 self.assertEqual(datetime_sc, as_datetime)
3245
Tim Peterscfd4a8b2002-12-16 21:12:37 +00003246def test_suite():
Tim Peters2a799bf2002-12-16 20:18:38 +00003247 allsuites = [unittest.makeSuite(klass, 'test')
3248 for klass in (TestModule,
3249 TestTZInfo,
3250 TestTimeDelta,
3251 TestDateOnly,
3252 TestDate,
3253 TestDateTime,
3254 TestTime,
3255 TestTimeTZ,
3256 TestDateTimeTZ,
Tim Peters621818b2002-12-29 23:44:49 +00003257 TestTimezoneConversions,
Tim Peters528ca532004-09-16 01:30:50 +00003258 Oddballs,
Tim Peters2a799bf2002-12-16 20:18:38 +00003259 )
3260 ]
3261 return unittest.TestSuite(allsuites)
3262
3263def test_main():
3264 import gc
3265 import sys
3266
Tim Peterscfd4a8b2002-12-16 21:12:37 +00003267 thesuite = test_suite()
Tim Peters2a799bf2002-12-16 20:18:38 +00003268 lastrc = None
3269 while True:
3270 test_support.run_suite(thesuite)
3271 if 1: # change to 0, under a debug build, for some leak detection
3272 break
3273 gc.collect()
3274 if gc.garbage:
3275 raise SystemError("gc.garbage not empty after test run: %r" %
3276 gc.garbage)
3277 if hasattr(sys, 'gettotalrefcount'):
3278 thisrc = sys.gettotalrefcount()
3279 print >> sys.stderr, '*' * 10, 'total refs:', thisrc,
3280 if lastrc:
3281 print >> sys.stderr, 'delta:', thisrc - lastrc
3282 else:
3283 print >> sys.stderr
3284 lastrc = thisrc
3285
3286if __name__ == "__main__":
3287 test_main()