blob: 9abfb875b9a79554891a5dceb0c4a322d8168e9a [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
6import sys
Guido van Rossum177e41a2003-01-30 22:06:23 +00007import pickle
8import cPickle
Tim Peters2a799bf2002-12-16 20:18:38 +00009import unittest
10
11from test import test_support
12
13from datetime import MINYEAR, MAXYEAR
14from datetime import timedelta
15from datetime import tzinfo
Tim Peters0bf60bd2003-01-08 20:40:01 +000016from datetime import time
17from datetime import date, datetime
18
Tim Peters35ad6412003-02-05 04:08:07 +000019pickle_choices = [(pickler, unpickler, proto)
20 for pickler in pickle, cPickle
21 for unpickler in pickle, cPickle
22 for proto in range(3)]
23assert len(pickle_choices) == 2*2*3
Guido van Rossum177e41a2003-01-30 22:06:23 +000024
Tim Peters68124bb2003-02-08 03:46:31 +000025# An arbitrary collection of objects of non-datetime types, for testing
26# mixed-type comparisons.
27OTHERSTUFF = (10, 10L, 34.5, "abc", {}, [], ())
Tim Peters0bf60bd2003-01-08 20:40:01 +000028
Tim Peters2a799bf2002-12-16 20:18:38 +000029
30#############################################################################
31# module tests
32
33class TestModule(unittest.TestCase):
34
35 def test_constants(self):
36 import datetime
37 self.assertEqual(datetime.MINYEAR, 1)
38 self.assertEqual(datetime.MAXYEAR, 9999)
39
40#############################################################################
41# tzinfo tests
42
43class FixedOffset(tzinfo):
44 def __init__(self, offset, name, dstoffset=42):
Tim Peters397301e2003-01-02 21:28:08 +000045 if isinstance(offset, int):
46 offset = timedelta(minutes=offset)
47 if isinstance(dstoffset, int):
48 dstoffset = timedelta(minutes=dstoffset)
Tim Peters2a799bf2002-12-16 20:18:38 +000049 self.__offset = offset
50 self.__name = name
51 self.__dstoffset = dstoffset
52 def __repr__(self):
53 return self.__name.lower()
54 def utcoffset(self, dt):
55 return self.__offset
56 def tzname(self, dt):
57 return self.__name
58 def dst(self, dt):
59 return self.__dstoffset
60
Tim Petersfb8472c2002-12-21 05:04:42 +000061class PicklableFixedOffset(FixedOffset):
62 def __init__(self, offset=None, name=None, dstoffset=None):
63 FixedOffset.__init__(self, offset, name, dstoffset)
64
Tim Peters2a799bf2002-12-16 20:18:38 +000065class TestTZInfo(unittest.TestCase):
66
67 def test_non_abstractness(self):
68 # In order to allow subclasses to get pickled, the C implementation
69 # wasn't able to get away with having __init__ raise
70 # NotImplementedError.
71 useless = tzinfo()
72 dt = datetime.max
73 self.assertRaises(NotImplementedError, useless.tzname, dt)
74 self.assertRaises(NotImplementedError, useless.utcoffset, dt)
75 self.assertRaises(NotImplementedError, useless.dst, dt)
76
77 def test_subclass_must_override(self):
78 class NotEnough(tzinfo):
79 def __init__(self, offset, name):
80 self.__offset = offset
81 self.__name = name
82 self.failUnless(issubclass(NotEnough, tzinfo))
83 ne = NotEnough(3, "NotByALongShot")
84 self.failUnless(isinstance(ne, tzinfo))
85
86 dt = datetime.now()
87 self.assertRaises(NotImplementedError, ne.tzname, dt)
88 self.assertRaises(NotImplementedError, ne.utcoffset, dt)
89 self.assertRaises(NotImplementedError, ne.dst, dt)
90
91 def test_normal(self):
92 fo = FixedOffset(3, "Three")
93 self.failUnless(isinstance(fo, tzinfo))
94 for dt in datetime.now(), None:
Tim Peters397301e2003-01-02 21:28:08 +000095 self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +000096 self.assertEqual(fo.tzname(dt), "Three")
Tim Peters397301e2003-01-02 21:28:08 +000097 self.assertEqual(fo.dst(dt), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +000098
99 def test_pickling_base(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000100 # There's no point to pickling tzinfo objects on their own (they
101 # carry no data), but they need to be picklable anyway else
102 # concrete subclasses can't be pickled.
103 orig = tzinfo.__new__(tzinfo)
104 self.failUnless(type(orig) is tzinfo)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000105 for pickler, unpickler, proto in pickle_choices:
Tim Petersf2715e02003-02-19 02:35:07 +0000106 green = pickler.dumps(orig, proto)
107 derived = unpickler.loads(green)
108 self.failUnless(type(derived) is tzinfo)
Tim Peters2a799bf2002-12-16 20:18:38 +0000109
110 def test_pickling_subclass(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000111 # Make sure we can pickle/unpickle an instance of a subclass.
Tim Peters397301e2003-01-02 21:28:08 +0000112 offset = timedelta(minutes=-300)
113 orig = PicklableFixedOffset(offset, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +0000114 self.failUnless(isinstance(orig, tzinfo))
Tim Petersfb8472c2002-12-21 05:04:42 +0000115 self.failUnless(type(orig) is PicklableFixedOffset)
Tim Peters397301e2003-01-02 21:28:08 +0000116 self.assertEqual(orig.utcoffset(None), offset)
Tim Peters2a799bf2002-12-16 20:18:38 +0000117 self.assertEqual(orig.tzname(None), 'cookie')
Guido van Rossum177e41a2003-01-30 22:06:23 +0000118 for pickler, unpickler, proto in pickle_choices:
Tim Petersf2715e02003-02-19 02:35:07 +0000119 green = pickler.dumps(orig, proto)
120 derived = unpickler.loads(green)
121 self.failUnless(isinstance(derived, tzinfo))
122 self.failUnless(type(derived) is PicklableFixedOffset)
123 self.assertEqual(derived.utcoffset(None), offset)
124 self.assertEqual(derived.tzname(None), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +0000125
126#############################################################################
Tim Peters07534a62003-02-07 22:50:28 +0000127# Base clase for testing a particular aspect of timedelta, time, date and
128# datetime comparisons.
129
130class HarmlessMixedComparison(unittest.TestCase):
131 # Test that __eq__ and __ne__ don't complain for mixed-type comparisons.
132
133 # Subclasses must define 'theclass', and theclass(1, 1, 1) must be a
134 # legit constructor.
135
136 def test_harmless_mixed_comparison(self):
137 me = self.theclass(1, 1, 1)
138
139 self.failIf(me == ())
140 self.failUnless(me != ())
141 self.failIf(() == me)
142 self.failUnless(() != me)
143
144 self.failUnless(me in [1, 20L, [], me])
145 self.failIf(me not in [1, 20L, [], me])
146
147 self.failUnless([] in [me, 1, 20L, []])
148 self.failIf([] not in [me, 1, 20L, []])
149
150 def test_harmful_mixed_comparison(self):
151 me = self.theclass(1, 1, 1)
152
153 self.assertRaises(TypeError, lambda: me < ())
154 self.assertRaises(TypeError, lambda: me <= ())
155 self.assertRaises(TypeError, lambda: me > ())
156 self.assertRaises(TypeError, lambda: me >= ())
157
158 self.assertRaises(TypeError, lambda: () < me)
159 self.assertRaises(TypeError, lambda: () <= me)
160 self.assertRaises(TypeError, lambda: () > me)
161 self.assertRaises(TypeError, lambda: () >= me)
162
163 self.assertRaises(TypeError, cmp, (), me)
164 self.assertRaises(TypeError, cmp, me, ())
165
166#############################################################################
Tim Peters2a799bf2002-12-16 20:18:38 +0000167# timedelta tests
168
Tim Peters07534a62003-02-07 22:50:28 +0000169class TestTimeDelta(HarmlessMixedComparison):
170
171 theclass = timedelta
Tim Peters2a799bf2002-12-16 20:18:38 +0000172
173 def test_constructor(self):
174 eq = self.assertEqual
175 td = timedelta
176
177 # Check keyword args to constructor
178 eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
179 milliseconds=0, microseconds=0))
180 eq(td(1), td(days=1))
181 eq(td(0, 1), td(seconds=1))
182 eq(td(0, 0, 1), td(microseconds=1))
183 eq(td(weeks=1), td(days=7))
184 eq(td(days=1), td(hours=24))
185 eq(td(hours=1), td(minutes=60))
186 eq(td(minutes=1), td(seconds=60))
187 eq(td(seconds=1), td(milliseconds=1000))
188 eq(td(milliseconds=1), td(microseconds=1000))
189
190 # Check float args to constructor
191 eq(td(weeks=1.0/7), td(days=1))
192 eq(td(days=1.0/24), td(hours=1))
193 eq(td(hours=1.0/60), td(minutes=1))
194 eq(td(minutes=1.0/60), td(seconds=1))
195 eq(td(seconds=0.001), td(milliseconds=1))
196 eq(td(milliseconds=0.001), td(microseconds=1))
197
198 def test_computations(self):
199 eq = self.assertEqual
200 td = timedelta
201
202 a = td(7) # One week
203 b = td(0, 60) # One minute
204 c = td(0, 0, 1000) # One millisecond
205 eq(a+b+c, td(7, 60, 1000))
206 eq(a-b, td(6, 24*3600 - 60))
207 eq(-a, td(-7))
208 eq(+a, td(7))
209 eq(-b, td(-1, 24*3600 - 60))
210 eq(-c, td(-1, 24*3600 - 1, 999000))
211 eq(abs(a), a)
212 eq(abs(-a), a)
213 eq(td(6, 24*3600), a)
214 eq(td(0, 0, 60*1000000), b)
215 eq(a*10, td(70))
216 eq(a*10, 10*a)
217 eq(a*10L, 10*a)
218 eq(b*10, td(0, 600))
219 eq(10*b, td(0, 600))
220 eq(b*10L, td(0, 600))
221 eq(c*10, td(0, 0, 10000))
222 eq(10*c, td(0, 0, 10000))
223 eq(c*10L, td(0, 0, 10000))
224 eq(a*-1, -a)
225 eq(b*-2, -b-b)
226 eq(c*-2, -c+-c)
227 eq(b*(60*24), (b*60)*24)
228 eq(b*(60*24), (60*b)*24)
229 eq(c*1000, td(0, 1))
230 eq(1000*c, td(0, 1))
231 eq(a//7, td(1))
232 eq(b//10, td(0, 6))
233 eq(c//1000, td(0, 0, 1))
234 eq(a//10, td(0, 7*24*360))
235 eq(a//3600000, td(0, 0, 7*24*1000))
236
237 def test_disallowed_computations(self):
238 a = timedelta(42)
239
240 # Add/sub ints, longs, floats should be illegal
241 for i in 1, 1L, 1.0:
242 self.assertRaises(TypeError, lambda: a+i)
243 self.assertRaises(TypeError, lambda: a-i)
244 self.assertRaises(TypeError, lambda: i+a)
245 self.assertRaises(TypeError, lambda: i-a)
246
247 # Mul/div by float isn't supported.
248 x = 2.3
249 self.assertRaises(TypeError, lambda: a*x)
250 self.assertRaises(TypeError, lambda: x*a)
251 self.assertRaises(TypeError, lambda: a/x)
252 self.assertRaises(TypeError, lambda: x/a)
253 self.assertRaises(TypeError, lambda: a // x)
254 self.assertRaises(TypeError, lambda: x // a)
255
256 # Divison of int by timedelta doesn't make sense.
257 # Division by zero doesn't make sense.
258 for zero in 0, 0L:
259 self.assertRaises(TypeError, lambda: zero // a)
260 self.assertRaises(ZeroDivisionError, lambda: a // zero)
261
262 def test_basic_attributes(self):
263 days, seconds, us = 1, 7, 31
264 td = timedelta(days, seconds, us)
265 self.assertEqual(td.days, days)
266 self.assertEqual(td.seconds, seconds)
267 self.assertEqual(td.microseconds, us)
268
269 def test_carries(self):
270 t1 = timedelta(days=100,
271 weeks=-7,
272 hours=-24*(100-49),
273 minutes=-3,
274 seconds=12,
275 microseconds=(3*60 - 12) * 1e6 + 1)
276 t2 = timedelta(microseconds=1)
277 self.assertEqual(t1, t2)
278
279 def test_hash_equality(self):
280 t1 = timedelta(days=100,
281 weeks=-7,
282 hours=-24*(100-49),
283 minutes=-3,
284 seconds=12,
285 microseconds=(3*60 - 12) * 1000000)
286 t2 = timedelta()
287 self.assertEqual(hash(t1), hash(t2))
288
289 t1 += timedelta(weeks=7)
290 t2 += timedelta(days=7*7)
291 self.assertEqual(t1, t2)
292 self.assertEqual(hash(t1), hash(t2))
293
294 d = {t1: 1}
295 d[t2] = 2
296 self.assertEqual(len(d), 1)
297 self.assertEqual(d[t1], 2)
298
299 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000300 args = 12, 34, 56
301 orig = timedelta(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000302 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +0000303 green = pickler.dumps(orig, proto)
304 derived = unpickler.loads(green)
305 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +0000306
307 def test_compare(self):
308 t1 = timedelta(2, 3, 4)
309 t2 = timedelta(2, 3, 4)
310 self.failUnless(t1 == t2)
311 self.failUnless(t1 <= t2)
312 self.failUnless(t1 >= t2)
313 self.failUnless(not t1 != t2)
314 self.failUnless(not t1 < t2)
315 self.failUnless(not t1 > t2)
316 self.assertEqual(cmp(t1, t2), 0)
317 self.assertEqual(cmp(t2, t1), 0)
318
319 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
320 t2 = timedelta(*args) # this is larger than t1
321 self.failUnless(t1 < t2)
322 self.failUnless(t2 > t1)
323 self.failUnless(t1 <= t2)
324 self.failUnless(t2 >= t1)
325 self.failUnless(t1 != t2)
326 self.failUnless(t2 != t1)
327 self.failUnless(not t1 == t2)
328 self.failUnless(not t2 == t1)
329 self.failUnless(not t1 > t2)
330 self.failUnless(not t2 < t1)
331 self.failUnless(not t1 >= t2)
332 self.failUnless(not t2 <= t1)
333 self.assertEqual(cmp(t1, t2), -1)
334 self.assertEqual(cmp(t2, t1), 1)
335
Tim Peters68124bb2003-02-08 03:46:31 +0000336 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +0000337 self.assertEqual(t1 == badarg, False)
338 self.assertEqual(t1 != badarg, True)
339 self.assertEqual(badarg == t1, False)
340 self.assertEqual(badarg != t1, True)
341
Tim Peters2a799bf2002-12-16 20:18:38 +0000342 self.assertRaises(TypeError, lambda: t1 <= badarg)
343 self.assertRaises(TypeError, lambda: t1 < badarg)
344 self.assertRaises(TypeError, lambda: t1 > badarg)
345 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +0000346 self.assertRaises(TypeError, lambda: badarg <= t1)
347 self.assertRaises(TypeError, lambda: badarg < t1)
348 self.assertRaises(TypeError, lambda: badarg > t1)
349 self.assertRaises(TypeError, lambda: badarg >= t1)
350
351 def test_str(self):
352 td = timedelta
353 eq = self.assertEqual
354
355 eq(str(td(1)), "1 day, 0:00:00")
356 eq(str(td(-1)), "-1 day, 0:00:00")
357 eq(str(td(2)), "2 days, 0:00:00")
358 eq(str(td(-2)), "-2 days, 0:00:00")
359
360 eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
361 eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
362 eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
363 "-210 days, 23:12:34")
364
365 eq(str(td(milliseconds=1)), "0:00:00.001000")
366 eq(str(td(microseconds=3)), "0:00:00.000003")
367
368 eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
369 microseconds=999999)),
370 "999999999 days, 23:59:59.999999")
371
372 def test_roundtrip(self):
373 for td in (timedelta(days=999999999, hours=23, minutes=59,
374 seconds=59, microseconds=999999),
375 timedelta(days=-999999999),
376 timedelta(days=1, seconds=2, microseconds=3)):
377
378 # Verify td -> string -> td identity.
379 s = repr(td)
380 self.failUnless(s.startswith('datetime.'))
381 s = s[9:]
382 td2 = eval(s)
383 self.assertEqual(td, td2)
384
385 # Verify identity via reconstructing from pieces.
386 td2 = timedelta(td.days, td.seconds, td.microseconds)
387 self.assertEqual(td, td2)
388
389 def test_resolution_info(self):
390 self.assert_(isinstance(timedelta.min, timedelta))
391 self.assert_(isinstance(timedelta.max, timedelta))
392 self.assert_(isinstance(timedelta.resolution, timedelta))
393 self.assert_(timedelta.max > timedelta.min)
394 self.assertEqual(timedelta.min, timedelta(-999999999))
395 self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
396 self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
397
398 def test_overflow(self):
399 tiny = timedelta.resolution
400
401 td = timedelta.min + tiny
402 td -= tiny # no problem
403 self.assertRaises(OverflowError, td.__sub__, tiny)
404 self.assertRaises(OverflowError, td.__add__, -tiny)
405
406 td = timedelta.max - tiny
407 td += tiny # no problem
408 self.assertRaises(OverflowError, td.__add__, tiny)
409 self.assertRaises(OverflowError, td.__sub__, -tiny)
410
411 self.assertRaises(OverflowError, lambda: -timedelta.max)
412
413 def test_microsecond_rounding(self):
414 td = timedelta
415 eq = self.assertEqual
416
417 # Single-field rounding.
418 eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
419 eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
420 eq(td(milliseconds=0.6/1000), td(microseconds=1))
421 eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
422
423 # Rounding due to contributions from more than one field.
424 us_per_hour = 3600e6
425 us_per_day = us_per_hour * 24
426 eq(td(days=.4/us_per_day), td(0))
427 eq(td(hours=.2/us_per_hour), td(0))
428 eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
429
430 eq(td(days=-.4/us_per_day), td(0))
431 eq(td(hours=-.2/us_per_hour), td(0))
432 eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
433
434 def test_massive_normalization(self):
435 td = timedelta(microseconds=-1)
436 self.assertEqual((td.days, td.seconds, td.microseconds),
437 (-1, 24*3600-1, 999999))
438
439 def test_bool(self):
440 self.failUnless(timedelta(1))
441 self.failUnless(timedelta(0, 1))
442 self.failUnless(timedelta(0, 0, 1))
443 self.failUnless(timedelta(microseconds=1))
444 self.failUnless(not timedelta(0))
445
Tim Petersb0c854d2003-05-17 15:57:00 +0000446 def test_subclass_timedelta(self):
447
448 class T(timedelta):
Guido van Rossum5a8a0372005-01-16 00:25:31 +0000449 @staticmethod
Tim Petersb0c854d2003-05-17 15:57:00 +0000450 def from_td(td):
451 return T(td.days, td.seconds, td.microseconds)
Tim Petersb0c854d2003-05-17 15:57:00 +0000452
453 def as_hours(self):
454 sum = (self.days * 24 +
455 self.seconds / 3600.0 +
456 self.microseconds / 3600e6)
457 return round(sum)
458
459 t1 = T(days=1)
460 self.assert_(type(t1) is T)
461 self.assertEqual(t1.as_hours(), 24)
462
463 t2 = T(days=-1, seconds=-3600)
464 self.assert_(type(t2) is T)
465 self.assertEqual(t2.as_hours(), -25)
466
467 t3 = t1 + t2
468 self.assert_(type(t3) is timedelta)
469 t4 = T.from_td(t3)
470 self.assert_(type(t4) is T)
471 self.assertEqual(t3.days, t4.days)
472 self.assertEqual(t3.seconds, t4.seconds)
473 self.assertEqual(t3.microseconds, t4.microseconds)
474 self.assertEqual(str(t3), str(t4))
475 self.assertEqual(t4.as_hours(), -1)
476
Tim Peters2a799bf2002-12-16 20:18:38 +0000477#############################################################################
478# date tests
479
480class TestDateOnly(unittest.TestCase):
481 # Tests here won't pass if also run on datetime objects, so don't
482 # subclass this to test datetimes too.
483
484 def test_delta_non_days_ignored(self):
485 dt = date(2000, 1, 2)
486 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
487 microseconds=5)
488 days = timedelta(delta.days)
489 self.assertEqual(days, timedelta(1))
490
491 dt2 = dt + delta
492 self.assertEqual(dt2, dt + days)
493
494 dt2 = delta + dt
495 self.assertEqual(dt2, dt + days)
496
497 dt2 = dt - delta
498 self.assertEqual(dt2, dt - days)
499
500 delta = -delta
501 days = timedelta(delta.days)
502 self.assertEqual(days, timedelta(-2))
503
504 dt2 = dt + delta
505 self.assertEqual(dt2, dt + days)
506
507 dt2 = delta + dt
508 self.assertEqual(dt2, dt + days)
509
510 dt2 = dt - delta
511 self.assertEqual(dt2, dt - days)
512
Tim Peters604c0132004-06-07 23:04:33 +0000513class SubclassDate(date):
514 sub_var = 1
515
Tim Peters07534a62003-02-07 22:50:28 +0000516class TestDate(HarmlessMixedComparison):
Tim Peters2a799bf2002-12-16 20:18:38 +0000517 # Tests here should pass for both dates and datetimes, except for a
518 # few tests that TestDateTime overrides.
519
520 theclass = date
521
522 def test_basic_attributes(self):
523 dt = self.theclass(2002, 3, 1)
524 self.assertEqual(dt.year, 2002)
525 self.assertEqual(dt.month, 3)
526 self.assertEqual(dt.day, 1)
527
528 def test_roundtrip(self):
529 for dt in (self.theclass(1, 2, 3),
530 self.theclass.today()):
531 # Verify dt -> string -> date identity.
532 s = repr(dt)
533 self.failUnless(s.startswith('datetime.'))
534 s = s[9:]
535 dt2 = eval(s)
536 self.assertEqual(dt, dt2)
537
538 # Verify identity via reconstructing from pieces.
539 dt2 = self.theclass(dt.year, dt.month, dt.day)
540 self.assertEqual(dt, dt2)
541
542 def test_ordinal_conversions(self):
543 # Check some fixed values.
544 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
545 (1, 12, 31, 365),
546 (2, 1, 1, 366),
547 # first example from "Calendrical Calculations"
548 (1945, 11, 12, 710347)]:
549 d = self.theclass(y, m, d)
550 self.assertEqual(n, d.toordinal())
551 fromord = self.theclass.fromordinal(n)
552 self.assertEqual(d, fromord)
553 if hasattr(fromord, "hour"):
Tim Petersf2715e02003-02-19 02:35:07 +0000554 # if we're checking something fancier than a date, verify
555 # the extra fields have been zeroed out
Tim Peters2a799bf2002-12-16 20:18:38 +0000556 self.assertEqual(fromord.hour, 0)
557 self.assertEqual(fromord.minute, 0)
558 self.assertEqual(fromord.second, 0)
559 self.assertEqual(fromord.microsecond, 0)
560
Tim Peters0bf60bd2003-01-08 20:40:01 +0000561 # Check first and last days of year spottily across the whole
562 # range of years supported.
563 for year in xrange(MINYEAR, MAXYEAR+1, 7):
Tim Peters2a799bf2002-12-16 20:18:38 +0000564 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
565 d = self.theclass(year, 1, 1)
566 n = d.toordinal()
567 d2 = self.theclass.fromordinal(n)
568 self.assertEqual(d, d2)
Tim Peters0bf60bd2003-01-08 20:40:01 +0000569 # Verify that moving back a day gets to the end of year-1.
570 if year > 1:
571 d = self.theclass.fromordinal(n-1)
572 d2 = self.theclass(year-1, 12, 31)
573 self.assertEqual(d, d2)
574 self.assertEqual(d2.toordinal(), n-1)
Tim Peters2a799bf2002-12-16 20:18:38 +0000575
576 # Test every day in a leap-year and a non-leap year.
577 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
578 for year, isleap in (2000, True), (2002, False):
579 n = self.theclass(year, 1, 1).toordinal()
580 for month, maxday in zip(range(1, 13), dim):
581 if month == 2 and isleap:
582 maxday += 1
583 for day in range(1, maxday+1):
584 d = self.theclass(year, month, day)
585 self.assertEqual(d.toordinal(), n)
586 self.assertEqual(d, self.theclass.fromordinal(n))
587 n += 1
588
589 def test_extreme_ordinals(self):
590 a = self.theclass.min
591 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
592 aord = a.toordinal()
593 b = a.fromordinal(aord)
594 self.assertEqual(a, b)
595
596 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
597
598 b = a + timedelta(days=1)
599 self.assertEqual(b.toordinal(), aord + 1)
600 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
601
602 a = self.theclass.max
603 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
604 aord = a.toordinal()
605 b = a.fromordinal(aord)
606 self.assertEqual(a, b)
607
608 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
609
610 b = a - timedelta(days=1)
611 self.assertEqual(b.toordinal(), aord - 1)
612 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
613
614 def test_bad_constructor_arguments(self):
615 # bad years
616 self.theclass(MINYEAR, 1, 1) # no exception
617 self.theclass(MAXYEAR, 1, 1) # no exception
618 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
619 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
620 # bad months
621 self.theclass(2000, 1, 1) # no exception
622 self.theclass(2000, 12, 1) # no exception
623 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
624 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
625 # bad days
626 self.theclass(2000, 2, 29) # no exception
627 self.theclass(2004, 2, 29) # no exception
628 self.theclass(2400, 2, 29) # no exception
629 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
630 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
631 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
632 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
633 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
634 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
635
636 def test_hash_equality(self):
637 d = self.theclass(2000, 12, 31)
638 # same thing
639 e = self.theclass(2000, 12, 31)
640 self.assertEqual(d, e)
641 self.assertEqual(hash(d), hash(e))
642
643 dic = {d: 1}
644 dic[e] = 2
645 self.assertEqual(len(dic), 1)
646 self.assertEqual(dic[d], 2)
647 self.assertEqual(dic[e], 2)
648
649 d = self.theclass(2001, 1, 1)
650 # same thing
651 e = self.theclass(2001, 1, 1)
652 self.assertEqual(d, e)
653 self.assertEqual(hash(d), hash(e))
654
655 dic = {d: 1}
656 dic[e] = 2
657 self.assertEqual(len(dic), 1)
658 self.assertEqual(dic[d], 2)
659 self.assertEqual(dic[e], 2)
660
661 def test_computations(self):
662 a = self.theclass(2002, 1, 31)
663 b = self.theclass(1956, 1, 31)
664
665 diff = a-b
666 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
667 self.assertEqual(diff.seconds, 0)
668 self.assertEqual(diff.microseconds, 0)
669
670 day = timedelta(1)
671 week = timedelta(7)
672 a = self.theclass(2002, 3, 2)
673 self.assertEqual(a + day, self.theclass(2002, 3, 3))
674 self.assertEqual(day + a, self.theclass(2002, 3, 3))
675 self.assertEqual(a - day, self.theclass(2002, 3, 1))
676 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
677 self.assertEqual(a + week, self.theclass(2002, 3, 9))
678 self.assertEqual(a - week, self.theclass(2002, 2, 23))
679 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
680 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
681 self.assertEqual((a + week) - a, week)
682 self.assertEqual((a + day) - a, day)
683 self.assertEqual((a - week) - a, -week)
684 self.assertEqual((a - day) - a, -day)
685 self.assertEqual(a - (a + week), -week)
686 self.assertEqual(a - (a + day), -day)
687 self.assertEqual(a - (a - week), week)
688 self.assertEqual(a - (a - day), day)
689
690 # Add/sub ints, longs, floats should be illegal
691 for i in 1, 1L, 1.0:
692 self.assertRaises(TypeError, lambda: a+i)
693 self.assertRaises(TypeError, lambda: a-i)
694 self.assertRaises(TypeError, lambda: i+a)
695 self.assertRaises(TypeError, lambda: i-a)
696
697 # delta - date is senseless.
698 self.assertRaises(TypeError, lambda: day - a)
699 # mixing date and (delta or date) via * or // is senseless
700 self.assertRaises(TypeError, lambda: day * a)
701 self.assertRaises(TypeError, lambda: a * day)
702 self.assertRaises(TypeError, lambda: day // a)
703 self.assertRaises(TypeError, lambda: a // day)
704 self.assertRaises(TypeError, lambda: a * a)
705 self.assertRaises(TypeError, lambda: a // a)
706 # date + date is senseless
707 self.assertRaises(TypeError, lambda: a + a)
708
709 def test_overflow(self):
710 tiny = self.theclass.resolution
711
712 dt = self.theclass.min + tiny
713 dt -= tiny # no problem
714 self.assertRaises(OverflowError, dt.__sub__, tiny)
715 self.assertRaises(OverflowError, dt.__add__, -tiny)
716
717 dt = self.theclass.max - tiny
718 dt += tiny # no problem
719 self.assertRaises(OverflowError, dt.__add__, tiny)
720 self.assertRaises(OverflowError, dt.__sub__, -tiny)
721
722 def test_fromtimestamp(self):
723 import time
724
725 # Try an arbitrary fixed value.
726 year, month, day = 1999, 9, 19
727 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
728 d = self.theclass.fromtimestamp(ts)
729 self.assertEqual(d.year, year)
730 self.assertEqual(d.month, month)
731 self.assertEqual(d.day, day)
732
Tim Peters1b6f7a92004-06-20 02:50:16 +0000733 def test_insane_fromtimestamp(self):
734 # It's possible that some platform maps time_t to double,
735 # and that this test will fail there. This test should
736 # exempt such platforms (provided they return reasonable
737 # results!).
738 for insane in -1e200, 1e200:
739 self.assertRaises(ValueError, self.theclass.fromtimestamp,
740 insane)
741
Tim Peters2a799bf2002-12-16 20:18:38 +0000742 def test_today(self):
743 import time
744
745 # We claim that today() is like fromtimestamp(time.time()), so
746 # prove it.
747 for dummy in range(3):
748 today = self.theclass.today()
749 ts = time.time()
750 todayagain = self.theclass.fromtimestamp(ts)
751 if today == todayagain:
752 break
753 # There are several legit reasons that could fail:
754 # 1. It recently became midnight, between the today() and the
755 # time() calls.
756 # 2. The platform time() has such fine resolution that we'll
757 # never get the same value twice.
758 # 3. The platform time() has poor resolution, and we just
759 # happened to call today() right before a resolution quantum
760 # boundary.
761 # 4. The system clock got fiddled between calls.
762 # In any case, wait a little while and try again.
763 time.sleep(0.1)
764
765 # It worked or it didn't. If it didn't, assume it's reason #2, and
766 # let the test pass if they're within half a second of each other.
767 self.failUnless(today == todayagain or
768 abs(todayagain - today) < timedelta(seconds=0.5))
769
770 def test_weekday(self):
771 for i in range(7):
772 # March 4, 2002 is a Monday
773 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
774 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
775 # January 2, 1956 is a Monday
776 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
777 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
778
779 def test_isocalendar(self):
780 # Check examples from
781 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
782 for i in range(7):
783 d = self.theclass(2003, 12, 22+i)
784 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
785 d = self.theclass(2003, 12, 29) + timedelta(i)
786 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
787 d = self.theclass(2004, 1, 5+i)
788 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
789 d = self.theclass(2009, 12, 21+i)
790 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
791 d = self.theclass(2009, 12, 28) + timedelta(i)
792 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
793 d = self.theclass(2010, 1, 4+i)
794 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
795
796 def test_iso_long_years(self):
797 # Calculate long ISO years and compare to table from
798 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
799 ISO_LONG_YEARS_TABLE = """
800 4 32 60 88
801 9 37 65 93
802 15 43 71 99
803 20 48 76
804 26 54 82
805
806 105 133 161 189
807 111 139 167 195
808 116 144 172
809 122 150 178
810 128 156 184
811
812 201 229 257 285
813 207 235 263 291
814 212 240 268 296
815 218 246 274
816 224 252 280
817
818 303 331 359 387
819 308 336 364 392
820 314 342 370 398
821 320 348 376
822 325 353 381
823 """
824 iso_long_years = map(int, ISO_LONG_YEARS_TABLE.split())
825 iso_long_years.sort()
826 L = []
827 for i in range(400):
828 d = self.theclass(2000+i, 12, 31)
829 d1 = self.theclass(1600+i, 12, 31)
830 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
831 if d.isocalendar()[1] == 53:
832 L.append(i)
833 self.assertEqual(L, iso_long_years)
834
835 def test_isoformat(self):
836 t = self.theclass(2, 3, 2)
837 self.assertEqual(t.isoformat(), "0002-03-02")
838
839 def test_ctime(self):
840 t = self.theclass(2002, 3, 2)
841 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
842
843 def test_strftime(self):
844 t = self.theclass(2005, 3, 2)
845 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
Raymond Hettingerf69d9f62003-06-27 08:14:17 +0000846 self.assertEqual(t.strftime(""), "") # SF bug #761337
Tim Peters2a799bf2002-12-16 20:18:38 +0000847
848 self.assertRaises(TypeError, t.strftime) # needs an arg
849 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
850 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
851
852 # A naive object replaces %z and %Z w/ empty strings.
853 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
854
855 def test_resolution_info(self):
856 self.assert_(isinstance(self.theclass.min, self.theclass))
857 self.assert_(isinstance(self.theclass.max, self.theclass))
858 self.assert_(isinstance(self.theclass.resolution, timedelta))
859 self.assert_(self.theclass.max > self.theclass.min)
860
861 def test_extreme_timedelta(self):
862 big = self.theclass.max - self.theclass.min
863 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
864 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
865 # n == 315537897599999999 ~= 2**58.13
866 justasbig = timedelta(0, 0, n)
867 self.assertEqual(big, justasbig)
868 self.assertEqual(self.theclass.min + big, self.theclass.max)
869 self.assertEqual(self.theclass.max - big, self.theclass.min)
870
871 def test_timetuple(self):
872 for i in range(7):
873 # January 2, 1956 is a Monday (0)
874 d = self.theclass(1956, 1, 2+i)
875 t = d.timetuple()
876 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
877 # February 1, 1956 is a Wednesday (2)
878 d = self.theclass(1956, 2, 1+i)
879 t = d.timetuple()
880 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
881 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
882 # of the year.
883 d = self.theclass(1956, 3, 1+i)
884 t = d.timetuple()
885 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
886 self.assertEqual(t.tm_year, 1956)
887 self.assertEqual(t.tm_mon, 3)
888 self.assertEqual(t.tm_mday, 1+i)
889 self.assertEqual(t.tm_hour, 0)
890 self.assertEqual(t.tm_min, 0)
891 self.assertEqual(t.tm_sec, 0)
892 self.assertEqual(t.tm_wday, (3+i)%7)
893 self.assertEqual(t.tm_yday, 61+i)
894 self.assertEqual(t.tm_isdst, -1)
895
896 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000897 args = 6, 7, 23
898 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000899 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +0000900 green = pickler.dumps(orig, proto)
901 derived = unpickler.loads(green)
902 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +0000903
904 def test_compare(self):
905 t1 = self.theclass(2, 3, 4)
906 t2 = self.theclass(2, 3, 4)
907 self.failUnless(t1 == t2)
908 self.failUnless(t1 <= t2)
909 self.failUnless(t1 >= t2)
910 self.failUnless(not t1 != t2)
911 self.failUnless(not t1 < t2)
912 self.failUnless(not t1 > t2)
913 self.assertEqual(cmp(t1, t2), 0)
914 self.assertEqual(cmp(t2, t1), 0)
915
916 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
917 t2 = self.theclass(*args) # this is larger than t1
918 self.failUnless(t1 < t2)
919 self.failUnless(t2 > t1)
920 self.failUnless(t1 <= t2)
921 self.failUnless(t2 >= t1)
922 self.failUnless(t1 != t2)
923 self.failUnless(t2 != t1)
924 self.failUnless(not t1 == t2)
925 self.failUnless(not 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.assertEqual(cmp(t1, t2), -1)
931 self.assertEqual(cmp(t2, t1), 1)
932
Tim Peters68124bb2003-02-08 03:46:31 +0000933 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +0000934 self.assertEqual(t1 == badarg, False)
935 self.assertEqual(t1 != badarg, True)
936 self.assertEqual(badarg == t1, False)
937 self.assertEqual(badarg != t1, True)
938
Tim Peters2a799bf2002-12-16 20:18:38 +0000939 self.assertRaises(TypeError, lambda: t1 < badarg)
940 self.assertRaises(TypeError, lambda: t1 > badarg)
941 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +0000942 self.assertRaises(TypeError, lambda: badarg <= t1)
943 self.assertRaises(TypeError, lambda: badarg < t1)
944 self.assertRaises(TypeError, lambda: badarg > t1)
945 self.assertRaises(TypeError, lambda: badarg >= t1)
946
Tim Peters8d81a012003-01-24 22:36:34 +0000947 def test_mixed_compare(self):
948 our = self.theclass(2000, 4, 5)
949 self.assertRaises(TypeError, cmp, our, 1)
950 self.assertRaises(TypeError, cmp, 1, our)
951
952 class AnotherDateTimeClass(object):
953 def __cmp__(self, other):
954 # Return "equal" so calling this can't be confused with
955 # compare-by-address (which never says "equal" for distinct
956 # objects).
957 return 0
958
959 # This still errors, because date and datetime comparison raise
960 # TypeError instead of NotImplemented when they don't know what to
961 # do, in order to stop comparison from falling back to the default
962 # compare-by-address.
963 their = AnotherDateTimeClass()
964 self.assertRaises(TypeError, cmp, our, their)
965 # Oops: The next stab raises TypeError in the C implementation,
966 # but not in the Python implementation of datetime. The difference
967 # is due to that the Python implementation defines __cmp__ but
968 # the C implementation defines tp_richcompare. This is more pain
969 # to fix than it's worth, so commenting out the test.
970 # self.assertEqual(cmp(their, our), 0)
971
972 # But date and datetime comparison return NotImplemented instead if the
973 # other object has a timetuple attr. This gives the other object a
974 # chance to do the comparison.
975 class Comparable(AnotherDateTimeClass):
976 def timetuple(self):
977 return ()
978
979 their = Comparable()
980 self.assertEqual(cmp(our, their), 0)
981 self.assertEqual(cmp(their, our), 0)
982 self.failUnless(our == their)
983 self.failUnless(their == our)
984
Tim Peters2a799bf2002-12-16 20:18:38 +0000985 def test_bool(self):
986 # All dates are considered true.
987 self.failUnless(self.theclass.min)
988 self.failUnless(self.theclass.max)
989
Tim Petersd6844152002-12-22 20:58:42 +0000990 def test_srftime_out_of_range(self):
991 # For nasty technical reasons, we can't handle years before 1900.
992 cls = self.theclass
993 self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900")
994 for y in 1, 49, 51, 99, 100, 1000, 1899:
995 self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y")
Tim Peters12bf3392002-12-24 05:41:27 +0000996
997 def test_replace(self):
998 cls = self.theclass
999 args = [1, 2, 3]
1000 base = cls(*args)
1001 self.assertEqual(base, base.replace())
1002
1003 i = 0
1004 for name, newval in (("year", 2),
1005 ("month", 3),
1006 ("day", 4)):
1007 newargs = args[:]
1008 newargs[i] = newval
1009 expected = cls(*newargs)
1010 got = base.replace(**{name: newval})
1011 self.assertEqual(expected, got)
1012 i += 1
1013
1014 # Out of bounds.
1015 base = cls(2000, 2, 29)
1016 self.assertRaises(ValueError, base.replace, year=2001)
1017
Tim Petersa98924a2003-05-17 05:55:19 +00001018 def test_subclass_date(self):
1019
1020 class C(self.theclass):
1021 theAnswer = 42
1022
1023 def __new__(cls, *args, **kws):
1024 temp = kws.copy()
1025 extra = temp.pop('extra')
1026 result = self.theclass.__new__(cls, *args, **temp)
1027 result.extra = extra
1028 return result
1029
1030 def newmeth(self, start):
1031 return start + self.year + self.month
1032
1033 args = 2003, 4, 14
1034
1035 dt1 = self.theclass(*args)
1036 dt2 = C(*args, **{'extra': 7})
1037
1038 self.assertEqual(dt2.__class__, C)
1039 self.assertEqual(dt2.theAnswer, 42)
1040 self.assertEqual(dt2.extra, 7)
1041 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1042 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1043
Tim Peters604c0132004-06-07 23:04:33 +00001044 def test_pickling_subclass_date(self):
1045
1046 args = 6, 7, 23
1047 orig = SubclassDate(*args)
1048 for pickler, unpickler, proto in pickle_choices:
1049 green = pickler.dumps(orig, proto)
1050 derived = unpickler.loads(green)
1051 self.assertEqual(orig, derived)
1052
Tim Peters3f606292004-03-21 23:38:41 +00001053 def test_backdoor_resistance(self):
1054 # For fast unpickling, the constructor accepts a pickle string.
1055 # This is a low-overhead backdoor. A user can (by intent or
1056 # mistake) pass a string directly, which (if it's the right length)
1057 # will get treated like a pickle, and bypass the normal sanity
1058 # checks in the constructor. This can create insane objects.
1059 # The constructor doesn't want to burn the time to validate all
1060 # fields, but does check the month field. This stops, e.g.,
1061 # datetime.datetime('1995-03-25') from yielding an insane object.
1062 base = '1995-03-25'
1063 if not issubclass(self.theclass, datetime):
1064 base = base[:4]
1065 for month_byte in '9', chr(0), chr(13), '\xff':
1066 self.assertRaises(TypeError, self.theclass,
1067 base[:2] + month_byte + base[3:])
1068 for ord_byte in range(1, 13):
1069 # This shouldn't blow up because of the month byte alone. If
1070 # the implementation changes to do more-careful checking, it may
1071 # blow up because other fields are insane.
1072 self.theclass(base[:2] + chr(ord_byte) + base[3:])
Tim Peterseb1a4962003-05-17 02:25:20 +00001073
Tim Peters2a799bf2002-12-16 20:18:38 +00001074#############################################################################
1075# datetime tests
1076
Tim Peters604c0132004-06-07 23:04:33 +00001077class SubclassDatetime(datetime):
1078 sub_var = 1
1079
Tim Peters2a799bf2002-12-16 20:18:38 +00001080class TestDateTime(TestDate):
1081
1082 theclass = datetime
1083
1084 def test_basic_attributes(self):
1085 dt = self.theclass(2002, 3, 1, 12, 0)
1086 self.assertEqual(dt.year, 2002)
1087 self.assertEqual(dt.month, 3)
1088 self.assertEqual(dt.day, 1)
1089 self.assertEqual(dt.hour, 12)
1090 self.assertEqual(dt.minute, 0)
1091 self.assertEqual(dt.second, 0)
1092 self.assertEqual(dt.microsecond, 0)
1093
1094 def test_basic_attributes_nonzero(self):
1095 # Make sure all attributes are non-zero so bugs in
1096 # bit-shifting access show up.
1097 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1098 self.assertEqual(dt.year, 2002)
1099 self.assertEqual(dt.month, 3)
1100 self.assertEqual(dt.day, 1)
1101 self.assertEqual(dt.hour, 12)
1102 self.assertEqual(dt.minute, 59)
1103 self.assertEqual(dt.second, 59)
1104 self.assertEqual(dt.microsecond, 8000)
1105
1106 def test_roundtrip(self):
1107 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1108 self.theclass.now()):
1109 # Verify dt -> string -> datetime identity.
1110 s = repr(dt)
1111 self.failUnless(s.startswith('datetime.'))
1112 s = s[9:]
1113 dt2 = eval(s)
1114 self.assertEqual(dt, dt2)
1115
1116 # Verify identity via reconstructing from pieces.
1117 dt2 = self.theclass(dt.year, dt.month, dt.day,
1118 dt.hour, dt.minute, dt.second,
1119 dt.microsecond)
1120 self.assertEqual(dt, dt2)
1121
1122 def test_isoformat(self):
1123 t = self.theclass(2, 3, 2, 4, 5, 1, 123)
1124 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123")
1125 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
1126 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
1127 # str is ISO format with the separator forced to a blank.
1128 self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
1129
1130 t = self.theclass(2, 3, 2)
1131 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1132 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1133 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1134 # str is ISO format with the separator forced to a blank.
1135 self.assertEqual(str(t), "0002-03-02 00:00:00")
1136
1137 def test_more_ctime(self):
1138 # Test fields that TestDate doesn't touch.
1139 import time
1140
1141 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1142 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1143 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1144 # out. The difference is that t.ctime() produces " 2" for the day,
1145 # but platform ctime() produces "02" for the day. According to
1146 # C99, t.ctime() is correct here.
1147 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1148
1149 # So test a case where that difference doesn't matter.
1150 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1151 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1152
1153 def test_tz_independent_comparing(self):
1154 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1155 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1156 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1157 self.assertEqual(dt1, dt3)
1158 self.assert_(dt2 > dt3)
1159
1160 # Make sure comparison doesn't forget microseconds, and isn't done
1161 # via comparing a float timestamp (an IEEE double doesn't have enough
1162 # precision to span microsecond resolution across years 1 thru 9999,
1163 # so comparing via timestamp necessarily calls some distinct values
1164 # equal).
1165 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1166 us = timedelta(microseconds=1)
1167 dt2 = dt1 + us
1168 self.assertEqual(dt2 - dt1, us)
1169 self.assert_(dt1 < dt2)
1170
1171 def test_bad_constructor_arguments(self):
1172 # bad years
1173 self.theclass(MINYEAR, 1, 1) # no exception
1174 self.theclass(MAXYEAR, 1, 1) # no exception
1175 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1176 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1177 # bad months
1178 self.theclass(2000, 1, 1) # no exception
1179 self.theclass(2000, 12, 1) # no exception
1180 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1181 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1182 # bad days
1183 self.theclass(2000, 2, 29) # no exception
1184 self.theclass(2004, 2, 29) # no exception
1185 self.theclass(2400, 2, 29) # no exception
1186 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1187 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1188 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1189 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1190 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1191 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1192 # bad hours
1193 self.theclass(2000, 1, 31, 0) # no exception
1194 self.theclass(2000, 1, 31, 23) # no exception
1195 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1196 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1197 # bad minutes
1198 self.theclass(2000, 1, 31, 23, 0) # no exception
1199 self.theclass(2000, 1, 31, 23, 59) # no exception
1200 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1201 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1202 # bad seconds
1203 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1204 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1205 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1206 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1207 # bad microseconds
1208 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1209 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1210 self.assertRaises(ValueError, self.theclass,
1211 2000, 1, 31, 23, 59, 59, -1)
1212 self.assertRaises(ValueError, self.theclass,
1213 2000, 1, 31, 23, 59, 59,
1214 1000000)
1215
1216 def test_hash_equality(self):
1217 d = self.theclass(2000, 12, 31, 23, 30, 17)
1218 e = self.theclass(2000, 12, 31, 23, 30, 17)
1219 self.assertEqual(d, e)
1220 self.assertEqual(hash(d), hash(e))
1221
1222 dic = {d: 1}
1223 dic[e] = 2
1224 self.assertEqual(len(dic), 1)
1225 self.assertEqual(dic[d], 2)
1226 self.assertEqual(dic[e], 2)
1227
1228 d = self.theclass(2001, 1, 1, 0, 5, 17)
1229 e = self.theclass(2001, 1, 1, 0, 5, 17)
1230 self.assertEqual(d, e)
1231 self.assertEqual(hash(d), hash(e))
1232
1233 dic = {d: 1}
1234 dic[e] = 2
1235 self.assertEqual(len(dic), 1)
1236 self.assertEqual(dic[d], 2)
1237 self.assertEqual(dic[e], 2)
1238
1239 def test_computations(self):
1240 a = self.theclass(2002, 1, 31)
1241 b = self.theclass(1956, 1, 31)
1242 diff = a-b
1243 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1244 self.assertEqual(diff.seconds, 0)
1245 self.assertEqual(diff.microseconds, 0)
1246 a = self.theclass(2002, 3, 2, 17, 6)
1247 millisec = timedelta(0, 0, 1000)
1248 hour = timedelta(0, 3600)
1249 day = timedelta(1)
1250 week = timedelta(7)
1251 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1252 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1253 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1254 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1255 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1256 self.assertEqual(a - hour, a + -hour)
1257 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1258 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1259 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1260 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1261 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1262 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1263 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1264 self.assertEqual((a + week) - a, week)
1265 self.assertEqual((a + day) - a, day)
1266 self.assertEqual((a + hour) - a, hour)
1267 self.assertEqual((a + millisec) - a, millisec)
1268 self.assertEqual((a - week) - a, -week)
1269 self.assertEqual((a - day) - a, -day)
1270 self.assertEqual((a - hour) - a, -hour)
1271 self.assertEqual((a - millisec) - a, -millisec)
1272 self.assertEqual(a - (a + week), -week)
1273 self.assertEqual(a - (a + day), -day)
1274 self.assertEqual(a - (a + hour), -hour)
1275 self.assertEqual(a - (a + millisec), -millisec)
1276 self.assertEqual(a - (a - week), week)
1277 self.assertEqual(a - (a - day), day)
1278 self.assertEqual(a - (a - hour), hour)
1279 self.assertEqual(a - (a - millisec), millisec)
1280 self.assertEqual(a + (week + day + hour + millisec),
1281 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1282 self.assertEqual(a + (week + day + hour + millisec),
1283 (((a + week) + day) + hour) + millisec)
1284 self.assertEqual(a - (week + day + hour + millisec),
1285 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1286 self.assertEqual(a - (week + day + hour + millisec),
1287 (((a - week) - day) - hour) - millisec)
1288 # Add/sub ints, longs, floats should be illegal
1289 for i in 1, 1L, 1.0:
1290 self.assertRaises(TypeError, lambda: a+i)
1291 self.assertRaises(TypeError, lambda: a-i)
1292 self.assertRaises(TypeError, lambda: i+a)
1293 self.assertRaises(TypeError, lambda: i-a)
1294
1295 # delta - datetime is senseless.
1296 self.assertRaises(TypeError, lambda: day - a)
1297 # mixing datetime and (delta or datetime) via * or // is senseless
1298 self.assertRaises(TypeError, lambda: day * a)
1299 self.assertRaises(TypeError, lambda: a * day)
1300 self.assertRaises(TypeError, lambda: day // a)
1301 self.assertRaises(TypeError, lambda: a // day)
1302 self.assertRaises(TypeError, lambda: a * a)
1303 self.assertRaises(TypeError, lambda: a // a)
1304 # datetime + datetime is senseless
1305 self.assertRaises(TypeError, lambda: a + a)
1306
1307 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001308 args = 6, 7, 23, 20, 59, 1, 64**2
1309 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001310 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001311 green = pickler.dumps(orig, proto)
1312 derived = unpickler.loads(green)
1313 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001314
Guido van Rossum275666f2003-02-07 21:49:01 +00001315 def test_more_pickling(self):
1316 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
1317 s = pickle.dumps(a)
1318 b = pickle.loads(s)
1319 self.assertEqual(b.year, 2003)
1320 self.assertEqual(b.month, 2)
1321 self.assertEqual(b.day, 7)
1322
Tim Peters604c0132004-06-07 23:04:33 +00001323 def test_pickling_subclass_datetime(self):
1324 args = 6, 7, 23, 20, 59, 1, 64**2
1325 orig = SubclassDatetime(*args)
1326 for pickler, unpickler, proto in pickle_choices:
1327 green = pickler.dumps(orig, proto)
1328 derived = unpickler.loads(green)
1329 self.assertEqual(orig, derived)
1330
Tim Peters2a799bf2002-12-16 20:18:38 +00001331 def test_more_compare(self):
1332 # The test_compare() inherited from TestDate covers the error cases.
1333 # We just want to test lexicographic ordering on the members datetime
1334 # has that date lacks.
1335 args = [2000, 11, 29, 20, 58, 16, 999998]
1336 t1 = self.theclass(*args)
1337 t2 = self.theclass(*args)
1338 self.failUnless(t1 == t2)
1339 self.failUnless(t1 <= t2)
1340 self.failUnless(t1 >= t2)
1341 self.failUnless(not t1 != t2)
1342 self.failUnless(not t1 < t2)
1343 self.failUnless(not t1 > t2)
1344 self.assertEqual(cmp(t1, t2), 0)
1345 self.assertEqual(cmp(t2, t1), 0)
1346
1347 for i in range(len(args)):
1348 newargs = args[:]
1349 newargs[i] = args[i] + 1
1350 t2 = self.theclass(*newargs) # this is larger than t1
1351 self.failUnless(t1 < t2)
1352 self.failUnless(t2 > t1)
1353 self.failUnless(t1 <= t2)
1354 self.failUnless(t2 >= t1)
1355 self.failUnless(t1 != t2)
1356 self.failUnless(t2 != t1)
1357 self.failUnless(not t1 == t2)
1358 self.failUnless(not t2 == t1)
1359 self.failUnless(not t1 > t2)
1360 self.failUnless(not t2 < t1)
1361 self.failUnless(not t1 >= t2)
1362 self.failUnless(not t2 <= t1)
1363 self.assertEqual(cmp(t1, t2), -1)
1364 self.assertEqual(cmp(t2, t1), 1)
1365
1366
1367 # A helper for timestamp constructor tests.
1368 def verify_field_equality(self, expected, got):
1369 self.assertEqual(expected.tm_year, got.year)
1370 self.assertEqual(expected.tm_mon, got.month)
1371 self.assertEqual(expected.tm_mday, got.day)
1372 self.assertEqual(expected.tm_hour, got.hour)
1373 self.assertEqual(expected.tm_min, got.minute)
1374 self.assertEqual(expected.tm_sec, got.second)
1375
1376 def test_fromtimestamp(self):
1377 import time
1378
1379 ts = time.time()
1380 expected = time.localtime(ts)
1381 got = self.theclass.fromtimestamp(ts)
1382 self.verify_field_equality(expected, got)
1383
1384 def test_utcfromtimestamp(self):
1385 import time
1386
1387 ts = time.time()
1388 expected = time.gmtime(ts)
1389 got = self.theclass.utcfromtimestamp(ts)
1390 self.verify_field_equality(expected, got)
1391
Tim Peters1b6f7a92004-06-20 02:50:16 +00001392 def test_insane_fromtimestamp(self):
1393 # It's possible that some platform maps time_t to double,
1394 # and that this test will fail there. This test should
1395 # exempt such platforms (provided they return reasonable
1396 # results!).
1397 for insane in -1e200, 1e200:
1398 self.assertRaises(ValueError, self.theclass.fromtimestamp,
1399 insane)
1400
1401 def test_insane_utcfromtimestamp(self):
1402 # It's possible that some platform maps time_t to double,
1403 # and that this test will fail there. This test should
1404 # exempt such platforms (provided they return reasonable
1405 # results!).
1406 for insane in -1e200, 1e200:
1407 self.assertRaises(ValueError, self.theclass.utcfromtimestamp,
1408 insane)
1409
Tim Peters2a799bf2002-12-16 20:18:38 +00001410 def test_utcnow(self):
1411 import time
1412
1413 # Call it a success if utcnow() and utcfromtimestamp() are within
1414 # a second of each other.
1415 tolerance = timedelta(seconds=1)
1416 for dummy in range(3):
1417 from_now = self.theclass.utcnow()
1418 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1419 if abs(from_timestamp - from_now) <= tolerance:
1420 break
1421 # Else try again a few times.
1422 self.failUnless(abs(from_timestamp - from_now) <= tolerance)
1423
Skip Montanaro0af3ade2005-01-13 04:12:31 +00001424 def test_strptime(self):
1425 import time
1426
1427 string = '2004-12-01 13:02:47'
1428 format = '%Y-%m-%d %H:%M:%S'
1429 expected = self.theclass(*(time.strptime(string, format)[0:6]))
1430 got = self.theclass.strptime(string, format)
1431 self.assertEqual(expected, got)
1432
Tim Peters2a799bf2002-12-16 20:18:38 +00001433 def test_more_timetuple(self):
1434 # This tests fields beyond those tested by the TestDate.test_timetuple.
1435 t = self.theclass(2004, 12, 31, 6, 22, 33)
1436 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1437 self.assertEqual(t.timetuple(),
1438 (t.year, t.month, t.day,
1439 t.hour, t.minute, t.second,
1440 t.weekday(),
1441 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1442 -1))
1443 tt = t.timetuple()
1444 self.assertEqual(tt.tm_year, t.year)
1445 self.assertEqual(tt.tm_mon, t.month)
1446 self.assertEqual(tt.tm_mday, t.day)
1447 self.assertEqual(tt.tm_hour, t.hour)
1448 self.assertEqual(tt.tm_min, t.minute)
1449 self.assertEqual(tt.tm_sec, t.second)
1450 self.assertEqual(tt.tm_wday, t.weekday())
1451 self.assertEqual(tt.tm_yday, t.toordinal() -
1452 date(t.year, 1, 1).toordinal() + 1)
1453 self.assertEqual(tt.tm_isdst, -1)
1454
1455 def test_more_strftime(self):
1456 # This tests fields beyond those tested by the TestDate.test_strftime.
1457 t = self.theclass(2004, 12, 31, 6, 22, 33)
1458 self.assertEqual(t.strftime("%m %d %y %S %M %H %j"),
1459 "12 31 04 33 22 06 366")
1460
1461 def test_extract(self):
1462 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1463 self.assertEqual(dt.date(), date(2002, 3, 4))
1464 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1465
1466 def test_combine(self):
1467 d = date(2002, 3, 4)
1468 t = time(18, 45, 3, 1234)
1469 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1470 combine = self.theclass.combine
1471 dt = combine(d, t)
1472 self.assertEqual(dt, expected)
1473
1474 dt = combine(time=t, date=d)
1475 self.assertEqual(dt, expected)
1476
1477 self.assertEqual(d, dt.date())
1478 self.assertEqual(t, dt.time())
1479 self.assertEqual(dt, combine(dt.date(), dt.time()))
1480
1481 self.assertRaises(TypeError, combine) # need an arg
1482 self.assertRaises(TypeError, combine, d) # need two args
1483 self.assertRaises(TypeError, combine, t, d) # args reversed
1484 self.assertRaises(TypeError, combine, d, t, 1) # too many args
1485 self.assertRaises(TypeError, combine, "date", "time") # wrong types
1486
Tim Peters12bf3392002-12-24 05:41:27 +00001487 def test_replace(self):
1488 cls = self.theclass
1489 args = [1, 2, 3, 4, 5, 6, 7]
1490 base = cls(*args)
1491 self.assertEqual(base, base.replace())
1492
1493 i = 0
1494 for name, newval in (("year", 2),
1495 ("month", 3),
1496 ("day", 4),
1497 ("hour", 5),
1498 ("minute", 6),
1499 ("second", 7),
1500 ("microsecond", 8)):
1501 newargs = args[:]
1502 newargs[i] = newval
1503 expected = cls(*newargs)
1504 got = base.replace(**{name: newval})
1505 self.assertEqual(expected, got)
1506 i += 1
1507
1508 # Out of bounds.
1509 base = cls(2000, 2, 29)
1510 self.assertRaises(ValueError, base.replace, year=2001)
1511
Tim Peters80475bb2002-12-25 07:40:55 +00001512 def test_astimezone(self):
Tim Peters52dcce22003-01-23 16:36:11 +00001513 # Pretty boring! The TZ test is more interesting here. astimezone()
1514 # simply can't be applied to a naive object.
Tim Peters80475bb2002-12-25 07:40:55 +00001515 dt = self.theclass.now()
1516 f = FixedOffset(44, "")
Tim Peters80475bb2002-12-25 07:40:55 +00001517 self.assertRaises(TypeError, dt.astimezone) # not enough args
1518 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
1519 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Tim Peters52dcce22003-01-23 16:36:11 +00001520 self.assertRaises(ValueError, dt.astimezone, f) # naive
1521 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
Tim Peters80475bb2002-12-25 07:40:55 +00001522
Tim Peters52dcce22003-01-23 16:36:11 +00001523 class Bogus(tzinfo):
1524 def utcoffset(self, dt): return None
1525 def dst(self, dt): return timedelta(0)
1526 bog = Bogus()
1527 self.assertRaises(ValueError, dt.astimezone, bog) # naive
1528
1529 class AlsoBogus(tzinfo):
1530 def utcoffset(self, dt): return timedelta(0)
1531 def dst(self, dt): return None
1532 alsobog = AlsoBogus()
1533 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
Tim Peters12bf3392002-12-24 05:41:27 +00001534
Tim Petersa98924a2003-05-17 05:55:19 +00001535 def test_subclass_datetime(self):
1536
1537 class C(self.theclass):
1538 theAnswer = 42
1539
1540 def __new__(cls, *args, **kws):
1541 temp = kws.copy()
1542 extra = temp.pop('extra')
1543 result = self.theclass.__new__(cls, *args, **temp)
1544 result.extra = extra
1545 return result
1546
1547 def newmeth(self, start):
1548 return start + self.year + self.month + self.second
1549
1550 args = 2003, 4, 14, 12, 13, 41
1551
1552 dt1 = self.theclass(*args)
1553 dt2 = C(*args, **{'extra': 7})
1554
1555 self.assertEqual(dt2.__class__, C)
1556 self.assertEqual(dt2.theAnswer, 42)
1557 self.assertEqual(dt2.extra, 7)
1558 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1559 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
1560 dt1.second - 7)
1561
Tim Peters604c0132004-06-07 23:04:33 +00001562class SubclassTime(time):
1563 sub_var = 1
1564
Tim Peters07534a62003-02-07 22:50:28 +00001565class TestTime(HarmlessMixedComparison):
Tim Peters2a799bf2002-12-16 20:18:38 +00001566
1567 theclass = time
1568
1569 def test_basic_attributes(self):
1570 t = self.theclass(12, 0)
1571 self.assertEqual(t.hour, 12)
1572 self.assertEqual(t.minute, 0)
1573 self.assertEqual(t.second, 0)
1574 self.assertEqual(t.microsecond, 0)
1575
1576 def test_basic_attributes_nonzero(self):
1577 # Make sure all attributes are non-zero so bugs in
1578 # bit-shifting access show up.
1579 t = self.theclass(12, 59, 59, 8000)
1580 self.assertEqual(t.hour, 12)
1581 self.assertEqual(t.minute, 59)
1582 self.assertEqual(t.second, 59)
1583 self.assertEqual(t.microsecond, 8000)
1584
1585 def test_roundtrip(self):
1586 t = self.theclass(1, 2, 3, 4)
1587
1588 # Verify t -> string -> time identity.
1589 s = repr(t)
1590 self.failUnless(s.startswith('datetime.'))
1591 s = s[9:]
1592 t2 = eval(s)
1593 self.assertEqual(t, t2)
1594
1595 # Verify identity via reconstructing from pieces.
1596 t2 = self.theclass(t.hour, t.minute, t.second,
1597 t.microsecond)
1598 self.assertEqual(t, t2)
1599
1600 def test_comparing(self):
1601 args = [1, 2, 3, 4]
1602 t1 = self.theclass(*args)
1603 t2 = self.theclass(*args)
1604 self.failUnless(t1 == t2)
1605 self.failUnless(t1 <= t2)
1606 self.failUnless(t1 >= t2)
1607 self.failUnless(not t1 != t2)
1608 self.failUnless(not t1 < t2)
1609 self.failUnless(not t1 > t2)
1610 self.assertEqual(cmp(t1, t2), 0)
1611 self.assertEqual(cmp(t2, t1), 0)
1612
1613 for i in range(len(args)):
1614 newargs = args[:]
1615 newargs[i] = args[i] + 1
1616 t2 = self.theclass(*newargs) # this is larger than t1
1617 self.failUnless(t1 < t2)
1618 self.failUnless(t2 > t1)
1619 self.failUnless(t1 <= t2)
1620 self.failUnless(t2 >= t1)
1621 self.failUnless(t1 != t2)
1622 self.failUnless(t2 != t1)
1623 self.failUnless(not t1 == t2)
1624 self.failUnless(not t2 == t1)
1625 self.failUnless(not t1 > t2)
1626 self.failUnless(not t2 < t1)
1627 self.failUnless(not t1 >= t2)
1628 self.failUnless(not t2 <= t1)
1629 self.assertEqual(cmp(t1, t2), -1)
1630 self.assertEqual(cmp(t2, t1), 1)
1631
Tim Peters68124bb2003-02-08 03:46:31 +00001632 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +00001633 self.assertEqual(t1 == badarg, False)
1634 self.assertEqual(t1 != badarg, True)
1635 self.assertEqual(badarg == t1, False)
1636 self.assertEqual(badarg != t1, True)
1637
Tim Peters2a799bf2002-12-16 20:18:38 +00001638 self.assertRaises(TypeError, lambda: t1 <= badarg)
1639 self.assertRaises(TypeError, lambda: t1 < badarg)
1640 self.assertRaises(TypeError, lambda: t1 > badarg)
1641 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +00001642 self.assertRaises(TypeError, lambda: badarg <= t1)
1643 self.assertRaises(TypeError, lambda: badarg < t1)
1644 self.assertRaises(TypeError, lambda: badarg > t1)
1645 self.assertRaises(TypeError, lambda: badarg >= t1)
1646
1647 def test_bad_constructor_arguments(self):
1648 # bad hours
1649 self.theclass(0, 0) # no exception
1650 self.theclass(23, 0) # no exception
1651 self.assertRaises(ValueError, self.theclass, -1, 0)
1652 self.assertRaises(ValueError, self.theclass, 24, 0)
1653 # bad minutes
1654 self.theclass(23, 0) # no exception
1655 self.theclass(23, 59) # no exception
1656 self.assertRaises(ValueError, self.theclass, 23, -1)
1657 self.assertRaises(ValueError, self.theclass, 23, 60)
1658 # bad seconds
1659 self.theclass(23, 59, 0) # no exception
1660 self.theclass(23, 59, 59) # no exception
1661 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
1662 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
1663 # bad microseconds
1664 self.theclass(23, 59, 59, 0) # no exception
1665 self.theclass(23, 59, 59, 999999) # no exception
1666 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
1667 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
1668
1669 def test_hash_equality(self):
1670 d = self.theclass(23, 30, 17)
1671 e = self.theclass(23, 30, 17)
1672 self.assertEqual(d, e)
1673 self.assertEqual(hash(d), hash(e))
1674
1675 dic = {d: 1}
1676 dic[e] = 2
1677 self.assertEqual(len(dic), 1)
1678 self.assertEqual(dic[d], 2)
1679 self.assertEqual(dic[e], 2)
1680
1681 d = self.theclass(0, 5, 17)
1682 e = self.theclass(0, 5, 17)
1683 self.assertEqual(d, e)
1684 self.assertEqual(hash(d), hash(e))
1685
1686 dic = {d: 1}
1687 dic[e] = 2
1688 self.assertEqual(len(dic), 1)
1689 self.assertEqual(dic[d], 2)
1690 self.assertEqual(dic[e], 2)
1691
1692 def test_isoformat(self):
1693 t = self.theclass(4, 5, 1, 123)
1694 self.assertEqual(t.isoformat(), "04:05:01.000123")
1695 self.assertEqual(t.isoformat(), str(t))
1696
1697 t = self.theclass()
1698 self.assertEqual(t.isoformat(), "00:00:00")
1699 self.assertEqual(t.isoformat(), str(t))
1700
1701 t = self.theclass(microsecond=1)
1702 self.assertEqual(t.isoformat(), "00:00:00.000001")
1703 self.assertEqual(t.isoformat(), str(t))
1704
1705 t = self.theclass(microsecond=10)
1706 self.assertEqual(t.isoformat(), "00:00:00.000010")
1707 self.assertEqual(t.isoformat(), str(t))
1708
1709 t = self.theclass(microsecond=100)
1710 self.assertEqual(t.isoformat(), "00:00:00.000100")
1711 self.assertEqual(t.isoformat(), str(t))
1712
1713 t = self.theclass(microsecond=1000)
1714 self.assertEqual(t.isoformat(), "00:00:00.001000")
1715 self.assertEqual(t.isoformat(), str(t))
1716
1717 t = self.theclass(microsecond=10000)
1718 self.assertEqual(t.isoformat(), "00:00:00.010000")
1719 self.assertEqual(t.isoformat(), str(t))
1720
1721 t = self.theclass(microsecond=100000)
1722 self.assertEqual(t.isoformat(), "00:00:00.100000")
1723 self.assertEqual(t.isoformat(), str(t))
1724
1725 def test_strftime(self):
1726 t = self.theclass(1, 2, 3, 4)
1727 self.assertEqual(t.strftime('%H %M %S'), "01 02 03")
1728 # A naive object replaces %z and %Z with empty strings.
1729 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1730
1731 def test_str(self):
1732 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
1733 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
1734 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
1735 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
1736 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
1737
1738 def test_repr(self):
1739 name = 'datetime.' + self.theclass.__name__
1740 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
1741 "%s(1, 2, 3, 4)" % name)
1742 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
1743 "%s(10, 2, 3, 4000)" % name)
1744 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
1745 "%s(0, 2, 3, 400000)" % name)
1746 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
1747 "%s(12, 2, 3)" % name)
1748 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
1749 "%s(23, 15)" % name)
1750
1751 def test_resolution_info(self):
1752 self.assert_(isinstance(self.theclass.min, self.theclass))
1753 self.assert_(isinstance(self.theclass.max, self.theclass))
1754 self.assert_(isinstance(self.theclass.resolution, timedelta))
1755 self.assert_(self.theclass.max > self.theclass.min)
1756
1757 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001758 args = 20, 59, 16, 64**2
1759 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001760 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001761 green = pickler.dumps(orig, proto)
1762 derived = unpickler.loads(green)
1763 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001764
Tim Peters604c0132004-06-07 23:04:33 +00001765 def test_pickling_subclass_time(self):
1766 args = 20, 59, 16, 64**2
1767 orig = SubclassTime(*args)
1768 for pickler, unpickler, proto in pickle_choices:
1769 green = pickler.dumps(orig, proto)
1770 derived = unpickler.loads(green)
1771 self.assertEqual(orig, derived)
1772
Tim Peters2a799bf2002-12-16 20:18:38 +00001773 def test_bool(self):
1774 cls = self.theclass
1775 self.failUnless(cls(1))
1776 self.failUnless(cls(0, 1))
1777 self.failUnless(cls(0, 0, 1))
1778 self.failUnless(cls(0, 0, 0, 1))
1779 self.failUnless(not cls(0))
1780 self.failUnless(not cls())
1781
Tim Peters12bf3392002-12-24 05:41:27 +00001782 def test_replace(self):
1783 cls = self.theclass
1784 args = [1, 2, 3, 4]
1785 base = cls(*args)
1786 self.assertEqual(base, base.replace())
1787
1788 i = 0
1789 for name, newval in (("hour", 5),
1790 ("minute", 6),
1791 ("second", 7),
1792 ("microsecond", 8)):
1793 newargs = args[:]
1794 newargs[i] = newval
1795 expected = cls(*newargs)
1796 got = base.replace(**{name: newval})
1797 self.assertEqual(expected, got)
1798 i += 1
1799
1800 # Out of bounds.
1801 base = cls(1)
1802 self.assertRaises(ValueError, base.replace, hour=24)
1803 self.assertRaises(ValueError, base.replace, minute=-1)
1804 self.assertRaises(ValueError, base.replace, second=100)
1805 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1806
Tim Petersa98924a2003-05-17 05:55:19 +00001807 def test_subclass_time(self):
1808
1809 class C(self.theclass):
1810 theAnswer = 42
1811
1812 def __new__(cls, *args, **kws):
1813 temp = kws.copy()
1814 extra = temp.pop('extra')
1815 result = self.theclass.__new__(cls, *args, **temp)
1816 result.extra = extra
1817 return result
1818
1819 def newmeth(self, start):
1820 return start + self.hour + self.second
1821
1822 args = 4, 5, 6
1823
1824 dt1 = self.theclass(*args)
1825 dt2 = C(*args, **{'extra': 7})
1826
1827 self.assertEqual(dt2.__class__, C)
1828 self.assertEqual(dt2.theAnswer, 42)
1829 self.assertEqual(dt2.extra, 7)
1830 self.assertEqual(dt1.isoformat(), dt2.isoformat())
1831 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
1832
Tim Peters855fe882002-12-22 03:43:39 +00001833# A mixin for classes with a tzinfo= argument. Subclasses must define
1834# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001835# must be legit (which is true for time and datetime).
Tim Peters855fe882002-12-22 03:43:39 +00001836class TZInfoBase(unittest.TestCase):
Tim Peters2a799bf2002-12-16 20:18:38 +00001837
Tim Petersbad8ff02002-12-30 20:52:32 +00001838 def test_argument_passing(self):
1839 cls = self.theclass
Tim Peters0bf60bd2003-01-08 20:40:01 +00001840 # A datetime passes itself on, a time passes None.
Tim Petersbad8ff02002-12-30 20:52:32 +00001841 class introspective(tzinfo):
1842 def tzname(self, dt): return dt and "real" or "none"
Tim Peters397301e2003-01-02 21:28:08 +00001843 def utcoffset(self, dt):
1844 return timedelta(minutes = dt and 42 or -42)
Tim Petersbad8ff02002-12-30 20:52:32 +00001845 dst = utcoffset
1846
1847 obj = cls(1, 2, 3, tzinfo=introspective())
1848
Tim Peters0bf60bd2003-01-08 20:40:01 +00001849 expected = cls is time and "none" or "real"
Tim Petersbad8ff02002-12-30 20:52:32 +00001850 self.assertEqual(obj.tzname(), expected)
1851
Tim Peters0bf60bd2003-01-08 20:40:01 +00001852 expected = timedelta(minutes=(cls is time and -42 or 42))
Tim Petersbad8ff02002-12-30 20:52:32 +00001853 self.assertEqual(obj.utcoffset(), expected)
1854 self.assertEqual(obj.dst(), expected)
1855
Tim Peters855fe882002-12-22 03:43:39 +00001856 def test_bad_tzinfo_classes(self):
1857 cls = self.theclass
1858 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
Tim Peters2a799bf2002-12-16 20:18:38 +00001859
Tim Peters855fe882002-12-22 03:43:39 +00001860 class NiceTry(object):
1861 def __init__(self): pass
1862 def utcoffset(self, dt): pass
1863 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
1864
1865 class BetterTry(tzinfo):
1866 def __init__(self): pass
1867 def utcoffset(self, dt): pass
1868 b = BetterTry()
1869 t = cls(1, 1, 1, tzinfo=b)
1870 self.failUnless(t.tzinfo is b)
1871
1872 def test_utc_offset_out_of_bounds(self):
1873 class Edgy(tzinfo):
1874 def __init__(self, offset):
Tim Peters397301e2003-01-02 21:28:08 +00001875 self.offset = timedelta(minutes=offset)
Tim Peters855fe882002-12-22 03:43:39 +00001876 def utcoffset(self, dt):
1877 return self.offset
1878
1879 cls = self.theclass
1880 for offset, legit in ((-1440, False),
1881 (-1439, True),
1882 (1439, True),
1883 (1440, False)):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001884 if cls is time:
Tim Peters855fe882002-12-22 03:43:39 +00001885 t = cls(1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001886 elif cls is datetime:
Tim Peters855fe882002-12-22 03:43:39 +00001887 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001888 else:
1889 assert 0, "impossible"
Tim Peters855fe882002-12-22 03:43:39 +00001890 if legit:
1891 aofs = abs(offset)
1892 h, m = divmod(aofs, 60)
1893 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001894 if isinstance(t, datetime):
Tim Peters855fe882002-12-22 03:43:39 +00001895 t = t.timetz()
1896 self.assertEqual(str(t), "01:02:03" + tag)
1897 else:
1898 self.assertRaises(ValueError, str, t)
1899
1900 def test_tzinfo_classes(self):
1901 cls = self.theclass
1902 class C1(tzinfo):
1903 def utcoffset(self, dt): return None
1904 def dst(self, dt): return None
1905 def tzname(self, dt): return None
1906 for t in (cls(1, 1, 1),
1907 cls(1, 1, 1, tzinfo=None),
1908 cls(1, 1, 1, tzinfo=C1())):
1909 self.failUnless(t.utcoffset() is None)
1910 self.failUnless(t.dst() is None)
1911 self.failUnless(t.tzname() is None)
1912
Tim Peters855fe882002-12-22 03:43:39 +00001913 class C3(tzinfo):
1914 def utcoffset(self, dt): return timedelta(minutes=-1439)
1915 def dst(self, dt): return timedelta(minutes=1439)
1916 def tzname(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001917 t = cls(1, 1, 1, tzinfo=C3())
1918 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
1919 self.assertEqual(t.dst(), timedelta(minutes=1439))
1920 self.assertEqual(t.tzname(), "aname")
Tim Peters855fe882002-12-22 03:43:39 +00001921
1922 # Wrong types.
1923 class C4(tzinfo):
1924 def utcoffset(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001925 def dst(self, dt): return 7
Tim Peters855fe882002-12-22 03:43:39 +00001926 def tzname(self, dt): return 0
1927 t = cls(1, 1, 1, tzinfo=C4())
1928 self.assertRaises(TypeError, t.utcoffset)
1929 self.assertRaises(TypeError, t.dst)
1930 self.assertRaises(TypeError, t.tzname)
1931
1932 # Offset out of range.
Tim Peters855fe882002-12-22 03:43:39 +00001933 class C6(tzinfo):
1934 def utcoffset(self, dt): return timedelta(hours=-24)
1935 def dst(self, dt): return timedelta(hours=24)
Tim Peters397301e2003-01-02 21:28:08 +00001936 t = cls(1, 1, 1, tzinfo=C6())
1937 self.assertRaises(ValueError, t.utcoffset)
1938 self.assertRaises(ValueError, t.dst)
Tim Peters855fe882002-12-22 03:43:39 +00001939
1940 # Not a whole number of minutes.
1941 class C7(tzinfo):
1942 def utcoffset(self, dt): return timedelta(seconds=61)
1943 def dst(self, dt): return timedelta(microseconds=-81)
1944 t = cls(1, 1, 1, tzinfo=C7())
1945 self.assertRaises(ValueError, t.utcoffset)
1946 self.assertRaises(ValueError, t.dst)
1947
Tim Peters4c0db782002-12-26 05:01:19 +00001948 def test_aware_compare(self):
1949 cls = self.theclass
1950
Tim Peters60c76e42002-12-27 00:41:11 +00001951 # Ensure that utcoffset() gets ignored if the comparands have
1952 # the same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00001953 class OperandDependentOffset(tzinfo):
1954 def utcoffset(self, t):
1955 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00001956 # d0 and d1 equal after adjustment
1957 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00001958 else:
Tim Peters397301e2003-01-02 21:28:08 +00001959 # d2 off in the weeds
1960 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00001961
1962 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
1963 d0 = base.replace(minute=3)
1964 d1 = base.replace(minute=9)
1965 d2 = base.replace(minute=11)
1966 for x in d0, d1, d2:
1967 for y in d0, d1, d2:
1968 got = cmp(x, y)
Tim Peters60c76e42002-12-27 00:41:11 +00001969 expected = cmp(x.minute, y.minute)
1970 self.assertEqual(got, expected)
1971
1972 # However, if they're different members, uctoffset is not ignored.
Tim Peters0bf60bd2003-01-08 20:40:01 +00001973 # Note that a time can't actually have an operand-depedent offset,
1974 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
1975 # so skip this test for time.
1976 if cls is not time:
Tim Petersbad8ff02002-12-30 20:52:32 +00001977 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
1978 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
1979 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
1980 for x in d0, d1, d2:
1981 for y in d0, d1, d2:
1982 got = cmp(x, y)
1983 if (x is d0 or x is d1) and (y is d0 or y is d1):
1984 expected = 0
1985 elif x is y is d2:
1986 expected = 0
1987 elif x is d2:
1988 expected = -1
1989 else:
1990 assert y is d2
1991 expected = 1
1992 self.assertEqual(got, expected)
Tim Peters4c0db782002-12-26 05:01:19 +00001993
Tim Peters855fe882002-12-22 03:43:39 +00001994
Tim Peters0bf60bd2003-01-08 20:40:01 +00001995# Testing time objects with a non-None tzinfo.
Tim Peters855fe882002-12-22 03:43:39 +00001996class TestTimeTZ(TestTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001997 theclass = time
Tim Peters2a799bf2002-12-16 20:18:38 +00001998
1999 def test_empty(self):
2000 t = self.theclass()
2001 self.assertEqual(t.hour, 0)
2002 self.assertEqual(t.minute, 0)
2003 self.assertEqual(t.second, 0)
2004 self.assertEqual(t.microsecond, 0)
2005 self.failUnless(t.tzinfo is None)
2006
Tim Peters2a799bf2002-12-16 20:18:38 +00002007 def test_zones(self):
2008 est = FixedOffset(-300, "EST", 1)
2009 utc = FixedOffset(0, "UTC", -2)
2010 met = FixedOffset(60, "MET", 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002011 t1 = time( 7, 47, tzinfo=est)
2012 t2 = time(12, 47, tzinfo=utc)
2013 t3 = time(13, 47, tzinfo=met)
2014 t4 = time(microsecond=40)
2015 t5 = time(microsecond=40, tzinfo=utc)
Tim Peters2a799bf2002-12-16 20:18:38 +00002016
2017 self.assertEqual(t1.tzinfo, est)
2018 self.assertEqual(t2.tzinfo, utc)
2019 self.assertEqual(t3.tzinfo, met)
2020 self.failUnless(t4.tzinfo is None)
2021 self.assertEqual(t5.tzinfo, utc)
2022
Tim Peters855fe882002-12-22 03:43:39 +00002023 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2024 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2025 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002026 self.failUnless(t4.utcoffset() is None)
2027 self.assertRaises(TypeError, t1.utcoffset, "no args")
2028
2029 self.assertEqual(t1.tzname(), "EST")
2030 self.assertEqual(t2.tzname(), "UTC")
2031 self.assertEqual(t3.tzname(), "MET")
2032 self.failUnless(t4.tzname() is None)
2033 self.assertRaises(TypeError, t1.tzname, "no args")
2034
Tim Peters855fe882002-12-22 03:43:39 +00002035 self.assertEqual(t1.dst(), timedelta(minutes=1))
2036 self.assertEqual(t2.dst(), timedelta(minutes=-2))
2037 self.assertEqual(t3.dst(), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +00002038 self.failUnless(t4.dst() is None)
2039 self.assertRaises(TypeError, t1.dst, "no args")
2040
2041 self.assertEqual(hash(t1), hash(t2))
2042 self.assertEqual(hash(t1), hash(t3))
2043 self.assertEqual(hash(t2), hash(t3))
2044
2045 self.assertEqual(t1, t2)
2046 self.assertEqual(t1, t3)
2047 self.assertEqual(t2, t3)
2048 self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
2049 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
2050 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
2051
2052 self.assertEqual(str(t1), "07:47:00-05:00")
2053 self.assertEqual(str(t2), "12:47:00+00:00")
2054 self.assertEqual(str(t3), "13:47:00+01:00")
2055 self.assertEqual(str(t4), "00:00:00.000040")
2056 self.assertEqual(str(t5), "00:00:00.000040+00:00")
2057
2058 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
2059 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
2060 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
2061 self.assertEqual(t4.isoformat(), "00:00:00.000040")
2062 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
2063
Tim Peters0bf60bd2003-01-08 20:40:01 +00002064 d = 'datetime.time'
Tim Peters2a799bf2002-12-16 20:18:38 +00002065 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
2066 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
2067 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
2068 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
2069 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2070
2071 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2072 "07:47:00 %Z=EST %z=-0500")
2073 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2074 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2075
2076 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002077 t1 = time(23, 59, tzinfo=yuck)
Tim Peters2a799bf2002-12-16 20:18:38 +00002078 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2079 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2080
Tim Petersb92bb712002-12-21 17:44:07 +00002081 # Check that an invalid tzname result raises an exception.
2082 class Badtzname(tzinfo):
2083 def tzname(self, dt): return 42
Tim Peters0bf60bd2003-01-08 20:40:01 +00002084 t = time(2, 3, 4, tzinfo=Badtzname())
Tim Petersb92bb712002-12-21 17:44:07 +00002085 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2086 self.assertRaises(TypeError, t.strftime, "%Z")
Tim Peters2a799bf2002-12-16 20:18:38 +00002087
2088 def test_hash_edge_cases(self):
2089 # Offsets that overflow a basic time.
2090 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2091 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2092 self.assertEqual(hash(t1), hash(t2))
2093
2094 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2095 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2096 self.assertEqual(hash(t1), hash(t2))
2097
Tim Peters2a799bf2002-12-16 20:18:38 +00002098 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002099 # Try one without a tzinfo.
2100 args = 20, 59, 16, 64**2
2101 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002102 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002103 green = pickler.dumps(orig, proto)
2104 derived = unpickler.loads(green)
2105 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002106
2107 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002108 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002109 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002110 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002111 green = pickler.dumps(orig, proto)
2112 derived = unpickler.loads(green)
2113 self.assertEqual(orig, derived)
2114 self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
2115 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2116 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002117
2118 def test_more_bool(self):
2119 # Test cases with non-None tzinfo.
2120 cls = self.theclass
2121
2122 t = cls(0, tzinfo=FixedOffset(-300, ""))
2123 self.failUnless(t)
2124
2125 t = cls(5, tzinfo=FixedOffset(-300, ""))
2126 self.failUnless(t)
2127
2128 t = cls(5, tzinfo=FixedOffset(300, ""))
2129 self.failUnless(not t)
2130
2131 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2132 self.failUnless(not t)
2133
2134 # Mostly ensuring this doesn't overflow internally.
2135 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
2136 self.failUnless(t)
2137
2138 # But this should yield a value error -- the utcoffset is bogus.
2139 t = cls(0, tzinfo=FixedOffset(24*60, ""))
2140 self.assertRaises(ValueError, lambda: bool(t))
2141
2142 # Likewise.
2143 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
2144 self.assertRaises(ValueError, lambda: bool(t))
2145
Tim Peters12bf3392002-12-24 05:41:27 +00002146 def test_replace(self):
2147 cls = self.theclass
2148 z100 = FixedOffset(100, "+100")
2149 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2150 args = [1, 2, 3, 4, z100]
2151 base = cls(*args)
2152 self.assertEqual(base, base.replace())
2153
2154 i = 0
2155 for name, newval in (("hour", 5),
2156 ("minute", 6),
2157 ("second", 7),
2158 ("microsecond", 8),
2159 ("tzinfo", zm200)):
2160 newargs = args[:]
2161 newargs[i] = newval
2162 expected = cls(*newargs)
2163 got = base.replace(**{name: newval})
2164 self.assertEqual(expected, got)
2165 i += 1
2166
2167 # Ensure we can get rid of a tzinfo.
2168 self.assertEqual(base.tzname(), "+100")
2169 base2 = base.replace(tzinfo=None)
2170 self.failUnless(base2.tzinfo is None)
2171 self.failUnless(base2.tzname() is None)
2172
2173 # Ensure we can add one.
2174 base3 = base2.replace(tzinfo=z100)
2175 self.assertEqual(base, base3)
2176 self.failUnless(base.tzinfo is base3.tzinfo)
2177
2178 # Out of bounds.
2179 base = cls(1)
2180 self.assertRaises(ValueError, base.replace, hour=24)
2181 self.assertRaises(ValueError, base.replace, minute=-1)
2182 self.assertRaises(ValueError, base.replace, second=100)
2183 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2184
Tim Peters60c76e42002-12-27 00:41:11 +00002185 def test_mixed_compare(self):
2186 t1 = time(1, 2, 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002187 t2 = time(1, 2, 3)
Tim Peters60c76e42002-12-27 00:41:11 +00002188 self.assertEqual(t1, t2)
2189 t2 = t2.replace(tzinfo=None)
2190 self.assertEqual(t1, t2)
2191 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2192 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002193 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2194 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002195
Tim Peters0bf60bd2003-01-08 20:40:01 +00002196 # In time w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002197 class Varies(tzinfo):
2198 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002199 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002200 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002201 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002202 return self.offset
2203
2204 v = Varies()
2205 t1 = t2.replace(tzinfo=v)
2206 t2 = t2.replace(tzinfo=v)
2207 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2208 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2209 self.assertEqual(t1, t2)
2210
2211 # But if they're not identical, it isn't ignored.
2212 t2 = t2.replace(tzinfo=Varies())
2213 self.failUnless(t1 < t2) # t1's offset counter still going up
2214
Tim Petersa98924a2003-05-17 05:55:19 +00002215 def test_subclass_timetz(self):
2216
2217 class C(self.theclass):
2218 theAnswer = 42
2219
2220 def __new__(cls, *args, **kws):
2221 temp = kws.copy()
2222 extra = temp.pop('extra')
2223 result = self.theclass.__new__(cls, *args, **temp)
2224 result.extra = extra
2225 return result
2226
2227 def newmeth(self, start):
2228 return start + self.hour + self.second
2229
2230 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2231
2232 dt1 = self.theclass(*args)
2233 dt2 = C(*args, **{'extra': 7})
2234
2235 self.assertEqual(dt2.__class__, C)
2236 self.assertEqual(dt2.theAnswer, 42)
2237 self.assertEqual(dt2.extra, 7)
2238 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2239 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2240
Tim Peters4c0db782002-12-26 05:01:19 +00002241
Tim Peters0bf60bd2003-01-08 20:40:01 +00002242# Testing datetime objects with a non-None tzinfo.
2243
Tim Peters855fe882002-12-22 03:43:39 +00002244class TestDateTimeTZ(TestDateTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002245 theclass = datetime
Tim Peters2a799bf2002-12-16 20:18:38 +00002246
2247 def test_trivial(self):
2248 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2249 self.assertEqual(dt.year, 1)
2250 self.assertEqual(dt.month, 2)
2251 self.assertEqual(dt.day, 3)
2252 self.assertEqual(dt.hour, 4)
2253 self.assertEqual(dt.minute, 5)
2254 self.assertEqual(dt.second, 6)
2255 self.assertEqual(dt.microsecond, 7)
2256 self.assertEqual(dt.tzinfo, None)
2257
2258 def test_even_more_compare(self):
2259 # The test_compare() and test_more_compare() inherited from TestDate
2260 # and TestDateTime covered non-tzinfo cases.
2261
2262 # Smallest possible after UTC adjustment.
2263 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2264 # Largest possible after UTC adjustment.
2265 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2266 tzinfo=FixedOffset(-1439, ""))
2267
2268 # Make sure those compare correctly, and w/o overflow.
2269 self.failUnless(t1 < t2)
2270 self.failUnless(t1 != t2)
2271 self.failUnless(t2 > t1)
2272
2273 self.failUnless(t1 == t1)
2274 self.failUnless(t2 == t2)
2275
2276 # Equal afer adjustment.
2277 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2278 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2279 self.assertEqual(t1, t2)
2280
2281 # Change t1 not to subtract a minute, and t1 should be larger.
2282 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2283 self.failUnless(t1 > t2)
2284
2285 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2286 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2287 self.failUnless(t1 < t2)
2288
2289 # Back to the original t1, but make seconds resolve it.
2290 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2291 second=1)
2292 self.failUnless(t1 > t2)
2293
2294 # Likewise, but make microseconds resolve it.
2295 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2296 microsecond=1)
2297 self.failUnless(t1 > t2)
2298
2299 # Make t2 naive and it should fail.
2300 t2 = self.theclass.min
2301 self.assertRaises(TypeError, lambda: t1 == t2)
2302 self.assertEqual(t2, t2)
2303
2304 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2305 class Naive(tzinfo):
2306 def utcoffset(self, dt): return None
2307 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
2308 self.assertRaises(TypeError, lambda: t1 == t2)
2309 self.assertEqual(t2, t2)
2310
2311 # OTOH, it's OK to compare two of these mixing the two ways of being
2312 # naive.
2313 t1 = self.theclass(5, 6, 7)
2314 self.assertEqual(t1, t2)
2315
2316 # Try a bogus uctoffset.
2317 class Bogus(tzinfo):
Tim Peters397301e2003-01-02 21:28:08 +00002318 def utcoffset(self, dt):
2319 return timedelta(minutes=1440) # out of bounds
Tim Peters2a799bf2002-12-16 20:18:38 +00002320 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2321 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
Tim Peters60c76e42002-12-27 00:41:11 +00002322 self.assertRaises(ValueError, lambda: t1 == t2)
Tim Peters2a799bf2002-12-16 20:18:38 +00002323
Tim Peters2a799bf2002-12-16 20:18:38 +00002324 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002325 # Try one without a tzinfo.
2326 args = 6, 7, 23, 20, 59, 1, 64**2
2327 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002328 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002329 green = pickler.dumps(orig, proto)
2330 derived = unpickler.loads(green)
2331 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002332
2333 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002334 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002335 orig = self.theclass(*args, **{'tzinfo': tinfo})
Tim Petersa9bc1682003-01-11 03:39:11 +00002336 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
Guido van Rossum177e41a2003-01-30 22:06:23 +00002337 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002338 green = pickler.dumps(orig, proto)
2339 derived = unpickler.loads(green)
2340 self.assertEqual(orig, derived)
2341 self.failUnless(isinstance(derived.tzinfo,
2342 PicklableFixedOffset))
2343 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2344 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002345
2346 def test_extreme_hashes(self):
2347 # If an attempt is made to hash these via subtracting the offset
2348 # then hashing a datetime object, OverflowError results. The
2349 # Python implementation used to blow up here.
2350 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2351 hash(t)
2352 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2353 tzinfo=FixedOffset(-1439, ""))
2354 hash(t)
2355
2356 # OTOH, an OOB offset should blow up.
2357 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2358 self.assertRaises(ValueError, hash, t)
2359
2360 def test_zones(self):
2361 est = FixedOffset(-300, "EST")
2362 utc = FixedOffset(0, "UTC")
2363 met = FixedOffset(60, "MET")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002364 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2365 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2366 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
Tim Peters2a799bf2002-12-16 20:18:38 +00002367 self.assertEqual(t1.tzinfo, est)
2368 self.assertEqual(t2.tzinfo, utc)
2369 self.assertEqual(t3.tzinfo, met)
Tim Peters855fe882002-12-22 03:43:39 +00002370 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2371 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2372 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002373 self.assertEqual(t1.tzname(), "EST")
2374 self.assertEqual(t2.tzname(), "UTC")
2375 self.assertEqual(t3.tzname(), "MET")
2376 self.assertEqual(hash(t1), hash(t2))
2377 self.assertEqual(hash(t1), hash(t3))
2378 self.assertEqual(hash(t2), hash(t3))
2379 self.assertEqual(t1, t2)
2380 self.assertEqual(t1, t3)
2381 self.assertEqual(t2, t3)
2382 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2383 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2384 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002385 d = 'datetime.datetime(2002, 3, 19, '
Tim Peters2a799bf2002-12-16 20:18:38 +00002386 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2387 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2388 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2389
2390 def test_combine(self):
2391 met = FixedOffset(60, "MET")
2392 d = date(2002, 3, 4)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002393 tz = time(18, 45, 3, 1234, tzinfo=met)
2394 dt = datetime.combine(d, tz)
2395 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
Tim Peters2a799bf2002-12-16 20:18:38 +00002396 tzinfo=met))
2397
2398 def test_extract(self):
2399 met = FixedOffset(60, "MET")
2400 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2401 self.assertEqual(dt.date(), date(2002, 3, 4))
2402 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
Tim Peters0bf60bd2003-01-08 20:40:01 +00002403 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
Tim Peters2a799bf2002-12-16 20:18:38 +00002404
2405 def test_tz_aware_arithmetic(self):
2406 import random
2407
2408 now = self.theclass.now()
2409 tz55 = FixedOffset(-330, "west 5:30")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002410 timeaware = now.time().replace(tzinfo=tz55)
Tim Peters2a799bf2002-12-16 20:18:38 +00002411 nowaware = self.theclass.combine(now.date(), timeaware)
2412 self.failUnless(nowaware.tzinfo is tz55)
2413 self.assertEqual(nowaware.timetz(), timeaware)
2414
2415 # Can't mix aware and non-aware.
2416 self.assertRaises(TypeError, lambda: now - nowaware)
2417 self.assertRaises(TypeError, lambda: nowaware - now)
2418
Tim Peters0bf60bd2003-01-08 20:40:01 +00002419 # And adding datetime's doesn't make sense, aware or not.
Tim Peters2a799bf2002-12-16 20:18:38 +00002420 self.assertRaises(TypeError, lambda: now + nowaware)
2421 self.assertRaises(TypeError, lambda: nowaware + now)
2422 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2423
2424 # Subtracting should yield 0.
2425 self.assertEqual(now - now, timedelta(0))
2426 self.assertEqual(nowaware - nowaware, timedelta(0))
2427
2428 # Adding a delta should preserve tzinfo.
2429 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2430 nowawareplus = nowaware + delta
2431 self.failUnless(nowaware.tzinfo is tz55)
2432 nowawareplus2 = delta + nowaware
2433 self.failUnless(nowawareplus2.tzinfo is tz55)
2434 self.assertEqual(nowawareplus, nowawareplus2)
2435
2436 # that - delta should be what we started with, and that - what we
2437 # started with should be delta.
2438 diff = nowawareplus - delta
2439 self.failUnless(diff.tzinfo is tz55)
2440 self.assertEqual(nowaware, diff)
2441 self.assertRaises(TypeError, lambda: delta - nowawareplus)
2442 self.assertEqual(nowawareplus - nowaware, delta)
2443
2444 # Make up a random timezone.
2445 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
Tim Peters4c0db782002-12-26 05:01:19 +00002446 # Attach it to nowawareplus.
2447 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Tim Peters2a799bf2002-12-16 20:18:38 +00002448 self.failUnless(nowawareplus.tzinfo is tzr)
2449 # Make sure the difference takes the timezone adjustments into account.
2450 got = nowaware - nowawareplus
2451 # Expected: (nowaware base - nowaware offset) -
2452 # (nowawareplus base - nowawareplus offset) =
2453 # (nowaware base - nowawareplus base) +
2454 # (nowawareplus offset - nowaware offset) =
2455 # -delta + nowawareplus offset - nowaware offset
Tim Peters855fe882002-12-22 03:43:39 +00002456 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
Tim Peters2a799bf2002-12-16 20:18:38 +00002457 self.assertEqual(got, expected)
2458
2459 # Try max possible difference.
2460 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2461 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2462 tzinfo=FixedOffset(-1439, "max"))
2463 maxdiff = max - min
2464 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2465 timedelta(minutes=2*1439))
2466
2467 def test_tzinfo_now(self):
2468 meth = self.theclass.now
2469 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2470 base = meth()
2471 # Try with and without naming the keyword.
2472 off42 = FixedOffset(42, "42")
2473 another = meth(off42)
Tim Peters10cadce2003-01-23 19:58:02 +00002474 again = meth(tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002475 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002476 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002477 # Bad argument with and w/o naming the keyword.
2478 self.assertRaises(TypeError, meth, 16)
2479 self.assertRaises(TypeError, meth, tzinfo=16)
2480 # Bad keyword name.
2481 self.assertRaises(TypeError, meth, tinfo=off42)
2482 # Too many args.
2483 self.assertRaises(TypeError, meth, off42, off42)
2484
Tim Peters10cadce2003-01-23 19:58:02 +00002485 # We don't know which time zone we're in, and don't have a tzinfo
2486 # class to represent it, so seeing whether a tz argument actually
2487 # does a conversion is tricky.
2488 weirdtz = FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0)
2489 utc = FixedOffset(0, "utc", 0)
2490 for dummy in range(3):
2491 now = datetime.now(weirdtz)
2492 self.failUnless(now.tzinfo is weirdtz)
2493 utcnow = datetime.utcnow().replace(tzinfo=utc)
2494 now2 = utcnow.astimezone(weirdtz)
2495 if abs(now - now2) < timedelta(seconds=30):
2496 break
2497 # Else the code is broken, or more than 30 seconds passed between
2498 # calls; assuming the latter, just try again.
2499 else:
2500 # Three strikes and we're out.
2501 self.fail("utcnow(), now(tz), or astimezone() may be broken")
2502
Tim Peters2a799bf2002-12-16 20:18:38 +00002503 def test_tzinfo_fromtimestamp(self):
2504 import time
2505 meth = self.theclass.fromtimestamp
2506 ts = time.time()
2507 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2508 base = meth(ts)
2509 # Try with and without naming the keyword.
2510 off42 = FixedOffset(42, "42")
2511 another = meth(ts, off42)
Tim Peters2a44a8d2003-01-23 20:53:10 +00002512 again = meth(ts, tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002513 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002514 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002515 # Bad argument with and w/o naming the keyword.
2516 self.assertRaises(TypeError, meth, ts, 16)
2517 self.assertRaises(TypeError, meth, ts, tzinfo=16)
2518 # Bad keyword name.
2519 self.assertRaises(TypeError, meth, ts, tinfo=off42)
2520 # Too many args.
2521 self.assertRaises(TypeError, meth, ts, off42, off42)
2522 # Too few args.
2523 self.assertRaises(TypeError, meth)
2524
Tim Peters2a44a8d2003-01-23 20:53:10 +00002525 # Try to make sure tz= actually does some conversion.
Tim Peters84407612003-02-06 16:42:14 +00002526 timestamp = 1000000000
2527 utcdatetime = datetime.utcfromtimestamp(timestamp)
2528 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
2529 # But on some flavor of Mac, it's nowhere near that. So we can't have
2530 # any idea here what time that actually is, we can only test that
2531 # relative changes match.
2532 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
2533 tz = FixedOffset(utcoffset, "tz", 0)
2534 expected = utcdatetime + utcoffset
2535 got = datetime.fromtimestamp(timestamp, tz)
2536 self.assertEqual(expected, got.replace(tzinfo=None))
Tim Peters2a44a8d2003-01-23 20:53:10 +00002537
Tim Peters2a799bf2002-12-16 20:18:38 +00002538 def test_tzinfo_utcnow(self):
2539 meth = self.theclass.utcnow
2540 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2541 base = meth()
2542 # Try with and without naming the keyword; for whatever reason,
2543 # utcnow() doesn't accept a tzinfo argument.
2544 off42 = FixedOffset(42, "42")
2545 self.assertRaises(TypeError, meth, off42)
2546 self.assertRaises(TypeError, meth, tzinfo=off42)
2547
2548 def test_tzinfo_utcfromtimestamp(self):
2549 import time
2550 meth = self.theclass.utcfromtimestamp
2551 ts = time.time()
2552 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2553 base = meth(ts)
2554 # Try with and without naming the keyword; for whatever reason,
2555 # utcfromtimestamp() doesn't accept a tzinfo argument.
2556 off42 = FixedOffset(42, "42")
2557 self.assertRaises(TypeError, meth, ts, off42)
2558 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
2559
2560 def test_tzinfo_timetuple(self):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002561 # TestDateTime tested most of this. datetime adds a twist to the
Tim Peters2a799bf2002-12-16 20:18:38 +00002562 # DST flag.
2563 class DST(tzinfo):
2564 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002565 if isinstance(dstvalue, int):
2566 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002567 self.dstvalue = dstvalue
2568 def dst(self, dt):
2569 return self.dstvalue
2570
2571 cls = self.theclass
2572 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
2573 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
2574 t = d.timetuple()
2575 self.assertEqual(1, t.tm_year)
2576 self.assertEqual(1, t.tm_mon)
2577 self.assertEqual(1, t.tm_mday)
2578 self.assertEqual(10, t.tm_hour)
2579 self.assertEqual(20, t.tm_min)
2580 self.assertEqual(30, t.tm_sec)
2581 self.assertEqual(0, t.tm_wday)
2582 self.assertEqual(1, t.tm_yday)
2583 self.assertEqual(flag, t.tm_isdst)
2584
2585 # dst() returns wrong type.
2586 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
2587
2588 # dst() at the edge.
2589 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
2590 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
2591
2592 # dst() out of range.
2593 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
2594 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
2595
2596 def test_utctimetuple(self):
2597 class DST(tzinfo):
2598 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002599 if isinstance(dstvalue, int):
2600 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002601 self.dstvalue = dstvalue
2602 def dst(self, dt):
2603 return self.dstvalue
2604
2605 cls = self.theclass
2606 # This can't work: DST didn't implement utcoffset.
2607 self.assertRaises(NotImplementedError,
2608 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
2609
2610 class UOFS(DST):
2611 def __init__(self, uofs, dofs=None):
2612 DST.__init__(self, dofs)
Tim Peters397301e2003-01-02 21:28:08 +00002613 self.uofs = timedelta(minutes=uofs)
Tim Peters2a799bf2002-12-16 20:18:38 +00002614 def utcoffset(self, dt):
2615 return self.uofs
2616
2617 # Ensure tm_isdst is 0 regardless of what dst() says: DST is never
2618 # in effect for a UTC time.
2619 for dstvalue in -33, 33, 0, None:
2620 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
2621 t = d.utctimetuple()
2622 self.assertEqual(d.year, t.tm_year)
2623 self.assertEqual(d.month, t.tm_mon)
2624 self.assertEqual(d.day, t.tm_mday)
2625 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
2626 self.assertEqual(13, t.tm_min)
2627 self.assertEqual(d.second, t.tm_sec)
2628 self.assertEqual(d.weekday(), t.tm_wday)
2629 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
2630 t.tm_yday)
2631 self.assertEqual(0, t.tm_isdst)
2632
2633 # At the edges, UTC adjustment can normalize into years out-of-range
2634 # for a datetime object. Ensure that a correct timetuple is
2635 # created anyway.
2636 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
2637 # That goes back 1 minute less than a full day.
2638 t = tiny.utctimetuple()
2639 self.assertEqual(t.tm_year, MINYEAR-1)
2640 self.assertEqual(t.tm_mon, 12)
2641 self.assertEqual(t.tm_mday, 31)
2642 self.assertEqual(t.tm_hour, 0)
2643 self.assertEqual(t.tm_min, 1)
2644 self.assertEqual(t.tm_sec, 37)
2645 self.assertEqual(t.tm_yday, 366) # "year 0" is a leap year
2646 self.assertEqual(t.tm_isdst, 0)
2647
2648 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
2649 # That goes forward 1 minute less than a full day.
2650 t = huge.utctimetuple()
2651 self.assertEqual(t.tm_year, MAXYEAR+1)
2652 self.assertEqual(t.tm_mon, 1)
2653 self.assertEqual(t.tm_mday, 1)
2654 self.assertEqual(t.tm_hour, 23)
2655 self.assertEqual(t.tm_min, 58)
2656 self.assertEqual(t.tm_sec, 37)
2657 self.assertEqual(t.tm_yday, 1)
2658 self.assertEqual(t.tm_isdst, 0)
2659
2660 def test_tzinfo_isoformat(self):
2661 zero = FixedOffset(0, "+00:00")
2662 plus = FixedOffset(220, "+03:40")
2663 minus = FixedOffset(-231, "-03:51")
2664 unknown = FixedOffset(None, "")
2665
2666 cls = self.theclass
2667 datestr = '0001-02-03'
2668 for ofs in None, zero, plus, minus, unknown:
Tim Peters6578dc92002-12-24 18:31:27 +00002669 for us in 0, 987001:
Tim Peters2a799bf2002-12-16 20:18:38 +00002670 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
2671 timestr = '04:05:59' + (us and '.987001' or '')
2672 ofsstr = ofs is not None and d.tzname() or ''
2673 tailstr = timestr + ofsstr
2674 iso = d.isoformat()
2675 self.assertEqual(iso, datestr + 'T' + tailstr)
2676 self.assertEqual(iso, d.isoformat('T'))
2677 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
2678 self.assertEqual(str(d), datestr + ' ' + tailstr)
2679
Tim Peters12bf3392002-12-24 05:41:27 +00002680 def test_replace(self):
2681 cls = self.theclass
2682 z100 = FixedOffset(100, "+100")
2683 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2684 args = [1, 2, 3, 4, 5, 6, 7, z100]
2685 base = cls(*args)
2686 self.assertEqual(base, base.replace())
2687
2688 i = 0
2689 for name, newval in (("year", 2),
2690 ("month", 3),
2691 ("day", 4),
2692 ("hour", 5),
2693 ("minute", 6),
2694 ("second", 7),
2695 ("microsecond", 8),
2696 ("tzinfo", zm200)):
2697 newargs = args[:]
2698 newargs[i] = newval
2699 expected = cls(*newargs)
2700 got = base.replace(**{name: newval})
2701 self.assertEqual(expected, got)
2702 i += 1
2703
2704 # Ensure we can get rid of a tzinfo.
2705 self.assertEqual(base.tzname(), "+100")
2706 base2 = base.replace(tzinfo=None)
2707 self.failUnless(base2.tzinfo is None)
2708 self.failUnless(base2.tzname() is None)
2709
2710 # Ensure we can add one.
2711 base3 = base2.replace(tzinfo=z100)
2712 self.assertEqual(base, base3)
2713 self.failUnless(base.tzinfo is base3.tzinfo)
2714
2715 # Out of bounds.
2716 base = cls(2000, 2, 29)
2717 self.assertRaises(ValueError, base.replace, year=2001)
Tim Peters2a799bf2002-12-16 20:18:38 +00002718
Tim Peters80475bb2002-12-25 07:40:55 +00002719 def test_more_astimezone(self):
2720 # The inherited test_astimezone covered some trivial and error cases.
2721 fnone = FixedOffset(None, "None")
2722 f44m = FixedOffset(44, "44")
2723 fm5h = FixedOffset(-timedelta(hours=5), "m300")
2724
Tim Peters10cadce2003-01-23 19:58:02 +00002725 dt = self.theclass.now(tz=f44m)
Tim Peters80475bb2002-12-25 07:40:55 +00002726 self.failUnless(dt.tzinfo is f44m)
Tim Peters52dcce22003-01-23 16:36:11 +00002727 # Replacing with degenerate tzinfo raises an exception.
2728 self.assertRaises(ValueError, dt.astimezone, fnone)
2729 # Ditto with None tz.
2730 self.assertRaises(TypeError, dt.astimezone, None)
2731 # Replacing with same tzinfo makes no change.
Tim Peters80475bb2002-12-25 07:40:55 +00002732 x = dt.astimezone(dt.tzinfo)
2733 self.failUnless(x.tzinfo is f44m)
2734 self.assertEqual(x.date(), dt.date())
2735 self.assertEqual(x.time(), dt.time())
2736
2737 # Replacing with different tzinfo does adjust.
2738 got = dt.astimezone(fm5h)
2739 self.failUnless(got.tzinfo is fm5h)
2740 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
2741 expected = dt - dt.utcoffset() # in effect, convert to UTC
2742 expected += fm5h.utcoffset(dt) # and from there to local time
2743 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
2744 self.assertEqual(got.date(), expected.date())
2745 self.assertEqual(got.time(), expected.time())
2746 self.assertEqual(got.timetz(), expected.timetz())
2747 self.failUnless(got.tzinfo is expected.tzinfo)
2748 self.assertEqual(got, expected)
2749
Tim Peters4c0db782002-12-26 05:01:19 +00002750 def test_aware_subtract(self):
2751 cls = self.theclass
2752
Tim Peters60c76e42002-12-27 00:41:11 +00002753 # Ensure that utcoffset() is ignored when the operands have the
2754 # same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00002755 class OperandDependentOffset(tzinfo):
2756 def utcoffset(self, t):
2757 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00002758 # d0 and d1 equal after adjustment
2759 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00002760 else:
Tim Peters397301e2003-01-02 21:28:08 +00002761 # d2 off in the weeds
2762 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00002763
2764 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
2765 d0 = base.replace(minute=3)
2766 d1 = base.replace(minute=9)
2767 d2 = base.replace(minute=11)
2768 for x in d0, d1, d2:
2769 for y in d0, d1, d2:
2770 got = x - y
Tim Peters60c76e42002-12-27 00:41:11 +00002771 expected = timedelta(minutes=x.minute - y.minute)
2772 self.assertEqual(got, expected)
2773
2774 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
2775 # ignored.
2776 base = cls(8, 9, 10, 11, 12, 13, 14)
2777 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2778 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2779 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2780 for x in d0, d1, d2:
2781 for y in d0, d1, d2:
2782 got = x - y
Tim Peters4c0db782002-12-26 05:01:19 +00002783 if (x is d0 or x is d1) and (y is d0 or y is d1):
2784 expected = timedelta(0)
2785 elif x is y is d2:
2786 expected = timedelta(0)
2787 elif x is d2:
2788 expected = timedelta(minutes=(11-59)-0)
2789 else:
2790 assert y is d2
2791 expected = timedelta(minutes=0-(11-59))
2792 self.assertEqual(got, expected)
2793
Tim Peters60c76e42002-12-27 00:41:11 +00002794 def test_mixed_compare(self):
2795 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002796 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters60c76e42002-12-27 00:41:11 +00002797 self.assertEqual(t1, t2)
2798 t2 = t2.replace(tzinfo=None)
2799 self.assertEqual(t1, t2)
2800 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2801 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002802 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2803 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002804
Tim Peters0bf60bd2003-01-08 20:40:01 +00002805 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002806 class Varies(tzinfo):
2807 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002808 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002809 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002810 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002811 return self.offset
2812
2813 v = Varies()
2814 t1 = t2.replace(tzinfo=v)
2815 t2 = t2.replace(tzinfo=v)
2816 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2817 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2818 self.assertEqual(t1, t2)
2819
2820 # But if they're not identical, it isn't ignored.
2821 t2 = t2.replace(tzinfo=Varies())
2822 self.failUnless(t1 < t2) # t1's offset counter still going up
Tim Peters80475bb2002-12-25 07:40:55 +00002823
Tim Petersa98924a2003-05-17 05:55:19 +00002824 def test_subclass_datetimetz(self):
2825
2826 class C(self.theclass):
2827 theAnswer = 42
2828
2829 def __new__(cls, *args, **kws):
2830 temp = kws.copy()
2831 extra = temp.pop('extra')
2832 result = self.theclass.__new__(cls, *args, **temp)
2833 result.extra = extra
2834 return result
2835
2836 def newmeth(self, start):
2837 return start + self.hour + self.year
2838
2839 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2840
2841 dt1 = self.theclass(*args)
2842 dt2 = C(*args, **{'extra': 7})
2843
2844 self.assertEqual(dt2.__class__, C)
2845 self.assertEqual(dt2.theAnswer, 42)
2846 self.assertEqual(dt2.extra, 7)
2847 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2848 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
2849
Tim Peters621818b2002-12-29 23:44:49 +00002850# Pain to set up DST-aware tzinfo classes.
2851
2852def first_sunday_on_or_after(dt):
2853 days_to_go = 6 - dt.weekday()
2854 if days_to_go:
2855 dt += timedelta(days_to_go)
2856 return dt
2857
2858ZERO = timedelta(0)
2859HOUR = timedelta(hours=1)
2860DAY = timedelta(days=1)
2861# In the US, DST starts at 2am (standard time) on the first Sunday in April.
2862DSTSTART = datetime(1, 4, 1, 2)
2863# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
Tim Peters327098a2003-01-20 22:54:38 +00002864# which is the first Sunday on or after Oct 25. Because we view 1:MM as
2865# being standard time on that day, there is no spelling in local time of
2866# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
2867DSTEND = datetime(1, 10, 25, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002868
2869class USTimeZone(tzinfo):
2870
2871 def __init__(self, hours, reprname, stdname, dstname):
2872 self.stdoffset = timedelta(hours=hours)
2873 self.reprname = reprname
2874 self.stdname = stdname
2875 self.dstname = dstname
2876
2877 def __repr__(self):
2878 return self.reprname
2879
2880 def tzname(self, dt):
2881 if self.dst(dt):
2882 return self.dstname
2883 else:
2884 return self.stdname
2885
2886 def utcoffset(self, dt):
2887 return self.stdoffset + self.dst(dt)
2888
2889 def dst(self, dt):
Tim Petersbad8ff02002-12-30 20:52:32 +00002890 if dt is None or dt.tzinfo is None:
Tim Peters621818b2002-12-29 23:44:49 +00002891 # An exception instead may be sensible here, in one or more of
2892 # the cases.
2893 return ZERO
Tim Peters521fc152002-12-31 17:36:56 +00002894 assert dt.tzinfo is self
Tim Peters621818b2002-12-29 23:44:49 +00002895
2896 # Find first Sunday in April.
2897 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
2898 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
2899
2900 # Find last Sunday in October.
2901 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
2902 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
2903
Tim Peters621818b2002-12-29 23:44:49 +00002904 # Can't compare naive to aware objects, so strip the timezone from
2905 # dt first.
Tim Peters52dcce22003-01-23 16:36:11 +00002906 if start <= dt.replace(tzinfo=None) < end:
Tim Peters621818b2002-12-29 23:44:49 +00002907 return HOUR
2908 else:
2909 return ZERO
2910
Tim Peters521fc152002-12-31 17:36:56 +00002911Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
2912Central = USTimeZone(-6, "Central", "CST", "CDT")
2913Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
2914Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
Tim Peters1024bf82002-12-30 17:09:40 +00002915utc_real = FixedOffset(0, "UTC", 0)
2916# For better test coverage, we want another flavor of UTC that's west of
2917# the Eastern and Pacific timezones.
Tim Petersadf64202003-01-04 06:03:15 +00002918utc_fake = FixedOffset(-12*60, "UTCfake", 0)
Tim Peters621818b2002-12-29 23:44:49 +00002919
2920class TestTimezoneConversions(unittest.TestCase):
Tim Peters327098a2003-01-20 22:54:38 +00002921 # The DST switch times for 2002, in std time.
Tim Peters0bf60bd2003-01-08 20:40:01 +00002922 dston = datetime(2002, 4, 7, 2)
Tim Peters327098a2003-01-20 22:54:38 +00002923 dstoff = datetime(2002, 10, 27, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002924
Tim Peters0bf60bd2003-01-08 20:40:01 +00002925 theclass = datetime
Tim Peters710fb152003-01-02 19:35:54 +00002926
Tim Peters521fc152002-12-31 17:36:56 +00002927 # Check a time that's inside DST.
2928 def checkinside(self, dt, tz, utc, dston, dstoff):
2929 self.assertEqual(dt.dst(), HOUR)
2930
2931 # Conversion to our own timezone is always an identity.
2932 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters521fc152002-12-31 17:36:56 +00002933
2934 asutc = dt.astimezone(utc)
2935 there_and_back = asutc.astimezone(tz)
2936
2937 # Conversion to UTC and back isn't always an identity here,
2938 # because there are redundant spellings (in local time) of
2939 # UTC time when DST begins: the clock jumps from 1:59:59
2940 # to 3:00:00, and a local time of 2:MM:SS doesn't really
2941 # make sense then. The classes above treat 2:MM:SS as
2942 # daylight time then (it's "after 2am"), really an alias
2943 # for 1:MM:SS standard time. The latter form is what
2944 # conversion back from UTC produces.
2945 if dt.date() == dston.date() and dt.hour == 2:
2946 # We're in the redundant hour, and coming back from
2947 # UTC gives the 1:MM:SS standard-time spelling.
2948 self.assertEqual(there_and_back + HOUR, dt)
2949 # Although during was considered to be in daylight
2950 # time, there_and_back is not.
2951 self.assertEqual(there_and_back.dst(), ZERO)
2952 # They're the same times in UTC.
2953 self.assertEqual(there_and_back.astimezone(utc),
2954 dt.astimezone(utc))
2955 else:
2956 # We're not in the redundant hour.
2957 self.assertEqual(dt, there_and_back)
2958
Tim Peters327098a2003-01-20 22:54:38 +00002959 # Because we have a redundant spelling when DST begins, there is
2960 # (unforunately) an hour when DST ends that can't be spelled at all in
2961 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
2962 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
2963 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
2964 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
2965 # expressed in local time. Nevertheless, we want conversion back
2966 # from UTC to mimic the local clock's "repeat an hour" behavior.
Tim Peters521fc152002-12-31 17:36:56 +00002967 nexthour_utc = asutc + HOUR
Tim Petersadf64202003-01-04 06:03:15 +00002968 nexthour_tz = nexthour_utc.astimezone(tz)
Tim Peters327098a2003-01-20 22:54:38 +00002969 if dt.date() == dstoff.date() and dt.hour == 0:
2970 # We're in the hour before the last DST hour. The last DST hour
Tim Petersadf64202003-01-04 06:03:15 +00002971 # is ineffable. We want the conversion back to repeat 1:MM.
Tim Peters327098a2003-01-20 22:54:38 +00002972 self.assertEqual(nexthour_tz, dt.replace(hour=1))
2973 nexthour_utc += HOUR
2974 nexthour_tz = nexthour_utc.astimezone(tz)
2975 self.assertEqual(nexthour_tz, dt.replace(hour=1))
Tim Peters521fc152002-12-31 17:36:56 +00002976 else:
Tim Peters327098a2003-01-20 22:54:38 +00002977 self.assertEqual(nexthour_tz - dt, HOUR)
Tim Peters521fc152002-12-31 17:36:56 +00002978
2979 # Check a time that's outside DST.
2980 def checkoutside(self, dt, tz, utc):
2981 self.assertEqual(dt.dst(), ZERO)
2982
2983 # Conversion to our own timezone is always an identity.
2984 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters52dcce22003-01-23 16:36:11 +00002985
2986 # Converting to UTC and back is an identity too.
2987 asutc = dt.astimezone(utc)
2988 there_and_back = asutc.astimezone(tz)
2989 self.assertEqual(dt, there_and_back)
Tim Peters521fc152002-12-31 17:36:56 +00002990
Tim Peters1024bf82002-12-30 17:09:40 +00002991 def convert_between_tz_and_utc(self, tz, utc):
2992 dston = self.dston.replace(tzinfo=tz)
Tim Peters327098a2003-01-20 22:54:38 +00002993 # Because 1:MM on the day DST ends is taken as being standard time,
2994 # there is no spelling in tz for the last hour of daylight time.
2995 # For purposes of the test, the last hour of DST is 0:MM, which is
2996 # taken as being daylight time (and 1:MM is taken as being standard
2997 # time).
Tim Peters1024bf82002-12-30 17:09:40 +00002998 dstoff = self.dstoff.replace(tzinfo=tz)
2999 for delta in (timedelta(weeks=13),
3000 DAY,
3001 HOUR,
3002 timedelta(minutes=1),
3003 timedelta(microseconds=1)):
3004
Tim Peters521fc152002-12-31 17:36:56 +00003005 self.checkinside(dston, tz, utc, dston, dstoff)
3006 for during in dston + delta, dstoff - delta:
3007 self.checkinside(during, tz, utc, dston, dstoff)
Tim Peters31cc3152002-12-30 17:37:30 +00003008
Tim Peters521fc152002-12-31 17:36:56 +00003009 self.checkoutside(dstoff, tz, utc)
3010 for outside in dston - delta, dstoff + delta:
3011 self.checkoutside(outside, tz, utc)
Tim Peters31cc3152002-12-30 17:37:30 +00003012
Tim Peters621818b2002-12-29 23:44:49 +00003013 def test_easy(self):
3014 # Despite the name of this test, the endcases are excruciating.
Tim Peters1024bf82002-12-30 17:09:40 +00003015 self.convert_between_tz_and_utc(Eastern, utc_real)
3016 self.convert_between_tz_and_utc(Pacific, utc_real)
3017 self.convert_between_tz_and_utc(Eastern, utc_fake)
3018 self.convert_between_tz_and_utc(Pacific, utc_fake)
3019 # The next is really dancing near the edge. It works because
3020 # Pacific and Eastern are far enough apart that their "problem
3021 # hours" don't overlap.
3022 self.convert_between_tz_and_utc(Eastern, Pacific)
3023 self.convert_between_tz_and_utc(Pacific, Eastern)
Tim Peters36087ed2003-01-01 04:18:51 +00003024 # OTOH, these fail! Don't enable them. The difficulty is that
3025 # the edge case tests assume that every hour is representable in
3026 # the "utc" class. This is always true for a fixed-offset tzinfo
3027 # class (lke utc_real and utc_fake), but not for Eastern or Central.
3028 # For these adjacent DST-aware time zones, the range of time offsets
3029 # tested ends up creating hours in the one that aren't representable
3030 # in the other. For the same reason, we would see failures in the
3031 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
3032 # offset deltas in convert_between_tz_and_utc().
3033 #
3034 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
3035 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
Tim Peters621818b2002-12-29 23:44:49 +00003036
Tim Petersf3615152003-01-01 21:51:37 +00003037 def test_tricky(self):
3038 # 22:00 on day before daylight starts.
3039 fourback = self.dston - timedelta(hours=4)
3040 ninewest = FixedOffset(-9*60, "-0900", 0)
Tim Peters52dcce22003-01-23 16:36:11 +00003041 fourback = fourback.replace(tzinfo=ninewest)
Tim Petersf3615152003-01-01 21:51:37 +00003042 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
3043 # 2", we should get the 3 spelling.
3044 # If we plug 22:00 the day before into Eastern, it "looks like std
3045 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
3046 # to 22:00 lands on 2:00, which makes no sense in local time (the
3047 # local clock jumps from 1 to 3). The point here is to make sure we
3048 # get the 3 spelling.
3049 expected = self.dston.replace(hour=3)
Tim Peters52dcce22003-01-23 16:36:11 +00003050 got = fourback.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00003051 self.assertEqual(expected, got)
3052
3053 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
3054 # case we want the 1:00 spelling.
Tim Peters52dcce22003-01-23 16:36:11 +00003055 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
Tim Petersf3615152003-01-01 21:51:37 +00003056 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
3057 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
3058 # spelling.
3059 expected = self.dston.replace(hour=1)
Tim Peters52dcce22003-01-23 16:36:11 +00003060 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00003061 self.assertEqual(expected, got)
Tim Peters621818b2002-12-29 23:44:49 +00003062
Tim Petersadf64202003-01-04 06:03:15 +00003063 # Now on the day DST ends, we want "repeat an hour" behavior.
3064 # UTC 4:MM 5:MM 6:MM 7:MM checking these
3065 # EST 23:MM 0:MM 1:MM 2:MM
3066 # EDT 0:MM 1:MM 2:MM 3:MM
3067 # wall 0:MM 1:MM 1:MM 2:MM against these
3068 for utc in utc_real, utc_fake:
3069 for tz in Eastern, Pacific:
Tim Peters327098a2003-01-20 22:54:38 +00003070 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
Tim Petersadf64202003-01-04 06:03:15 +00003071 # Convert that to UTC.
3072 first_std_hour -= tz.utcoffset(None)
3073 # Adjust for possibly fake UTC.
3074 asutc = first_std_hour + utc.utcoffset(None)
3075 # First UTC hour to convert; this is 4:00 when utc=utc_real &
3076 # tz=Eastern.
3077 asutcbase = asutc.replace(tzinfo=utc)
3078 for tzhour in (0, 1, 1, 2):
3079 expectedbase = self.dstoff.replace(hour=tzhour)
3080 for minute in 0, 30, 59:
3081 expected = expectedbase.replace(minute=minute)
3082 asutc = asutcbase.replace(minute=minute)
3083 astz = asutc.astimezone(tz)
3084 self.assertEqual(astz.replace(tzinfo=None), expected)
3085 asutcbase += HOUR
3086
3087
Tim Peters710fb152003-01-02 19:35:54 +00003088 def test_bogus_dst(self):
3089 class ok(tzinfo):
3090 def utcoffset(self, dt): return HOUR
3091 def dst(self, dt): return HOUR
3092
3093 now = self.theclass.now().replace(tzinfo=utc_real)
3094 # Doesn't blow up.
3095 now.astimezone(ok())
3096
3097 # Does blow up.
3098 class notok(ok):
3099 def dst(self, dt): return None
3100 self.assertRaises(ValueError, now.astimezone, notok())
3101
Tim Peters52dcce22003-01-23 16:36:11 +00003102 def test_fromutc(self):
3103 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
3104 now = datetime.utcnow().replace(tzinfo=utc_real)
3105 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3106 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
3107 enow = Eastern.fromutc(now) # doesn't blow up
3108 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3109 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3110 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3111
3112 # Always converts UTC to standard time.
3113 class FauxUSTimeZone(USTimeZone):
3114 def fromutc(self, dt):
3115 return dt + self.stdoffset
3116 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
3117
3118 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
3119 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
3120 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
3121
3122 # Check around DST start.
3123 start = self.dston.replace(hour=4, tzinfo=Eastern)
3124 fstart = start.replace(tzinfo=FEastern)
3125 for wall in 23, 0, 1, 3, 4, 5:
3126 expected = start.replace(hour=wall)
3127 if wall == 23:
3128 expected -= timedelta(days=1)
3129 got = Eastern.fromutc(start)
3130 self.assertEqual(expected, got)
3131
3132 expected = fstart + FEastern.stdoffset
3133 got = FEastern.fromutc(fstart)
3134 self.assertEqual(expected, got)
3135
3136 # Ensure astimezone() calls fromutc() too.
3137 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3138 self.assertEqual(expected, got)
3139
3140 start += HOUR
3141 fstart += HOUR
3142
3143 # Check around DST end.
3144 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
3145 fstart = start.replace(tzinfo=FEastern)
3146 for wall in 0, 1, 1, 2, 3, 4:
3147 expected = start.replace(hour=wall)
3148 got = Eastern.fromutc(start)
3149 self.assertEqual(expected, got)
3150
3151 expected = fstart + FEastern.stdoffset
3152 got = FEastern.fromutc(fstart)
3153 self.assertEqual(expected, got)
3154
3155 # Ensure astimezone() calls fromutc() too.
3156 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3157 self.assertEqual(expected, got)
3158
3159 start += HOUR
3160 fstart += HOUR
3161
Tim Peters710fb152003-01-02 19:35:54 +00003162
Tim Peters528ca532004-09-16 01:30:50 +00003163#############################################################################
3164# oddballs
3165
3166class Oddballs(unittest.TestCase):
3167
3168 def test_bug_1028306(self):
3169 # Trying to compare a date to a datetime should act like a mixed-
3170 # type comparison, despite that datetime is a subclass of date.
3171 as_date = date.today()
3172 as_datetime = datetime.combine(as_date, time())
3173 self.assert_(as_date != as_datetime)
3174 self.assert_(as_datetime != as_date)
3175 self.assert_(not as_date == as_datetime)
3176 self.assert_(not as_datetime == as_date)
3177 self.assertRaises(TypeError, lambda: as_date < as_datetime)
3178 self.assertRaises(TypeError, lambda: as_datetime < as_date)
3179 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
3180 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
3181 self.assertRaises(TypeError, lambda: as_date > as_datetime)
3182 self.assertRaises(TypeError, lambda: as_datetime > as_date)
3183 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
3184 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
3185
3186 # Neverthelss, comparison should work with the base-class (date)
3187 # projection if use of a date method is forced.
3188 self.assert_(as_date.__eq__(as_datetime))
3189 different_day = (as_date.day + 1) % 20 + 1
3190 self.assert_(not as_date.__eq__(as_datetime.replace(day=
3191 different_day)))
3192
3193 # And date should compare with other subclasses of date. If a
3194 # subclass wants to stop this, it's up to the subclass to do so.
3195 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
3196 self.assertEqual(as_date, date_sc)
3197 self.assertEqual(date_sc, as_date)
3198
3199 # Ditto for datetimes.
3200 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
3201 as_date.day, 0, 0, 0)
3202 self.assertEqual(as_datetime, datetime_sc)
3203 self.assertEqual(datetime_sc, as_datetime)
3204
Tim Peterscfd4a8b2002-12-16 21:12:37 +00003205def test_suite():
Tim Peters2a799bf2002-12-16 20:18:38 +00003206 allsuites = [unittest.makeSuite(klass, 'test')
3207 for klass in (TestModule,
3208 TestTZInfo,
3209 TestTimeDelta,
3210 TestDateOnly,
3211 TestDate,
3212 TestDateTime,
3213 TestTime,
3214 TestTimeTZ,
3215 TestDateTimeTZ,
Tim Peters621818b2002-12-29 23:44:49 +00003216 TestTimezoneConversions,
Tim Peters528ca532004-09-16 01:30:50 +00003217 Oddballs,
Tim Peters2a799bf2002-12-16 20:18:38 +00003218 )
3219 ]
3220 return unittest.TestSuite(allsuites)
3221
3222def test_main():
3223 import gc
3224 import sys
3225
Tim Peterscfd4a8b2002-12-16 21:12:37 +00003226 thesuite = test_suite()
Tim Peters2a799bf2002-12-16 20:18:38 +00003227 lastrc = None
3228 while True:
3229 test_support.run_suite(thesuite)
3230 if 1: # change to 0, under a debug build, for some leak detection
3231 break
3232 gc.collect()
3233 if gc.garbage:
3234 raise SystemError("gc.garbage not empty after test run: %r" %
3235 gc.garbage)
3236 if hasattr(sys, 'gettotalrefcount'):
3237 thisrc = sys.gettotalrefcount()
3238 print >> sys.stderr, '*' * 10, 'total refs:', thisrc,
3239 if lastrc:
3240 print >> sys.stderr, 'delta:', thisrc - lastrc
3241 else:
3242 print >> sys.stderr
3243 lastrc = thisrc
3244
3245if __name__ == "__main__":
3246 test_main()