blob: f55a796ed093b331ea2b776b7b6f0601105b92ea [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
Guido van Rossumf1200f82007-03-07 15:16:29 +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
Collin Winterc2898c52007-04-25 17:29:52 +0000131class HarmlessMixedComparison:
Tim Peters07534a62003-02-07 22:50:28 +0000132 # 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
Collin Winterc2898c52007-04-25 17:29:52 +0000170class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
Tim Peters07534a62003-02-07 22:50:28 +0000171
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
Collin Winterc2898c52007-04-25 17:29:52 +0000517class TestDate(HarmlessMixedComparison, unittest.TestCase):
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 Brandl4ddfcd32006-09-30 11:17:34 +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
Guido van Rossum2054ee92007-03-06 15:50:01 +00001429 def test_negative_float_fromtimestamp(self):
Guido van Rossumf1200f82007-03-07 15:16:29 +00001430 # Windows doesn't accept negative timestamps
1431 if os.name == "nt":
1432 return
Guido van Rossum2054ee92007-03-06 15:50:01 +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):
Guido van Rossumf1200f82007-03-07 15:16:29 +00001438 # Windows doesn't accept negative timestamps
1439 if os.name == "nt":
1440 return
Guido van Rossum2054ee92007-03-06 15:50:01 +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
Collin Winterc2898c52007-04-25 17:29:52 +00001599class TestTime(HarmlessMixedComparison, unittest.TestCase):
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
Martin v. Löwis4c11a922007-02-08 09:13:36 +00001759 def test_1653736(self):
1760 # verify it doesn't accept extra keyword arguments
1761 t = self.theclass(second=1)
1762 self.assertRaises(TypeError, t.isoformat, foo=3)
1763
Tim Peters2a799bf2002-12-16 20:18:38 +00001764 def test_strftime(self):
1765 t = self.theclass(1, 2, 3, 4)
1766 self.assertEqual(t.strftime('%H %M %S'), "01 02 03")
1767 # A naive object replaces %z and %Z with empty strings.
1768 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1769
1770 def test_str(self):
1771 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
1772 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
1773 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
1774 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
1775 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
1776
1777 def test_repr(self):
1778 name = 'datetime.' + self.theclass.__name__
1779 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
1780 "%s(1, 2, 3, 4)" % name)
1781 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
1782 "%s(10, 2, 3, 4000)" % name)
1783 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
1784 "%s(0, 2, 3, 400000)" % name)
1785 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
1786 "%s(12, 2, 3)" % name)
1787 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
1788 "%s(23, 15)" % name)
1789
1790 def test_resolution_info(self):
1791 self.assert_(isinstance(self.theclass.min, self.theclass))
1792 self.assert_(isinstance(self.theclass.max, self.theclass))
1793 self.assert_(isinstance(self.theclass.resolution, timedelta))
1794 self.assert_(self.theclass.max > self.theclass.min)
1795
1796 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001797 args = 20, 59, 16, 64**2
1798 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001799 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001800 green = pickler.dumps(orig, proto)
1801 derived = unpickler.loads(green)
1802 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001803
Tim Peters604c0132004-06-07 23:04:33 +00001804 def test_pickling_subclass_time(self):
1805 args = 20, 59, 16, 64**2
1806 orig = SubclassTime(*args)
1807 for pickler, unpickler, proto in pickle_choices:
1808 green = pickler.dumps(orig, proto)
1809 derived = unpickler.loads(green)
1810 self.assertEqual(orig, derived)
1811
Tim Peters2a799bf2002-12-16 20:18:38 +00001812 def test_bool(self):
1813 cls = self.theclass
1814 self.failUnless(cls(1))
1815 self.failUnless(cls(0, 1))
1816 self.failUnless(cls(0, 0, 1))
1817 self.failUnless(cls(0, 0, 0, 1))
1818 self.failUnless(not cls(0))
1819 self.failUnless(not cls())
1820
Tim Peters12bf3392002-12-24 05:41:27 +00001821 def test_replace(self):
1822 cls = self.theclass
1823 args = [1, 2, 3, 4]
1824 base = cls(*args)
1825 self.assertEqual(base, base.replace())
1826
1827 i = 0
1828 for name, newval in (("hour", 5),
1829 ("minute", 6),
1830 ("second", 7),
1831 ("microsecond", 8)):
1832 newargs = args[:]
1833 newargs[i] = newval
1834 expected = cls(*newargs)
1835 got = base.replace(**{name: newval})
1836 self.assertEqual(expected, got)
1837 i += 1
1838
1839 # Out of bounds.
1840 base = cls(1)
1841 self.assertRaises(ValueError, base.replace, hour=24)
1842 self.assertRaises(ValueError, base.replace, minute=-1)
1843 self.assertRaises(ValueError, base.replace, second=100)
1844 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1845
Tim Petersa98924a2003-05-17 05:55:19 +00001846 def test_subclass_time(self):
1847
1848 class C(self.theclass):
1849 theAnswer = 42
1850
1851 def __new__(cls, *args, **kws):
1852 temp = kws.copy()
1853 extra = temp.pop('extra')
1854 result = self.theclass.__new__(cls, *args, **temp)
1855 result.extra = extra
1856 return result
1857
1858 def newmeth(self, start):
1859 return start + self.hour + self.second
1860
1861 args = 4, 5, 6
1862
1863 dt1 = self.theclass(*args)
1864 dt2 = C(*args, **{'extra': 7})
1865
1866 self.assertEqual(dt2.__class__, C)
1867 self.assertEqual(dt2.theAnswer, 42)
1868 self.assertEqual(dt2.extra, 7)
1869 self.assertEqual(dt1.isoformat(), dt2.isoformat())
1870 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
1871
Armin Rigof4afb212005-11-07 07:15:48 +00001872 def test_backdoor_resistance(self):
1873 # see TestDate.test_backdoor_resistance().
1874 base = '2:59.0'
1875 for hour_byte in ' ', '9', chr(24), '\xff':
1876 self.assertRaises(TypeError, self.theclass,
1877 hour_byte + base[1:])
1878
Tim Peters855fe882002-12-22 03:43:39 +00001879# A mixin for classes with a tzinfo= argument. Subclasses must define
1880# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001881# must be legit (which is true for time and datetime).
Collin Winterc2898c52007-04-25 17:29:52 +00001882class TZInfoBase:
Tim Peters2a799bf2002-12-16 20:18:38 +00001883
Tim Petersbad8ff02002-12-30 20:52:32 +00001884 def test_argument_passing(self):
1885 cls = self.theclass
Tim Peters0bf60bd2003-01-08 20:40:01 +00001886 # A datetime passes itself on, a time passes None.
Tim Petersbad8ff02002-12-30 20:52:32 +00001887 class introspective(tzinfo):
1888 def tzname(self, dt): return dt and "real" or "none"
Tim Peters397301e2003-01-02 21:28:08 +00001889 def utcoffset(self, dt):
1890 return timedelta(minutes = dt and 42 or -42)
Tim Petersbad8ff02002-12-30 20:52:32 +00001891 dst = utcoffset
1892
1893 obj = cls(1, 2, 3, tzinfo=introspective())
1894
Tim Peters0bf60bd2003-01-08 20:40:01 +00001895 expected = cls is time and "none" or "real"
Tim Petersbad8ff02002-12-30 20:52:32 +00001896 self.assertEqual(obj.tzname(), expected)
1897
Tim Peters0bf60bd2003-01-08 20:40:01 +00001898 expected = timedelta(minutes=(cls is time and -42 or 42))
Tim Petersbad8ff02002-12-30 20:52:32 +00001899 self.assertEqual(obj.utcoffset(), expected)
1900 self.assertEqual(obj.dst(), expected)
1901
Tim Peters855fe882002-12-22 03:43:39 +00001902 def test_bad_tzinfo_classes(self):
1903 cls = self.theclass
1904 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
Tim Peters2a799bf2002-12-16 20:18:38 +00001905
Tim Peters855fe882002-12-22 03:43:39 +00001906 class NiceTry(object):
1907 def __init__(self): pass
1908 def utcoffset(self, dt): pass
1909 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
1910
1911 class BetterTry(tzinfo):
1912 def __init__(self): pass
1913 def utcoffset(self, dt): pass
1914 b = BetterTry()
1915 t = cls(1, 1, 1, tzinfo=b)
1916 self.failUnless(t.tzinfo is b)
1917
1918 def test_utc_offset_out_of_bounds(self):
1919 class Edgy(tzinfo):
1920 def __init__(self, offset):
Tim Peters397301e2003-01-02 21:28:08 +00001921 self.offset = timedelta(minutes=offset)
Tim Peters855fe882002-12-22 03:43:39 +00001922 def utcoffset(self, dt):
1923 return self.offset
1924
1925 cls = self.theclass
1926 for offset, legit in ((-1440, False),
1927 (-1439, True),
1928 (1439, True),
1929 (1440, False)):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001930 if cls is time:
Tim Peters855fe882002-12-22 03:43:39 +00001931 t = cls(1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001932 elif cls is datetime:
Tim Peters855fe882002-12-22 03:43:39 +00001933 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001934 else:
1935 assert 0, "impossible"
Tim Peters855fe882002-12-22 03:43:39 +00001936 if legit:
1937 aofs = abs(offset)
1938 h, m = divmod(aofs, 60)
1939 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001940 if isinstance(t, datetime):
Tim Peters855fe882002-12-22 03:43:39 +00001941 t = t.timetz()
1942 self.assertEqual(str(t), "01:02:03" + tag)
1943 else:
1944 self.assertRaises(ValueError, str, t)
1945
1946 def test_tzinfo_classes(self):
1947 cls = self.theclass
1948 class C1(tzinfo):
1949 def utcoffset(self, dt): return None
1950 def dst(self, dt): return None
1951 def tzname(self, dt): return None
1952 for t in (cls(1, 1, 1),
1953 cls(1, 1, 1, tzinfo=None),
1954 cls(1, 1, 1, tzinfo=C1())):
1955 self.failUnless(t.utcoffset() is None)
1956 self.failUnless(t.dst() is None)
1957 self.failUnless(t.tzname() is None)
1958
Tim Peters855fe882002-12-22 03:43:39 +00001959 class C3(tzinfo):
1960 def utcoffset(self, dt): return timedelta(minutes=-1439)
1961 def dst(self, dt): return timedelta(minutes=1439)
1962 def tzname(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001963 t = cls(1, 1, 1, tzinfo=C3())
1964 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
1965 self.assertEqual(t.dst(), timedelta(minutes=1439))
1966 self.assertEqual(t.tzname(), "aname")
Tim Peters855fe882002-12-22 03:43:39 +00001967
1968 # Wrong types.
1969 class C4(tzinfo):
1970 def utcoffset(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001971 def dst(self, dt): return 7
Tim Peters855fe882002-12-22 03:43:39 +00001972 def tzname(self, dt): return 0
1973 t = cls(1, 1, 1, tzinfo=C4())
1974 self.assertRaises(TypeError, t.utcoffset)
1975 self.assertRaises(TypeError, t.dst)
1976 self.assertRaises(TypeError, t.tzname)
1977
1978 # Offset out of range.
Tim Peters855fe882002-12-22 03:43:39 +00001979 class C6(tzinfo):
1980 def utcoffset(self, dt): return timedelta(hours=-24)
1981 def dst(self, dt): return timedelta(hours=24)
Tim Peters397301e2003-01-02 21:28:08 +00001982 t = cls(1, 1, 1, tzinfo=C6())
1983 self.assertRaises(ValueError, t.utcoffset)
1984 self.assertRaises(ValueError, t.dst)
Tim Peters855fe882002-12-22 03:43:39 +00001985
1986 # Not a whole number of minutes.
1987 class C7(tzinfo):
1988 def utcoffset(self, dt): return timedelta(seconds=61)
1989 def dst(self, dt): return timedelta(microseconds=-81)
1990 t = cls(1, 1, 1, tzinfo=C7())
1991 self.assertRaises(ValueError, t.utcoffset)
1992 self.assertRaises(ValueError, t.dst)
1993
Tim Peters4c0db782002-12-26 05:01:19 +00001994 def test_aware_compare(self):
1995 cls = self.theclass
1996
Tim Peters60c76e42002-12-27 00:41:11 +00001997 # Ensure that utcoffset() gets ignored if the comparands have
1998 # the same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00001999 class OperandDependentOffset(tzinfo):
2000 def utcoffset(self, t):
2001 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00002002 # d0 and d1 equal after adjustment
2003 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00002004 else:
Tim Peters397301e2003-01-02 21:28:08 +00002005 # d2 off in the weeds
2006 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00002007
2008 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
2009 d0 = base.replace(minute=3)
2010 d1 = base.replace(minute=9)
2011 d2 = base.replace(minute=11)
2012 for x in d0, d1, d2:
2013 for y in d0, d1, d2:
2014 got = cmp(x, y)
Tim Peters60c76e42002-12-27 00:41:11 +00002015 expected = cmp(x.minute, y.minute)
2016 self.assertEqual(got, expected)
2017
2018 # However, if they're different members, uctoffset is not ignored.
Tim Peters0bf60bd2003-01-08 20:40:01 +00002019 # Note that a time can't actually have an operand-depedent offset,
2020 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
2021 # so skip this test for time.
2022 if cls is not time:
Tim Petersbad8ff02002-12-30 20:52:32 +00002023 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2024 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2025 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2026 for x in d0, d1, d2:
2027 for y in d0, d1, d2:
2028 got = cmp(x, y)
2029 if (x is d0 or x is d1) and (y is d0 or y is d1):
2030 expected = 0
2031 elif x is y is d2:
2032 expected = 0
2033 elif x is d2:
2034 expected = -1
2035 else:
2036 assert y is d2
2037 expected = 1
2038 self.assertEqual(got, expected)
Tim Peters4c0db782002-12-26 05:01:19 +00002039
Tim Peters855fe882002-12-22 03:43:39 +00002040
Tim Peters0bf60bd2003-01-08 20:40:01 +00002041# Testing time objects with a non-None tzinfo.
Collin Winterc2898c52007-04-25 17:29:52 +00002042class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002043 theclass = time
Tim Peters2a799bf2002-12-16 20:18:38 +00002044
2045 def test_empty(self):
2046 t = self.theclass()
2047 self.assertEqual(t.hour, 0)
2048 self.assertEqual(t.minute, 0)
2049 self.assertEqual(t.second, 0)
2050 self.assertEqual(t.microsecond, 0)
2051 self.failUnless(t.tzinfo is None)
2052
Tim Peters2a799bf2002-12-16 20:18:38 +00002053 def test_zones(self):
2054 est = FixedOffset(-300, "EST", 1)
2055 utc = FixedOffset(0, "UTC", -2)
2056 met = FixedOffset(60, "MET", 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002057 t1 = time( 7, 47, tzinfo=est)
2058 t2 = time(12, 47, tzinfo=utc)
2059 t3 = time(13, 47, tzinfo=met)
2060 t4 = time(microsecond=40)
2061 t5 = time(microsecond=40, tzinfo=utc)
Tim Peters2a799bf2002-12-16 20:18:38 +00002062
2063 self.assertEqual(t1.tzinfo, est)
2064 self.assertEqual(t2.tzinfo, utc)
2065 self.assertEqual(t3.tzinfo, met)
2066 self.failUnless(t4.tzinfo is None)
2067 self.assertEqual(t5.tzinfo, utc)
2068
Tim Peters855fe882002-12-22 03:43:39 +00002069 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2070 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2071 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002072 self.failUnless(t4.utcoffset() is None)
2073 self.assertRaises(TypeError, t1.utcoffset, "no args")
2074
2075 self.assertEqual(t1.tzname(), "EST")
2076 self.assertEqual(t2.tzname(), "UTC")
2077 self.assertEqual(t3.tzname(), "MET")
2078 self.failUnless(t4.tzname() is None)
2079 self.assertRaises(TypeError, t1.tzname, "no args")
2080
Tim Peters855fe882002-12-22 03:43:39 +00002081 self.assertEqual(t1.dst(), timedelta(minutes=1))
2082 self.assertEqual(t2.dst(), timedelta(minutes=-2))
2083 self.assertEqual(t3.dst(), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +00002084 self.failUnless(t4.dst() is None)
2085 self.assertRaises(TypeError, t1.dst, "no args")
2086
2087 self.assertEqual(hash(t1), hash(t2))
2088 self.assertEqual(hash(t1), hash(t3))
2089 self.assertEqual(hash(t2), hash(t3))
2090
2091 self.assertEqual(t1, t2)
2092 self.assertEqual(t1, t3)
2093 self.assertEqual(t2, t3)
2094 self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
2095 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
2096 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
2097
2098 self.assertEqual(str(t1), "07:47:00-05:00")
2099 self.assertEqual(str(t2), "12:47:00+00:00")
2100 self.assertEqual(str(t3), "13:47:00+01:00")
2101 self.assertEqual(str(t4), "00:00:00.000040")
2102 self.assertEqual(str(t5), "00:00:00.000040+00:00")
2103
2104 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
2105 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
2106 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
2107 self.assertEqual(t4.isoformat(), "00:00:00.000040")
2108 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
2109
Tim Peters0bf60bd2003-01-08 20:40:01 +00002110 d = 'datetime.time'
Tim Peters2a799bf2002-12-16 20:18:38 +00002111 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
2112 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
2113 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
2114 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
2115 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2116
2117 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2118 "07:47:00 %Z=EST %z=-0500")
2119 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2120 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2121
2122 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002123 t1 = time(23, 59, tzinfo=yuck)
Tim Peters2a799bf2002-12-16 20:18:38 +00002124 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2125 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2126
Tim Petersb92bb712002-12-21 17:44:07 +00002127 # Check that an invalid tzname result raises an exception.
2128 class Badtzname(tzinfo):
2129 def tzname(self, dt): return 42
Tim Peters0bf60bd2003-01-08 20:40:01 +00002130 t = time(2, 3, 4, tzinfo=Badtzname())
Tim Petersb92bb712002-12-21 17:44:07 +00002131 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2132 self.assertRaises(TypeError, t.strftime, "%Z")
Tim Peters2a799bf2002-12-16 20:18:38 +00002133
2134 def test_hash_edge_cases(self):
2135 # Offsets that overflow a basic time.
2136 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2137 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2138 self.assertEqual(hash(t1), hash(t2))
2139
2140 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2141 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2142 self.assertEqual(hash(t1), hash(t2))
2143
Tim Peters2a799bf2002-12-16 20:18:38 +00002144 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002145 # Try one without a tzinfo.
2146 args = 20, 59, 16, 64**2
2147 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002148 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002149 green = pickler.dumps(orig, proto)
2150 derived = unpickler.loads(green)
2151 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002152
2153 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002154 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002155 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002156 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002157 green = pickler.dumps(orig, proto)
2158 derived = unpickler.loads(green)
2159 self.assertEqual(orig, derived)
2160 self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
2161 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2162 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002163
2164 def test_more_bool(self):
2165 # Test cases with non-None tzinfo.
2166 cls = self.theclass
2167
2168 t = cls(0, tzinfo=FixedOffset(-300, ""))
2169 self.failUnless(t)
2170
2171 t = cls(5, tzinfo=FixedOffset(-300, ""))
2172 self.failUnless(t)
2173
2174 t = cls(5, tzinfo=FixedOffset(300, ""))
2175 self.failUnless(not t)
2176
2177 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2178 self.failUnless(not t)
2179
2180 # Mostly ensuring this doesn't overflow internally.
2181 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
2182 self.failUnless(t)
2183
2184 # But this should yield a value error -- the utcoffset is bogus.
2185 t = cls(0, tzinfo=FixedOffset(24*60, ""))
2186 self.assertRaises(ValueError, lambda: bool(t))
2187
2188 # Likewise.
2189 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
2190 self.assertRaises(ValueError, lambda: bool(t))
2191
Tim Peters12bf3392002-12-24 05:41:27 +00002192 def test_replace(self):
2193 cls = self.theclass
2194 z100 = FixedOffset(100, "+100")
2195 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2196 args = [1, 2, 3, 4, z100]
2197 base = cls(*args)
2198 self.assertEqual(base, base.replace())
2199
2200 i = 0
2201 for name, newval in (("hour", 5),
2202 ("minute", 6),
2203 ("second", 7),
2204 ("microsecond", 8),
2205 ("tzinfo", zm200)):
2206 newargs = args[:]
2207 newargs[i] = newval
2208 expected = cls(*newargs)
2209 got = base.replace(**{name: newval})
2210 self.assertEqual(expected, got)
2211 i += 1
2212
2213 # Ensure we can get rid of a tzinfo.
2214 self.assertEqual(base.tzname(), "+100")
2215 base2 = base.replace(tzinfo=None)
2216 self.failUnless(base2.tzinfo is None)
2217 self.failUnless(base2.tzname() is None)
2218
2219 # Ensure we can add one.
2220 base3 = base2.replace(tzinfo=z100)
2221 self.assertEqual(base, base3)
2222 self.failUnless(base.tzinfo is base3.tzinfo)
2223
2224 # Out of bounds.
2225 base = cls(1)
2226 self.assertRaises(ValueError, base.replace, hour=24)
2227 self.assertRaises(ValueError, base.replace, minute=-1)
2228 self.assertRaises(ValueError, base.replace, second=100)
2229 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2230
Tim Peters60c76e42002-12-27 00:41:11 +00002231 def test_mixed_compare(self):
2232 t1 = time(1, 2, 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002233 t2 = time(1, 2, 3)
Tim Peters60c76e42002-12-27 00:41:11 +00002234 self.assertEqual(t1, t2)
2235 t2 = t2.replace(tzinfo=None)
2236 self.assertEqual(t1, t2)
2237 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2238 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002239 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2240 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002241
Tim Peters0bf60bd2003-01-08 20:40:01 +00002242 # In time w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002243 class Varies(tzinfo):
2244 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002245 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002246 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002247 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002248 return self.offset
2249
2250 v = Varies()
2251 t1 = t2.replace(tzinfo=v)
2252 t2 = t2.replace(tzinfo=v)
2253 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2254 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2255 self.assertEqual(t1, t2)
2256
2257 # But if they're not identical, it isn't ignored.
2258 t2 = t2.replace(tzinfo=Varies())
2259 self.failUnless(t1 < t2) # t1's offset counter still going up
2260
Tim Petersa98924a2003-05-17 05:55:19 +00002261 def test_subclass_timetz(self):
2262
2263 class C(self.theclass):
2264 theAnswer = 42
2265
2266 def __new__(cls, *args, **kws):
2267 temp = kws.copy()
2268 extra = temp.pop('extra')
2269 result = self.theclass.__new__(cls, *args, **temp)
2270 result.extra = extra
2271 return result
2272
2273 def newmeth(self, start):
2274 return start + self.hour + self.second
2275
2276 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2277
2278 dt1 = self.theclass(*args)
2279 dt2 = C(*args, **{'extra': 7})
2280
2281 self.assertEqual(dt2.__class__, C)
2282 self.assertEqual(dt2.theAnswer, 42)
2283 self.assertEqual(dt2.extra, 7)
2284 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2285 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2286
Tim Peters4c0db782002-12-26 05:01:19 +00002287
Tim Peters0bf60bd2003-01-08 20:40:01 +00002288# Testing datetime objects with a non-None tzinfo.
2289
Collin Winterc2898c52007-04-25 17:29:52 +00002290class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002291 theclass = datetime
Tim Peters2a799bf2002-12-16 20:18:38 +00002292
2293 def test_trivial(self):
2294 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2295 self.assertEqual(dt.year, 1)
2296 self.assertEqual(dt.month, 2)
2297 self.assertEqual(dt.day, 3)
2298 self.assertEqual(dt.hour, 4)
2299 self.assertEqual(dt.minute, 5)
2300 self.assertEqual(dt.second, 6)
2301 self.assertEqual(dt.microsecond, 7)
2302 self.assertEqual(dt.tzinfo, None)
2303
2304 def test_even_more_compare(self):
2305 # The test_compare() and test_more_compare() inherited from TestDate
2306 # and TestDateTime covered non-tzinfo cases.
2307
2308 # Smallest possible after UTC adjustment.
2309 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2310 # Largest possible after UTC adjustment.
2311 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2312 tzinfo=FixedOffset(-1439, ""))
2313
2314 # Make sure those compare correctly, and w/o overflow.
2315 self.failUnless(t1 < t2)
2316 self.failUnless(t1 != t2)
2317 self.failUnless(t2 > t1)
2318
2319 self.failUnless(t1 == t1)
2320 self.failUnless(t2 == t2)
2321
2322 # Equal afer adjustment.
2323 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2324 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2325 self.assertEqual(t1, t2)
2326
2327 # Change t1 not to subtract a minute, and t1 should be larger.
2328 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2329 self.failUnless(t1 > t2)
2330
2331 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2332 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2333 self.failUnless(t1 < t2)
2334
2335 # Back to the original t1, but make seconds resolve it.
2336 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2337 second=1)
2338 self.failUnless(t1 > t2)
2339
2340 # Likewise, but make microseconds resolve it.
2341 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2342 microsecond=1)
2343 self.failUnless(t1 > t2)
2344
2345 # Make t2 naive and it should fail.
2346 t2 = self.theclass.min
2347 self.assertRaises(TypeError, lambda: t1 == t2)
2348 self.assertEqual(t2, t2)
2349
2350 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2351 class Naive(tzinfo):
2352 def utcoffset(self, dt): return None
2353 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
2354 self.assertRaises(TypeError, lambda: t1 == t2)
2355 self.assertEqual(t2, t2)
2356
2357 # OTOH, it's OK to compare two of these mixing the two ways of being
2358 # naive.
2359 t1 = self.theclass(5, 6, 7)
2360 self.assertEqual(t1, t2)
2361
2362 # Try a bogus uctoffset.
2363 class Bogus(tzinfo):
Tim Peters397301e2003-01-02 21:28:08 +00002364 def utcoffset(self, dt):
2365 return timedelta(minutes=1440) # out of bounds
Tim Peters2a799bf2002-12-16 20:18:38 +00002366 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2367 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
Tim Peters60c76e42002-12-27 00:41:11 +00002368 self.assertRaises(ValueError, lambda: t1 == t2)
Tim Peters2a799bf2002-12-16 20:18:38 +00002369
Tim Peters2a799bf2002-12-16 20:18:38 +00002370 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002371 # Try one without a tzinfo.
2372 args = 6, 7, 23, 20, 59, 1, 64**2
2373 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002374 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002375 green = pickler.dumps(orig, proto)
2376 derived = unpickler.loads(green)
2377 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002378
2379 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002380 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002381 orig = self.theclass(*args, **{'tzinfo': tinfo})
Tim Petersa9bc1682003-01-11 03:39:11 +00002382 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
Guido van Rossum177e41a2003-01-30 22:06:23 +00002383 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002384 green = pickler.dumps(orig, proto)
2385 derived = unpickler.loads(green)
2386 self.assertEqual(orig, derived)
2387 self.failUnless(isinstance(derived.tzinfo,
2388 PicklableFixedOffset))
2389 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2390 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002391
2392 def test_extreme_hashes(self):
2393 # If an attempt is made to hash these via subtracting the offset
2394 # then hashing a datetime object, OverflowError results. The
2395 # Python implementation used to blow up here.
2396 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2397 hash(t)
2398 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2399 tzinfo=FixedOffset(-1439, ""))
2400 hash(t)
2401
2402 # OTOH, an OOB offset should blow up.
2403 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2404 self.assertRaises(ValueError, hash, t)
2405
2406 def test_zones(self):
2407 est = FixedOffset(-300, "EST")
2408 utc = FixedOffset(0, "UTC")
2409 met = FixedOffset(60, "MET")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002410 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2411 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2412 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
Tim Peters2a799bf2002-12-16 20:18:38 +00002413 self.assertEqual(t1.tzinfo, est)
2414 self.assertEqual(t2.tzinfo, utc)
2415 self.assertEqual(t3.tzinfo, met)
Tim Peters855fe882002-12-22 03:43:39 +00002416 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2417 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2418 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002419 self.assertEqual(t1.tzname(), "EST")
2420 self.assertEqual(t2.tzname(), "UTC")
2421 self.assertEqual(t3.tzname(), "MET")
2422 self.assertEqual(hash(t1), hash(t2))
2423 self.assertEqual(hash(t1), hash(t3))
2424 self.assertEqual(hash(t2), hash(t3))
2425 self.assertEqual(t1, t2)
2426 self.assertEqual(t1, t3)
2427 self.assertEqual(t2, t3)
2428 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2429 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2430 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002431 d = 'datetime.datetime(2002, 3, 19, '
Tim Peters2a799bf2002-12-16 20:18:38 +00002432 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2433 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2434 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2435
2436 def test_combine(self):
2437 met = FixedOffset(60, "MET")
2438 d = date(2002, 3, 4)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002439 tz = time(18, 45, 3, 1234, tzinfo=met)
2440 dt = datetime.combine(d, tz)
2441 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
Tim Peters2a799bf2002-12-16 20:18:38 +00002442 tzinfo=met))
2443
2444 def test_extract(self):
2445 met = FixedOffset(60, "MET")
2446 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2447 self.assertEqual(dt.date(), date(2002, 3, 4))
2448 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
Tim Peters0bf60bd2003-01-08 20:40:01 +00002449 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
Tim Peters2a799bf2002-12-16 20:18:38 +00002450
2451 def test_tz_aware_arithmetic(self):
2452 import random
2453
2454 now = self.theclass.now()
2455 tz55 = FixedOffset(-330, "west 5:30")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002456 timeaware = now.time().replace(tzinfo=tz55)
Tim Peters2a799bf2002-12-16 20:18:38 +00002457 nowaware = self.theclass.combine(now.date(), timeaware)
2458 self.failUnless(nowaware.tzinfo is tz55)
2459 self.assertEqual(nowaware.timetz(), timeaware)
2460
2461 # Can't mix aware and non-aware.
2462 self.assertRaises(TypeError, lambda: now - nowaware)
2463 self.assertRaises(TypeError, lambda: nowaware - now)
2464
Tim Peters0bf60bd2003-01-08 20:40:01 +00002465 # And adding datetime's doesn't make sense, aware or not.
Tim Peters2a799bf2002-12-16 20:18:38 +00002466 self.assertRaises(TypeError, lambda: now + nowaware)
2467 self.assertRaises(TypeError, lambda: nowaware + now)
2468 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2469
2470 # Subtracting should yield 0.
2471 self.assertEqual(now - now, timedelta(0))
2472 self.assertEqual(nowaware - nowaware, timedelta(0))
2473
2474 # Adding a delta should preserve tzinfo.
2475 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2476 nowawareplus = nowaware + delta
2477 self.failUnless(nowaware.tzinfo is tz55)
2478 nowawareplus2 = delta + nowaware
2479 self.failUnless(nowawareplus2.tzinfo is tz55)
2480 self.assertEqual(nowawareplus, nowawareplus2)
2481
2482 # that - delta should be what we started with, and that - what we
2483 # started with should be delta.
2484 diff = nowawareplus - delta
2485 self.failUnless(diff.tzinfo is tz55)
2486 self.assertEqual(nowaware, diff)
2487 self.assertRaises(TypeError, lambda: delta - nowawareplus)
2488 self.assertEqual(nowawareplus - nowaware, delta)
2489
2490 # Make up a random timezone.
2491 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
Tim Peters4c0db782002-12-26 05:01:19 +00002492 # Attach it to nowawareplus.
2493 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Tim Peters2a799bf2002-12-16 20:18:38 +00002494 self.failUnless(nowawareplus.tzinfo is tzr)
2495 # Make sure the difference takes the timezone adjustments into account.
2496 got = nowaware - nowawareplus
2497 # Expected: (nowaware base - nowaware offset) -
2498 # (nowawareplus base - nowawareplus offset) =
2499 # (nowaware base - nowawareplus base) +
2500 # (nowawareplus offset - nowaware offset) =
2501 # -delta + nowawareplus offset - nowaware offset
Tim Peters855fe882002-12-22 03:43:39 +00002502 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
Tim Peters2a799bf2002-12-16 20:18:38 +00002503 self.assertEqual(got, expected)
2504
2505 # Try max possible difference.
2506 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2507 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2508 tzinfo=FixedOffset(-1439, "max"))
2509 maxdiff = max - min
2510 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2511 timedelta(minutes=2*1439))
2512
2513 def test_tzinfo_now(self):
2514 meth = self.theclass.now
2515 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2516 base = meth()
2517 # Try with and without naming the keyword.
2518 off42 = FixedOffset(42, "42")
2519 another = meth(off42)
Tim Peters10cadce2003-01-23 19:58:02 +00002520 again = meth(tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002521 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002522 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002523 # Bad argument with and w/o naming the keyword.
2524 self.assertRaises(TypeError, meth, 16)
2525 self.assertRaises(TypeError, meth, tzinfo=16)
2526 # Bad keyword name.
2527 self.assertRaises(TypeError, meth, tinfo=off42)
2528 # Too many args.
2529 self.assertRaises(TypeError, meth, off42, off42)
2530
Tim Peters10cadce2003-01-23 19:58:02 +00002531 # We don't know which time zone we're in, and don't have a tzinfo
2532 # class to represent it, so seeing whether a tz argument actually
2533 # does a conversion is tricky.
2534 weirdtz = FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0)
2535 utc = FixedOffset(0, "utc", 0)
2536 for dummy in range(3):
2537 now = datetime.now(weirdtz)
2538 self.failUnless(now.tzinfo is weirdtz)
2539 utcnow = datetime.utcnow().replace(tzinfo=utc)
2540 now2 = utcnow.astimezone(weirdtz)
2541 if abs(now - now2) < timedelta(seconds=30):
2542 break
2543 # Else the code is broken, or more than 30 seconds passed between
2544 # calls; assuming the latter, just try again.
2545 else:
2546 # Three strikes and we're out.
2547 self.fail("utcnow(), now(tz), or astimezone() may be broken")
2548
Tim Peters2a799bf2002-12-16 20:18:38 +00002549 def test_tzinfo_fromtimestamp(self):
2550 import time
2551 meth = self.theclass.fromtimestamp
2552 ts = time.time()
2553 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2554 base = meth(ts)
2555 # Try with and without naming the keyword.
2556 off42 = FixedOffset(42, "42")
2557 another = meth(ts, off42)
Tim Peters2a44a8d2003-01-23 20:53:10 +00002558 again = meth(ts, tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002559 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002560 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002561 # Bad argument with and w/o naming the keyword.
2562 self.assertRaises(TypeError, meth, ts, 16)
2563 self.assertRaises(TypeError, meth, ts, tzinfo=16)
2564 # Bad keyword name.
2565 self.assertRaises(TypeError, meth, ts, tinfo=off42)
2566 # Too many args.
2567 self.assertRaises(TypeError, meth, ts, off42, off42)
2568 # Too few args.
2569 self.assertRaises(TypeError, meth)
2570
Tim Peters2a44a8d2003-01-23 20:53:10 +00002571 # Try to make sure tz= actually does some conversion.
Tim Peters84407612003-02-06 16:42:14 +00002572 timestamp = 1000000000
2573 utcdatetime = datetime.utcfromtimestamp(timestamp)
2574 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
2575 # But on some flavor of Mac, it's nowhere near that. So we can't have
2576 # any idea here what time that actually is, we can only test that
2577 # relative changes match.
2578 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
2579 tz = FixedOffset(utcoffset, "tz", 0)
2580 expected = utcdatetime + utcoffset
2581 got = datetime.fromtimestamp(timestamp, tz)
2582 self.assertEqual(expected, got.replace(tzinfo=None))
Tim Peters2a44a8d2003-01-23 20:53:10 +00002583
Tim Peters2a799bf2002-12-16 20:18:38 +00002584 def test_tzinfo_utcnow(self):
2585 meth = self.theclass.utcnow
2586 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2587 base = meth()
2588 # Try with and without naming the keyword; for whatever reason,
2589 # utcnow() doesn't accept a tzinfo argument.
2590 off42 = FixedOffset(42, "42")
2591 self.assertRaises(TypeError, meth, off42)
2592 self.assertRaises(TypeError, meth, tzinfo=off42)
2593
2594 def test_tzinfo_utcfromtimestamp(self):
2595 import time
2596 meth = self.theclass.utcfromtimestamp
2597 ts = time.time()
2598 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2599 base = meth(ts)
2600 # Try with and without naming the keyword; for whatever reason,
2601 # utcfromtimestamp() doesn't accept a tzinfo argument.
2602 off42 = FixedOffset(42, "42")
2603 self.assertRaises(TypeError, meth, ts, off42)
2604 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
2605
2606 def test_tzinfo_timetuple(self):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002607 # TestDateTime tested most of this. datetime adds a twist to the
Tim Peters2a799bf2002-12-16 20:18:38 +00002608 # DST flag.
2609 class DST(tzinfo):
2610 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002611 if isinstance(dstvalue, int):
2612 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002613 self.dstvalue = dstvalue
2614 def dst(self, dt):
2615 return self.dstvalue
2616
2617 cls = self.theclass
2618 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
2619 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
2620 t = d.timetuple()
2621 self.assertEqual(1, t.tm_year)
2622 self.assertEqual(1, t.tm_mon)
2623 self.assertEqual(1, t.tm_mday)
2624 self.assertEqual(10, t.tm_hour)
2625 self.assertEqual(20, t.tm_min)
2626 self.assertEqual(30, t.tm_sec)
2627 self.assertEqual(0, t.tm_wday)
2628 self.assertEqual(1, t.tm_yday)
2629 self.assertEqual(flag, t.tm_isdst)
2630
2631 # dst() returns wrong type.
2632 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
2633
2634 # dst() at the edge.
2635 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
2636 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
2637
2638 # dst() out of range.
2639 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
2640 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
2641
2642 def test_utctimetuple(self):
2643 class DST(tzinfo):
2644 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002645 if isinstance(dstvalue, int):
2646 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002647 self.dstvalue = dstvalue
2648 def dst(self, dt):
2649 return self.dstvalue
2650
2651 cls = self.theclass
2652 # This can't work: DST didn't implement utcoffset.
2653 self.assertRaises(NotImplementedError,
2654 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
2655
2656 class UOFS(DST):
2657 def __init__(self, uofs, dofs=None):
2658 DST.__init__(self, dofs)
Tim Peters397301e2003-01-02 21:28:08 +00002659 self.uofs = timedelta(minutes=uofs)
Tim Peters2a799bf2002-12-16 20:18:38 +00002660 def utcoffset(self, dt):
2661 return self.uofs
2662
2663 # Ensure tm_isdst is 0 regardless of what dst() says: DST is never
2664 # in effect for a UTC time.
2665 for dstvalue in -33, 33, 0, None:
2666 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
2667 t = d.utctimetuple()
2668 self.assertEqual(d.year, t.tm_year)
2669 self.assertEqual(d.month, t.tm_mon)
2670 self.assertEqual(d.day, t.tm_mday)
2671 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
2672 self.assertEqual(13, t.tm_min)
2673 self.assertEqual(d.second, t.tm_sec)
2674 self.assertEqual(d.weekday(), t.tm_wday)
2675 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
2676 t.tm_yday)
2677 self.assertEqual(0, t.tm_isdst)
2678
2679 # At the edges, UTC adjustment can normalize into years out-of-range
2680 # for a datetime object. Ensure that a correct timetuple is
2681 # created anyway.
2682 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
2683 # That goes back 1 minute less than a full day.
2684 t = tiny.utctimetuple()
2685 self.assertEqual(t.tm_year, MINYEAR-1)
2686 self.assertEqual(t.tm_mon, 12)
2687 self.assertEqual(t.tm_mday, 31)
2688 self.assertEqual(t.tm_hour, 0)
2689 self.assertEqual(t.tm_min, 1)
2690 self.assertEqual(t.tm_sec, 37)
2691 self.assertEqual(t.tm_yday, 366) # "year 0" is a leap year
2692 self.assertEqual(t.tm_isdst, 0)
2693
2694 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
2695 # That goes forward 1 minute less than a full day.
2696 t = huge.utctimetuple()
2697 self.assertEqual(t.tm_year, MAXYEAR+1)
2698 self.assertEqual(t.tm_mon, 1)
2699 self.assertEqual(t.tm_mday, 1)
2700 self.assertEqual(t.tm_hour, 23)
2701 self.assertEqual(t.tm_min, 58)
2702 self.assertEqual(t.tm_sec, 37)
2703 self.assertEqual(t.tm_yday, 1)
2704 self.assertEqual(t.tm_isdst, 0)
2705
2706 def test_tzinfo_isoformat(self):
2707 zero = FixedOffset(0, "+00:00")
2708 plus = FixedOffset(220, "+03:40")
2709 minus = FixedOffset(-231, "-03:51")
2710 unknown = FixedOffset(None, "")
2711
2712 cls = self.theclass
2713 datestr = '0001-02-03'
2714 for ofs in None, zero, plus, minus, unknown:
Tim Peters6578dc92002-12-24 18:31:27 +00002715 for us in 0, 987001:
Tim Peters2a799bf2002-12-16 20:18:38 +00002716 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
2717 timestr = '04:05:59' + (us and '.987001' or '')
2718 ofsstr = ofs is not None and d.tzname() or ''
2719 tailstr = timestr + ofsstr
2720 iso = d.isoformat()
2721 self.assertEqual(iso, datestr + 'T' + tailstr)
2722 self.assertEqual(iso, d.isoformat('T'))
2723 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
2724 self.assertEqual(str(d), datestr + ' ' + tailstr)
2725
Tim Peters12bf3392002-12-24 05:41:27 +00002726 def test_replace(self):
2727 cls = self.theclass
2728 z100 = FixedOffset(100, "+100")
2729 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2730 args = [1, 2, 3, 4, 5, 6, 7, z100]
2731 base = cls(*args)
2732 self.assertEqual(base, base.replace())
2733
2734 i = 0
2735 for name, newval in (("year", 2),
2736 ("month", 3),
2737 ("day", 4),
2738 ("hour", 5),
2739 ("minute", 6),
2740 ("second", 7),
2741 ("microsecond", 8),
2742 ("tzinfo", zm200)):
2743 newargs = args[:]
2744 newargs[i] = newval
2745 expected = cls(*newargs)
2746 got = base.replace(**{name: newval})
2747 self.assertEqual(expected, got)
2748 i += 1
2749
2750 # Ensure we can get rid of a tzinfo.
2751 self.assertEqual(base.tzname(), "+100")
2752 base2 = base.replace(tzinfo=None)
2753 self.failUnless(base2.tzinfo is None)
2754 self.failUnless(base2.tzname() is None)
2755
2756 # Ensure we can add one.
2757 base3 = base2.replace(tzinfo=z100)
2758 self.assertEqual(base, base3)
2759 self.failUnless(base.tzinfo is base3.tzinfo)
2760
2761 # Out of bounds.
2762 base = cls(2000, 2, 29)
2763 self.assertRaises(ValueError, base.replace, year=2001)
Tim Peters2a799bf2002-12-16 20:18:38 +00002764
Tim Peters80475bb2002-12-25 07:40:55 +00002765 def test_more_astimezone(self):
2766 # The inherited test_astimezone covered some trivial and error cases.
2767 fnone = FixedOffset(None, "None")
2768 f44m = FixedOffset(44, "44")
2769 fm5h = FixedOffset(-timedelta(hours=5), "m300")
2770
Tim Peters10cadce2003-01-23 19:58:02 +00002771 dt = self.theclass.now(tz=f44m)
Tim Peters80475bb2002-12-25 07:40:55 +00002772 self.failUnless(dt.tzinfo is f44m)
Tim Peters52dcce22003-01-23 16:36:11 +00002773 # Replacing with degenerate tzinfo raises an exception.
2774 self.assertRaises(ValueError, dt.astimezone, fnone)
2775 # Ditto with None tz.
2776 self.assertRaises(TypeError, dt.astimezone, None)
2777 # Replacing with same tzinfo makes no change.
Tim Peters80475bb2002-12-25 07:40:55 +00002778 x = dt.astimezone(dt.tzinfo)
2779 self.failUnless(x.tzinfo is f44m)
2780 self.assertEqual(x.date(), dt.date())
2781 self.assertEqual(x.time(), dt.time())
2782
2783 # Replacing with different tzinfo does adjust.
2784 got = dt.astimezone(fm5h)
2785 self.failUnless(got.tzinfo is fm5h)
2786 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
2787 expected = dt - dt.utcoffset() # in effect, convert to UTC
2788 expected += fm5h.utcoffset(dt) # and from there to local time
2789 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
2790 self.assertEqual(got.date(), expected.date())
2791 self.assertEqual(got.time(), expected.time())
2792 self.assertEqual(got.timetz(), expected.timetz())
2793 self.failUnless(got.tzinfo is expected.tzinfo)
2794 self.assertEqual(got, expected)
2795
Tim Peters4c0db782002-12-26 05:01:19 +00002796 def test_aware_subtract(self):
2797 cls = self.theclass
2798
Tim Peters60c76e42002-12-27 00:41:11 +00002799 # Ensure that utcoffset() is ignored when the operands have the
2800 # same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00002801 class OperandDependentOffset(tzinfo):
2802 def utcoffset(self, t):
2803 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00002804 # d0 and d1 equal after adjustment
2805 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00002806 else:
Tim Peters397301e2003-01-02 21:28:08 +00002807 # d2 off in the weeds
2808 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00002809
2810 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
2811 d0 = base.replace(minute=3)
2812 d1 = base.replace(minute=9)
2813 d2 = base.replace(minute=11)
2814 for x in d0, d1, d2:
2815 for y in d0, d1, d2:
2816 got = x - y
Tim Peters60c76e42002-12-27 00:41:11 +00002817 expected = timedelta(minutes=x.minute - y.minute)
2818 self.assertEqual(got, expected)
2819
2820 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
2821 # ignored.
2822 base = cls(8, 9, 10, 11, 12, 13, 14)
2823 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2824 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2825 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2826 for x in d0, d1, d2:
2827 for y in d0, d1, d2:
2828 got = x - y
Tim Peters4c0db782002-12-26 05:01:19 +00002829 if (x is d0 or x is d1) and (y is d0 or y is d1):
2830 expected = timedelta(0)
2831 elif x is y is d2:
2832 expected = timedelta(0)
2833 elif x is d2:
2834 expected = timedelta(minutes=(11-59)-0)
2835 else:
2836 assert y is d2
2837 expected = timedelta(minutes=0-(11-59))
2838 self.assertEqual(got, expected)
2839
Tim Peters60c76e42002-12-27 00:41:11 +00002840 def test_mixed_compare(self):
2841 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002842 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters60c76e42002-12-27 00:41:11 +00002843 self.assertEqual(t1, t2)
2844 t2 = t2.replace(tzinfo=None)
2845 self.assertEqual(t1, t2)
2846 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2847 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002848 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2849 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002850
Tim Peters0bf60bd2003-01-08 20:40:01 +00002851 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002852 class Varies(tzinfo):
2853 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002854 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002855 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002856 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002857 return self.offset
2858
2859 v = Varies()
2860 t1 = t2.replace(tzinfo=v)
2861 t2 = t2.replace(tzinfo=v)
2862 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2863 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2864 self.assertEqual(t1, t2)
2865
2866 # But if they're not identical, it isn't ignored.
2867 t2 = t2.replace(tzinfo=Varies())
2868 self.failUnless(t1 < t2) # t1's offset counter still going up
Tim Peters80475bb2002-12-25 07:40:55 +00002869
Tim Petersa98924a2003-05-17 05:55:19 +00002870 def test_subclass_datetimetz(self):
2871
2872 class C(self.theclass):
2873 theAnswer = 42
2874
2875 def __new__(cls, *args, **kws):
2876 temp = kws.copy()
2877 extra = temp.pop('extra')
2878 result = self.theclass.__new__(cls, *args, **temp)
2879 result.extra = extra
2880 return result
2881
2882 def newmeth(self, start):
2883 return start + self.hour + self.year
2884
2885 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2886
2887 dt1 = self.theclass(*args)
2888 dt2 = C(*args, **{'extra': 7})
2889
2890 self.assertEqual(dt2.__class__, C)
2891 self.assertEqual(dt2.theAnswer, 42)
2892 self.assertEqual(dt2.extra, 7)
2893 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2894 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
2895
Tim Peters621818b2002-12-29 23:44:49 +00002896# Pain to set up DST-aware tzinfo classes.
2897
2898def first_sunday_on_or_after(dt):
2899 days_to_go = 6 - dt.weekday()
2900 if days_to_go:
2901 dt += timedelta(days_to_go)
2902 return dt
2903
2904ZERO = timedelta(0)
2905HOUR = timedelta(hours=1)
2906DAY = timedelta(days=1)
2907# In the US, DST starts at 2am (standard time) on the first Sunday in April.
2908DSTSTART = datetime(1, 4, 1, 2)
2909# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
Tim Peters327098a2003-01-20 22:54:38 +00002910# which is the first Sunday on or after Oct 25. Because we view 1:MM as
2911# being standard time on that day, there is no spelling in local time of
2912# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
2913DSTEND = datetime(1, 10, 25, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002914
2915class USTimeZone(tzinfo):
2916
2917 def __init__(self, hours, reprname, stdname, dstname):
2918 self.stdoffset = timedelta(hours=hours)
2919 self.reprname = reprname
2920 self.stdname = stdname
2921 self.dstname = dstname
2922
2923 def __repr__(self):
2924 return self.reprname
2925
2926 def tzname(self, dt):
2927 if self.dst(dt):
2928 return self.dstname
2929 else:
2930 return self.stdname
2931
2932 def utcoffset(self, dt):
2933 return self.stdoffset + self.dst(dt)
2934
2935 def dst(self, dt):
Tim Petersbad8ff02002-12-30 20:52:32 +00002936 if dt is None or dt.tzinfo is None:
Tim Peters621818b2002-12-29 23:44:49 +00002937 # An exception instead may be sensible here, in one or more of
2938 # the cases.
2939 return ZERO
Tim Peters521fc152002-12-31 17:36:56 +00002940 assert dt.tzinfo is self
Tim Peters621818b2002-12-29 23:44:49 +00002941
2942 # Find first Sunday in April.
2943 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
2944 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
2945
2946 # Find last Sunday in October.
2947 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
2948 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
2949
Tim Peters621818b2002-12-29 23:44:49 +00002950 # Can't compare naive to aware objects, so strip the timezone from
2951 # dt first.
Tim Peters52dcce22003-01-23 16:36:11 +00002952 if start <= dt.replace(tzinfo=None) < end:
Tim Peters621818b2002-12-29 23:44:49 +00002953 return HOUR
2954 else:
2955 return ZERO
2956
Tim Peters521fc152002-12-31 17:36:56 +00002957Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
2958Central = USTimeZone(-6, "Central", "CST", "CDT")
2959Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
2960Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
Tim Peters1024bf82002-12-30 17:09:40 +00002961utc_real = FixedOffset(0, "UTC", 0)
2962# For better test coverage, we want another flavor of UTC that's west of
2963# the Eastern and Pacific timezones.
Tim Petersadf64202003-01-04 06:03:15 +00002964utc_fake = FixedOffset(-12*60, "UTCfake", 0)
Tim Peters621818b2002-12-29 23:44:49 +00002965
2966class TestTimezoneConversions(unittest.TestCase):
Tim Peters327098a2003-01-20 22:54:38 +00002967 # The DST switch times for 2002, in std time.
Tim Peters0bf60bd2003-01-08 20:40:01 +00002968 dston = datetime(2002, 4, 7, 2)
Tim Peters327098a2003-01-20 22:54:38 +00002969 dstoff = datetime(2002, 10, 27, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002970
Tim Peters0bf60bd2003-01-08 20:40:01 +00002971 theclass = datetime
Tim Peters710fb152003-01-02 19:35:54 +00002972
Tim Peters521fc152002-12-31 17:36:56 +00002973 # Check a time that's inside DST.
2974 def checkinside(self, dt, tz, utc, dston, dstoff):
2975 self.assertEqual(dt.dst(), HOUR)
2976
2977 # Conversion to our own timezone is always an identity.
2978 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters521fc152002-12-31 17:36:56 +00002979
2980 asutc = dt.astimezone(utc)
2981 there_and_back = asutc.astimezone(tz)
2982
2983 # Conversion to UTC and back isn't always an identity here,
2984 # because there are redundant spellings (in local time) of
2985 # UTC time when DST begins: the clock jumps from 1:59:59
2986 # to 3:00:00, and a local time of 2:MM:SS doesn't really
2987 # make sense then. The classes above treat 2:MM:SS as
2988 # daylight time then (it's "after 2am"), really an alias
2989 # for 1:MM:SS standard time. The latter form is what
2990 # conversion back from UTC produces.
2991 if dt.date() == dston.date() and dt.hour == 2:
2992 # We're in the redundant hour, and coming back from
2993 # UTC gives the 1:MM:SS standard-time spelling.
2994 self.assertEqual(there_and_back + HOUR, dt)
2995 # Although during was considered to be in daylight
2996 # time, there_and_back is not.
2997 self.assertEqual(there_and_back.dst(), ZERO)
2998 # They're the same times in UTC.
2999 self.assertEqual(there_and_back.astimezone(utc),
3000 dt.astimezone(utc))
3001 else:
3002 # We're not in the redundant hour.
3003 self.assertEqual(dt, there_and_back)
3004
Tim Peters327098a2003-01-20 22:54:38 +00003005 # Because we have a redundant spelling when DST begins, there is
3006 # (unforunately) an hour when DST ends that can't be spelled at all in
3007 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
3008 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
3009 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
3010 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
3011 # expressed in local time. Nevertheless, we want conversion back
3012 # from UTC to mimic the local clock's "repeat an hour" behavior.
Tim Peters521fc152002-12-31 17:36:56 +00003013 nexthour_utc = asutc + HOUR
Tim Petersadf64202003-01-04 06:03:15 +00003014 nexthour_tz = nexthour_utc.astimezone(tz)
Tim Peters327098a2003-01-20 22:54:38 +00003015 if dt.date() == dstoff.date() and dt.hour == 0:
3016 # We're in the hour before the last DST hour. The last DST hour
Tim Petersadf64202003-01-04 06:03:15 +00003017 # is ineffable. We want the conversion back to repeat 1:MM.
Tim Peters327098a2003-01-20 22:54:38 +00003018 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3019 nexthour_utc += HOUR
3020 nexthour_tz = nexthour_utc.astimezone(tz)
3021 self.assertEqual(nexthour_tz, dt.replace(hour=1))
Tim Peters521fc152002-12-31 17:36:56 +00003022 else:
Tim Peters327098a2003-01-20 22:54:38 +00003023 self.assertEqual(nexthour_tz - dt, HOUR)
Tim Peters521fc152002-12-31 17:36:56 +00003024
3025 # Check a time that's outside DST.
3026 def checkoutside(self, dt, tz, utc):
3027 self.assertEqual(dt.dst(), ZERO)
3028
3029 # Conversion to our own timezone is always an identity.
3030 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters52dcce22003-01-23 16:36:11 +00003031
3032 # Converting to UTC and back is an identity too.
3033 asutc = dt.astimezone(utc)
3034 there_and_back = asutc.astimezone(tz)
3035 self.assertEqual(dt, there_and_back)
Tim Peters521fc152002-12-31 17:36:56 +00003036
Tim Peters1024bf82002-12-30 17:09:40 +00003037 def convert_between_tz_and_utc(self, tz, utc):
3038 dston = self.dston.replace(tzinfo=tz)
Tim Peters327098a2003-01-20 22:54:38 +00003039 # Because 1:MM on the day DST ends is taken as being standard time,
3040 # there is no spelling in tz for the last hour of daylight time.
3041 # For purposes of the test, the last hour of DST is 0:MM, which is
3042 # taken as being daylight time (and 1:MM is taken as being standard
3043 # time).
Tim Peters1024bf82002-12-30 17:09:40 +00003044 dstoff = self.dstoff.replace(tzinfo=tz)
3045 for delta in (timedelta(weeks=13),
3046 DAY,
3047 HOUR,
3048 timedelta(minutes=1),
3049 timedelta(microseconds=1)):
3050
Tim Peters521fc152002-12-31 17:36:56 +00003051 self.checkinside(dston, tz, utc, dston, dstoff)
3052 for during in dston + delta, dstoff - delta:
3053 self.checkinside(during, tz, utc, dston, dstoff)
Tim Peters31cc3152002-12-30 17:37:30 +00003054
Tim Peters521fc152002-12-31 17:36:56 +00003055 self.checkoutside(dstoff, tz, utc)
3056 for outside in dston - delta, dstoff + delta:
3057 self.checkoutside(outside, tz, utc)
Tim Peters31cc3152002-12-30 17:37:30 +00003058
Tim Peters621818b2002-12-29 23:44:49 +00003059 def test_easy(self):
3060 # Despite the name of this test, the endcases are excruciating.
Tim Peters1024bf82002-12-30 17:09:40 +00003061 self.convert_between_tz_and_utc(Eastern, utc_real)
3062 self.convert_between_tz_and_utc(Pacific, utc_real)
3063 self.convert_between_tz_and_utc(Eastern, utc_fake)
3064 self.convert_between_tz_and_utc(Pacific, utc_fake)
3065 # The next is really dancing near the edge. It works because
3066 # Pacific and Eastern are far enough apart that their "problem
3067 # hours" don't overlap.
3068 self.convert_between_tz_and_utc(Eastern, Pacific)
3069 self.convert_between_tz_and_utc(Pacific, Eastern)
Tim Peters36087ed2003-01-01 04:18:51 +00003070 # OTOH, these fail! Don't enable them. The difficulty is that
3071 # the edge case tests assume that every hour is representable in
3072 # the "utc" class. This is always true for a fixed-offset tzinfo
3073 # class (lke utc_real and utc_fake), but not for Eastern or Central.
3074 # For these adjacent DST-aware time zones, the range of time offsets
3075 # tested ends up creating hours in the one that aren't representable
3076 # in the other. For the same reason, we would see failures in the
3077 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
3078 # offset deltas in convert_between_tz_and_utc().
3079 #
3080 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
3081 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
Tim Peters621818b2002-12-29 23:44:49 +00003082
Tim Petersf3615152003-01-01 21:51:37 +00003083 def test_tricky(self):
3084 # 22:00 on day before daylight starts.
3085 fourback = self.dston - timedelta(hours=4)
3086 ninewest = FixedOffset(-9*60, "-0900", 0)
Tim Peters52dcce22003-01-23 16:36:11 +00003087 fourback = fourback.replace(tzinfo=ninewest)
Tim Petersf3615152003-01-01 21:51:37 +00003088 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
3089 # 2", we should get the 3 spelling.
3090 # If we plug 22:00 the day before into Eastern, it "looks like std
3091 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
3092 # to 22:00 lands on 2:00, which makes no sense in local time (the
3093 # local clock jumps from 1 to 3). The point here is to make sure we
3094 # get the 3 spelling.
3095 expected = self.dston.replace(hour=3)
Tim Peters52dcce22003-01-23 16:36:11 +00003096 got = fourback.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00003097 self.assertEqual(expected, got)
3098
3099 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
3100 # case we want the 1:00 spelling.
Tim Peters52dcce22003-01-23 16:36:11 +00003101 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
Tim Petersf3615152003-01-01 21:51:37 +00003102 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
3103 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
3104 # spelling.
3105 expected = self.dston.replace(hour=1)
Tim Peters52dcce22003-01-23 16:36:11 +00003106 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00003107 self.assertEqual(expected, got)
Tim Peters621818b2002-12-29 23:44:49 +00003108
Tim Petersadf64202003-01-04 06:03:15 +00003109 # Now on the day DST ends, we want "repeat an hour" behavior.
3110 # UTC 4:MM 5:MM 6:MM 7:MM checking these
3111 # EST 23:MM 0:MM 1:MM 2:MM
3112 # EDT 0:MM 1:MM 2:MM 3:MM
3113 # wall 0:MM 1:MM 1:MM 2:MM against these
3114 for utc in utc_real, utc_fake:
3115 for tz in Eastern, Pacific:
Tim Peters327098a2003-01-20 22:54:38 +00003116 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
Tim Petersadf64202003-01-04 06:03:15 +00003117 # Convert that to UTC.
3118 first_std_hour -= tz.utcoffset(None)
3119 # Adjust for possibly fake UTC.
3120 asutc = first_std_hour + utc.utcoffset(None)
3121 # First UTC hour to convert; this is 4:00 when utc=utc_real &
3122 # tz=Eastern.
3123 asutcbase = asutc.replace(tzinfo=utc)
3124 for tzhour in (0, 1, 1, 2):
3125 expectedbase = self.dstoff.replace(hour=tzhour)
3126 for minute in 0, 30, 59:
3127 expected = expectedbase.replace(minute=minute)
3128 asutc = asutcbase.replace(minute=minute)
3129 astz = asutc.astimezone(tz)
3130 self.assertEqual(astz.replace(tzinfo=None), expected)
3131 asutcbase += HOUR
3132
3133
Tim Peters710fb152003-01-02 19:35:54 +00003134 def test_bogus_dst(self):
3135 class ok(tzinfo):
3136 def utcoffset(self, dt): return HOUR
3137 def dst(self, dt): return HOUR
3138
3139 now = self.theclass.now().replace(tzinfo=utc_real)
3140 # Doesn't blow up.
3141 now.astimezone(ok())
3142
3143 # Does blow up.
3144 class notok(ok):
3145 def dst(self, dt): return None
3146 self.assertRaises(ValueError, now.astimezone, notok())
3147
Tim Peters52dcce22003-01-23 16:36:11 +00003148 def test_fromutc(self):
3149 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
3150 now = datetime.utcnow().replace(tzinfo=utc_real)
3151 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3152 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
3153 enow = Eastern.fromutc(now) # doesn't blow up
3154 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3155 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3156 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3157
3158 # Always converts UTC to standard time.
3159 class FauxUSTimeZone(USTimeZone):
3160 def fromutc(self, dt):
3161 return dt + self.stdoffset
3162 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
3163
3164 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
3165 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
3166 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
3167
3168 # Check around DST start.
3169 start = self.dston.replace(hour=4, tzinfo=Eastern)
3170 fstart = start.replace(tzinfo=FEastern)
3171 for wall in 23, 0, 1, 3, 4, 5:
3172 expected = start.replace(hour=wall)
3173 if wall == 23:
3174 expected -= timedelta(days=1)
3175 got = Eastern.fromutc(start)
3176 self.assertEqual(expected, got)
3177
3178 expected = fstart + FEastern.stdoffset
3179 got = FEastern.fromutc(fstart)
3180 self.assertEqual(expected, got)
3181
3182 # Ensure astimezone() calls fromutc() too.
3183 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3184 self.assertEqual(expected, got)
3185
3186 start += HOUR
3187 fstart += HOUR
3188
3189 # Check around DST end.
3190 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
3191 fstart = start.replace(tzinfo=FEastern)
3192 for wall in 0, 1, 1, 2, 3, 4:
3193 expected = start.replace(hour=wall)
3194 got = Eastern.fromutc(start)
3195 self.assertEqual(expected, got)
3196
3197 expected = fstart + FEastern.stdoffset
3198 got = FEastern.fromutc(fstart)
3199 self.assertEqual(expected, got)
3200
3201 # Ensure astimezone() calls fromutc() too.
3202 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3203 self.assertEqual(expected, got)
3204
3205 start += HOUR
3206 fstart += HOUR
3207
Tim Peters710fb152003-01-02 19:35:54 +00003208
Tim Peters528ca532004-09-16 01:30:50 +00003209#############################################################################
3210# oddballs
3211
3212class Oddballs(unittest.TestCase):
3213
3214 def test_bug_1028306(self):
3215 # Trying to compare a date to a datetime should act like a mixed-
3216 # type comparison, despite that datetime is a subclass of date.
3217 as_date = date.today()
3218 as_datetime = datetime.combine(as_date, time())
3219 self.assert_(as_date != as_datetime)
3220 self.assert_(as_datetime != as_date)
3221 self.assert_(not as_date == as_datetime)
3222 self.assert_(not as_datetime == as_date)
3223 self.assertRaises(TypeError, lambda: as_date < as_datetime)
3224 self.assertRaises(TypeError, lambda: as_datetime < as_date)
3225 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
3226 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
3227 self.assertRaises(TypeError, lambda: as_date > as_datetime)
3228 self.assertRaises(TypeError, lambda: as_datetime > as_date)
3229 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
3230 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
3231
3232 # Neverthelss, comparison should work with the base-class (date)
3233 # projection if use of a date method is forced.
3234 self.assert_(as_date.__eq__(as_datetime))
3235 different_day = (as_date.day + 1) % 20 + 1
3236 self.assert_(not as_date.__eq__(as_datetime.replace(day=
3237 different_day)))
3238
3239 # And date should compare with other subclasses of date. If a
3240 # subclass wants to stop this, it's up to the subclass to do so.
3241 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
3242 self.assertEqual(as_date, date_sc)
3243 self.assertEqual(date_sc, as_date)
3244
3245 # Ditto for datetimes.
3246 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
3247 as_date.day, 0, 0, 0)
3248 self.assertEqual(as_datetime, datetime_sc)
3249 self.assertEqual(datetime_sc, as_datetime)
3250
Tim Peters2a799bf2002-12-16 20:18:38 +00003251def test_main():
Collin Winterbec754c2007-04-25 17:37:35 +00003252 test_support.run_unittest(__name__)
Tim Peters2a799bf2002-12-16 20:18:38 +00003253
3254if __name__ == "__main__":
3255 test_main()