blob: f7fec572cf834db7b638d68aadcce662fff78107 [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):
449 def from_td(td):
450 return T(td.days, td.seconds, td.microseconds)
451 from_td = staticmethod(from_td)
452
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
733 def test_today(self):
734 import time
735
736 # We claim that today() is like fromtimestamp(time.time()), so
737 # prove it.
738 for dummy in range(3):
739 today = self.theclass.today()
740 ts = time.time()
741 todayagain = self.theclass.fromtimestamp(ts)
742 if today == todayagain:
743 break
744 # There are several legit reasons that could fail:
745 # 1. It recently became midnight, between the today() and the
746 # time() calls.
747 # 2. The platform time() has such fine resolution that we'll
748 # never get the same value twice.
749 # 3. The platform time() has poor resolution, and we just
750 # happened to call today() right before a resolution quantum
751 # boundary.
752 # 4. The system clock got fiddled between calls.
753 # In any case, wait a little while and try again.
754 time.sleep(0.1)
755
756 # It worked or it didn't. If it didn't, assume it's reason #2, and
757 # let the test pass if they're within half a second of each other.
758 self.failUnless(today == todayagain or
759 abs(todayagain - today) < timedelta(seconds=0.5))
760
761 def test_weekday(self):
762 for i in range(7):
763 # March 4, 2002 is a Monday
764 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
765 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
766 # January 2, 1956 is a Monday
767 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
768 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
769
770 def test_isocalendar(self):
771 # Check examples from
772 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
773 for i in range(7):
774 d = self.theclass(2003, 12, 22+i)
775 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
776 d = self.theclass(2003, 12, 29) + timedelta(i)
777 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
778 d = self.theclass(2004, 1, 5+i)
779 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
780 d = self.theclass(2009, 12, 21+i)
781 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
782 d = self.theclass(2009, 12, 28) + timedelta(i)
783 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
784 d = self.theclass(2010, 1, 4+i)
785 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
786
787 def test_iso_long_years(self):
788 # Calculate long ISO years and compare to table from
789 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
790 ISO_LONG_YEARS_TABLE = """
791 4 32 60 88
792 9 37 65 93
793 15 43 71 99
794 20 48 76
795 26 54 82
796
797 105 133 161 189
798 111 139 167 195
799 116 144 172
800 122 150 178
801 128 156 184
802
803 201 229 257 285
804 207 235 263 291
805 212 240 268 296
806 218 246 274
807 224 252 280
808
809 303 331 359 387
810 308 336 364 392
811 314 342 370 398
812 320 348 376
813 325 353 381
814 """
815 iso_long_years = map(int, ISO_LONG_YEARS_TABLE.split())
816 iso_long_years.sort()
817 L = []
818 for i in range(400):
819 d = self.theclass(2000+i, 12, 31)
820 d1 = self.theclass(1600+i, 12, 31)
821 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
822 if d.isocalendar()[1] == 53:
823 L.append(i)
824 self.assertEqual(L, iso_long_years)
825
826 def test_isoformat(self):
827 t = self.theclass(2, 3, 2)
828 self.assertEqual(t.isoformat(), "0002-03-02")
829
830 def test_ctime(self):
831 t = self.theclass(2002, 3, 2)
832 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
833
834 def test_strftime(self):
835 t = self.theclass(2005, 3, 2)
836 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
Raymond Hettingerf69d9f62003-06-27 08:14:17 +0000837 self.assertEqual(t.strftime(""), "") # SF bug #761337
Tim Peters2a799bf2002-12-16 20:18:38 +0000838
839 self.assertRaises(TypeError, t.strftime) # needs an arg
840 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
841 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
842
843 # A naive object replaces %z and %Z w/ empty strings.
844 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
845
846 def test_resolution_info(self):
847 self.assert_(isinstance(self.theclass.min, self.theclass))
848 self.assert_(isinstance(self.theclass.max, self.theclass))
849 self.assert_(isinstance(self.theclass.resolution, timedelta))
850 self.assert_(self.theclass.max > self.theclass.min)
851
852 def test_extreme_timedelta(self):
853 big = self.theclass.max - self.theclass.min
854 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
855 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
856 # n == 315537897599999999 ~= 2**58.13
857 justasbig = timedelta(0, 0, n)
858 self.assertEqual(big, justasbig)
859 self.assertEqual(self.theclass.min + big, self.theclass.max)
860 self.assertEqual(self.theclass.max - big, self.theclass.min)
861
862 def test_timetuple(self):
863 for i in range(7):
864 # January 2, 1956 is a Monday (0)
865 d = self.theclass(1956, 1, 2+i)
866 t = d.timetuple()
867 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
868 # February 1, 1956 is a Wednesday (2)
869 d = self.theclass(1956, 2, 1+i)
870 t = d.timetuple()
871 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
872 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
873 # of the year.
874 d = self.theclass(1956, 3, 1+i)
875 t = d.timetuple()
876 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
877 self.assertEqual(t.tm_year, 1956)
878 self.assertEqual(t.tm_mon, 3)
879 self.assertEqual(t.tm_mday, 1+i)
880 self.assertEqual(t.tm_hour, 0)
881 self.assertEqual(t.tm_min, 0)
882 self.assertEqual(t.tm_sec, 0)
883 self.assertEqual(t.tm_wday, (3+i)%7)
884 self.assertEqual(t.tm_yday, 61+i)
885 self.assertEqual(t.tm_isdst, -1)
886
887 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000888 args = 6, 7, 23
889 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000890 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +0000891 green = pickler.dumps(orig, proto)
892 derived = unpickler.loads(green)
893 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +0000894
895 def test_compare(self):
896 t1 = self.theclass(2, 3, 4)
897 t2 = self.theclass(2, 3, 4)
898 self.failUnless(t1 == t2)
899 self.failUnless(t1 <= t2)
900 self.failUnless(t1 >= t2)
901 self.failUnless(not t1 != t2)
902 self.failUnless(not t1 < t2)
903 self.failUnless(not t1 > t2)
904 self.assertEqual(cmp(t1, t2), 0)
905 self.assertEqual(cmp(t2, t1), 0)
906
907 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
908 t2 = self.theclass(*args) # this is larger than t1
909 self.failUnless(t1 < t2)
910 self.failUnless(t2 > t1)
911 self.failUnless(t1 <= t2)
912 self.failUnless(t2 >= t1)
913 self.failUnless(t1 != t2)
914 self.failUnless(t2 != t1)
915 self.failUnless(not t1 == t2)
916 self.failUnless(not t2 == t1)
917 self.failUnless(not t1 > t2)
918 self.failUnless(not t2 < t1)
919 self.failUnless(not t1 >= t2)
920 self.failUnless(not t2 <= t1)
921 self.assertEqual(cmp(t1, t2), -1)
922 self.assertEqual(cmp(t2, t1), 1)
923
Tim Peters68124bb2003-02-08 03:46:31 +0000924 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +0000925 self.assertEqual(t1 == badarg, False)
926 self.assertEqual(t1 != badarg, True)
927 self.assertEqual(badarg == t1, False)
928 self.assertEqual(badarg != t1, True)
929
Tim Peters2a799bf2002-12-16 20:18:38 +0000930 self.assertRaises(TypeError, lambda: t1 < badarg)
931 self.assertRaises(TypeError, lambda: t1 > badarg)
932 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +0000933 self.assertRaises(TypeError, lambda: badarg <= t1)
934 self.assertRaises(TypeError, lambda: badarg < t1)
935 self.assertRaises(TypeError, lambda: badarg > t1)
936 self.assertRaises(TypeError, lambda: badarg >= t1)
937
Tim Peters8d81a012003-01-24 22:36:34 +0000938 def test_mixed_compare(self):
939 our = self.theclass(2000, 4, 5)
940 self.assertRaises(TypeError, cmp, our, 1)
941 self.assertRaises(TypeError, cmp, 1, our)
942
943 class AnotherDateTimeClass(object):
944 def __cmp__(self, other):
945 # Return "equal" so calling this can't be confused with
946 # compare-by-address (which never says "equal" for distinct
947 # objects).
948 return 0
949
950 # This still errors, because date and datetime comparison raise
951 # TypeError instead of NotImplemented when they don't know what to
952 # do, in order to stop comparison from falling back to the default
953 # compare-by-address.
954 their = AnotherDateTimeClass()
955 self.assertRaises(TypeError, cmp, our, their)
956 # Oops: The next stab raises TypeError in the C implementation,
957 # but not in the Python implementation of datetime. The difference
958 # is due to that the Python implementation defines __cmp__ but
959 # the C implementation defines tp_richcompare. This is more pain
960 # to fix than it's worth, so commenting out the test.
961 # self.assertEqual(cmp(their, our), 0)
962
963 # But date and datetime comparison return NotImplemented instead if the
964 # other object has a timetuple attr. This gives the other object a
965 # chance to do the comparison.
966 class Comparable(AnotherDateTimeClass):
967 def timetuple(self):
968 return ()
969
970 their = Comparable()
971 self.assertEqual(cmp(our, their), 0)
972 self.assertEqual(cmp(their, our), 0)
973 self.failUnless(our == their)
974 self.failUnless(their == our)
975
Tim Peters2a799bf2002-12-16 20:18:38 +0000976 def test_bool(self):
977 # All dates are considered true.
978 self.failUnless(self.theclass.min)
979 self.failUnless(self.theclass.max)
980
Tim Petersd6844152002-12-22 20:58:42 +0000981 def test_srftime_out_of_range(self):
982 # For nasty technical reasons, we can't handle years before 1900.
983 cls = self.theclass
984 self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900")
985 for y in 1, 49, 51, 99, 100, 1000, 1899:
986 self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y")
Tim Peters12bf3392002-12-24 05:41:27 +0000987
988 def test_replace(self):
989 cls = self.theclass
990 args = [1, 2, 3]
991 base = cls(*args)
992 self.assertEqual(base, base.replace())
993
994 i = 0
995 for name, newval in (("year", 2),
996 ("month", 3),
997 ("day", 4)):
998 newargs = args[:]
999 newargs[i] = newval
1000 expected = cls(*newargs)
1001 got = base.replace(**{name: newval})
1002 self.assertEqual(expected, got)
1003 i += 1
1004
1005 # Out of bounds.
1006 base = cls(2000, 2, 29)
1007 self.assertRaises(ValueError, base.replace, year=2001)
1008
Tim Petersa98924a2003-05-17 05:55:19 +00001009 def test_subclass_date(self):
1010
1011 class C(self.theclass):
1012 theAnswer = 42
1013
1014 def __new__(cls, *args, **kws):
1015 temp = kws.copy()
1016 extra = temp.pop('extra')
1017 result = self.theclass.__new__(cls, *args, **temp)
1018 result.extra = extra
1019 return result
1020
1021 def newmeth(self, start):
1022 return start + self.year + self.month
1023
1024 args = 2003, 4, 14
1025
1026 dt1 = self.theclass(*args)
1027 dt2 = C(*args, **{'extra': 7})
1028
1029 self.assertEqual(dt2.__class__, C)
1030 self.assertEqual(dt2.theAnswer, 42)
1031 self.assertEqual(dt2.extra, 7)
1032 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1033 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1034
Tim Peters604c0132004-06-07 23:04:33 +00001035 def test_pickling_subclass_date(self):
1036
1037 args = 6, 7, 23
1038 orig = SubclassDate(*args)
1039 for pickler, unpickler, proto in pickle_choices:
1040 green = pickler.dumps(orig, proto)
1041 derived = unpickler.loads(green)
1042 self.assertEqual(orig, derived)
1043
Tim Peters3f606292004-03-21 23:38:41 +00001044 def test_backdoor_resistance(self):
1045 # For fast unpickling, the constructor accepts a pickle string.
1046 # This is a low-overhead backdoor. A user can (by intent or
1047 # mistake) pass a string directly, which (if it's the right length)
1048 # will get treated like a pickle, and bypass the normal sanity
1049 # checks in the constructor. This can create insane objects.
1050 # The constructor doesn't want to burn the time to validate all
1051 # fields, but does check the month field. This stops, e.g.,
1052 # datetime.datetime('1995-03-25') from yielding an insane object.
1053 base = '1995-03-25'
1054 if not issubclass(self.theclass, datetime):
1055 base = base[:4]
1056 for month_byte in '9', chr(0), chr(13), '\xff':
1057 self.assertRaises(TypeError, self.theclass,
1058 base[:2] + month_byte + base[3:])
1059 for ord_byte in range(1, 13):
1060 # This shouldn't blow up because of the month byte alone. If
1061 # the implementation changes to do more-careful checking, it may
1062 # blow up because other fields are insane.
1063 self.theclass(base[:2] + chr(ord_byte) + base[3:])
Tim Peterseb1a4962003-05-17 02:25:20 +00001064
Tim Peters2a799bf2002-12-16 20:18:38 +00001065#############################################################################
1066# datetime tests
1067
Tim Peters604c0132004-06-07 23:04:33 +00001068class SubclassDatetime(datetime):
1069 sub_var = 1
1070
Tim Peters2a799bf2002-12-16 20:18:38 +00001071class TestDateTime(TestDate):
1072
1073 theclass = datetime
1074
1075 def test_basic_attributes(self):
1076 dt = self.theclass(2002, 3, 1, 12, 0)
1077 self.assertEqual(dt.year, 2002)
1078 self.assertEqual(dt.month, 3)
1079 self.assertEqual(dt.day, 1)
1080 self.assertEqual(dt.hour, 12)
1081 self.assertEqual(dt.minute, 0)
1082 self.assertEqual(dt.second, 0)
1083 self.assertEqual(dt.microsecond, 0)
1084
1085 def test_basic_attributes_nonzero(self):
1086 # Make sure all attributes are non-zero so bugs in
1087 # bit-shifting access show up.
1088 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1089 self.assertEqual(dt.year, 2002)
1090 self.assertEqual(dt.month, 3)
1091 self.assertEqual(dt.day, 1)
1092 self.assertEqual(dt.hour, 12)
1093 self.assertEqual(dt.minute, 59)
1094 self.assertEqual(dt.second, 59)
1095 self.assertEqual(dt.microsecond, 8000)
1096
1097 def test_roundtrip(self):
1098 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1099 self.theclass.now()):
1100 # Verify dt -> string -> datetime identity.
1101 s = repr(dt)
1102 self.failUnless(s.startswith('datetime.'))
1103 s = s[9:]
1104 dt2 = eval(s)
1105 self.assertEqual(dt, dt2)
1106
1107 # Verify identity via reconstructing from pieces.
1108 dt2 = self.theclass(dt.year, dt.month, dt.day,
1109 dt.hour, dt.minute, dt.second,
1110 dt.microsecond)
1111 self.assertEqual(dt, dt2)
1112
1113 def test_isoformat(self):
1114 t = self.theclass(2, 3, 2, 4, 5, 1, 123)
1115 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123")
1116 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
1117 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
1118 # str is ISO format with the separator forced to a blank.
1119 self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
1120
1121 t = self.theclass(2, 3, 2)
1122 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1123 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1124 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1125 # str is ISO format with the separator forced to a blank.
1126 self.assertEqual(str(t), "0002-03-02 00:00:00")
1127
1128 def test_more_ctime(self):
1129 # Test fields that TestDate doesn't touch.
1130 import time
1131
1132 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1133 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1134 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1135 # out. The difference is that t.ctime() produces " 2" for the day,
1136 # but platform ctime() produces "02" for the day. According to
1137 # C99, t.ctime() is correct here.
1138 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1139
1140 # So test a case where that difference doesn't matter.
1141 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1142 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1143
1144 def test_tz_independent_comparing(self):
1145 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1146 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1147 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1148 self.assertEqual(dt1, dt3)
1149 self.assert_(dt2 > dt3)
1150
1151 # Make sure comparison doesn't forget microseconds, and isn't done
1152 # via comparing a float timestamp (an IEEE double doesn't have enough
1153 # precision to span microsecond resolution across years 1 thru 9999,
1154 # so comparing via timestamp necessarily calls some distinct values
1155 # equal).
1156 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1157 us = timedelta(microseconds=1)
1158 dt2 = dt1 + us
1159 self.assertEqual(dt2 - dt1, us)
1160 self.assert_(dt1 < dt2)
1161
1162 def test_bad_constructor_arguments(self):
1163 # bad years
1164 self.theclass(MINYEAR, 1, 1) # no exception
1165 self.theclass(MAXYEAR, 1, 1) # no exception
1166 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1167 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1168 # bad months
1169 self.theclass(2000, 1, 1) # no exception
1170 self.theclass(2000, 12, 1) # no exception
1171 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1172 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1173 # bad days
1174 self.theclass(2000, 2, 29) # no exception
1175 self.theclass(2004, 2, 29) # no exception
1176 self.theclass(2400, 2, 29) # no exception
1177 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1178 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1179 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1180 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1181 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1182 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1183 # bad hours
1184 self.theclass(2000, 1, 31, 0) # no exception
1185 self.theclass(2000, 1, 31, 23) # no exception
1186 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1187 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1188 # bad minutes
1189 self.theclass(2000, 1, 31, 23, 0) # no exception
1190 self.theclass(2000, 1, 31, 23, 59) # no exception
1191 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1192 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1193 # bad seconds
1194 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1195 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1196 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1197 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1198 # bad microseconds
1199 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1200 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1201 self.assertRaises(ValueError, self.theclass,
1202 2000, 1, 31, 23, 59, 59, -1)
1203 self.assertRaises(ValueError, self.theclass,
1204 2000, 1, 31, 23, 59, 59,
1205 1000000)
1206
1207 def test_hash_equality(self):
1208 d = self.theclass(2000, 12, 31, 23, 30, 17)
1209 e = self.theclass(2000, 12, 31, 23, 30, 17)
1210 self.assertEqual(d, e)
1211 self.assertEqual(hash(d), hash(e))
1212
1213 dic = {d: 1}
1214 dic[e] = 2
1215 self.assertEqual(len(dic), 1)
1216 self.assertEqual(dic[d], 2)
1217 self.assertEqual(dic[e], 2)
1218
1219 d = self.theclass(2001, 1, 1, 0, 5, 17)
1220 e = self.theclass(2001, 1, 1, 0, 5, 17)
1221 self.assertEqual(d, e)
1222 self.assertEqual(hash(d), hash(e))
1223
1224 dic = {d: 1}
1225 dic[e] = 2
1226 self.assertEqual(len(dic), 1)
1227 self.assertEqual(dic[d], 2)
1228 self.assertEqual(dic[e], 2)
1229
1230 def test_computations(self):
1231 a = self.theclass(2002, 1, 31)
1232 b = self.theclass(1956, 1, 31)
1233 diff = a-b
1234 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1235 self.assertEqual(diff.seconds, 0)
1236 self.assertEqual(diff.microseconds, 0)
1237 a = self.theclass(2002, 3, 2, 17, 6)
1238 millisec = timedelta(0, 0, 1000)
1239 hour = timedelta(0, 3600)
1240 day = timedelta(1)
1241 week = timedelta(7)
1242 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1243 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1244 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1245 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1246 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1247 self.assertEqual(a - hour, a + -hour)
1248 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1249 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1250 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1251 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1252 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1253 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1254 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1255 self.assertEqual((a + week) - a, week)
1256 self.assertEqual((a + day) - a, day)
1257 self.assertEqual((a + hour) - a, hour)
1258 self.assertEqual((a + millisec) - a, millisec)
1259 self.assertEqual((a - week) - a, -week)
1260 self.assertEqual((a - day) - a, -day)
1261 self.assertEqual((a - hour) - a, -hour)
1262 self.assertEqual((a - millisec) - a, -millisec)
1263 self.assertEqual(a - (a + week), -week)
1264 self.assertEqual(a - (a + day), -day)
1265 self.assertEqual(a - (a + hour), -hour)
1266 self.assertEqual(a - (a + millisec), -millisec)
1267 self.assertEqual(a - (a - week), week)
1268 self.assertEqual(a - (a - day), day)
1269 self.assertEqual(a - (a - hour), hour)
1270 self.assertEqual(a - (a - millisec), millisec)
1271 self.assertEqual(a + (week + day + hour + millisec),
1272 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1273 self.assertEqual(a + (week + day + hour + millisec),
1274 (((a + week) + day) + hour) + millisec)
1275 self.assertEqual(a - (week + day + hour + millisec),
1276 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1277 self.assertEqual(a - (week + day + hour + millisec),
1278 (((a - week) - day) - hour) - millisec)
1279 # Add/sub ints, longs, floats should be illegal
1280 for i in 1, 1L, 1.0:
1281 self.assertRaises(TypeError, lambda: a+i)
1282 self.assertRaises(TypeError, lambda: a-i)
1283 self.assertRaises(TypeError, lambda: i+a)
1284 self.assertRaises(TypeError, lambda: i-a)
1285
1286 # delta - datetime is senseless.
1287 self.assertRaises(TypeError, lambda: day - a)
1288 # mixing datetime and (delta or datetime) via * or // is senseless
1289 self.assertRaises(TypeError, lambda: day * a)
1290 self.assertRaises(TypeError, lambda: a * day)
1291 self.assertRaises(TypeError, lambda: day // a)
1292 self.assertRaises(TypeError, lambda: a // day)
1293 self.assertRaises(TypeError, lambda: a * a)
1294 self.assertRaises(TypeError, lambda: a // a)
1295 # datetime + datetime is senseless
1296 self.assertRaises(TypeError, lambda: a + a)
1297
1298 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001299 args = 6, 7, 23, 20, 59, 1, 64**2
1300 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001301 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001302 green = pickler.dumps(orig, proto)
1303 derived = unpickler.loads(green)
1304 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001305
Guido van Rossum275666f2003-02-07 21:49:01 +00001306 def test_more_pickling(self):
1307 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
1308 s = pickle.dumps(a)
1309 b = pickle.loads(s)
1310 self.assertEqual(b.year, 2003)
1311 self.assertEqual(b.month, 2)
1312 self.assertEqual(b.day, 7)
1313
Tim Peters604c0132004-06-07 23:04:33 +00001314 def test_pickling_subclass_datetime(self):
1315 args = 6, 7, 23, 20, 59, 1, 64**2
1316 orig = SubclassDatetime(*args)
1317 for pickler, unpickler, proto in pickle_choices:
1318 green = pickler.dumps(orig, proto)
1319 derived = unpickler.loads(green)
1320 self.assertEqual(orig, derived)
1321
Tim Peters2a799bf2002-12-16 20:18:38 +00001322 def test_more_compare(self):
1323 # The test_compare() inherited from TestDate covers the error cases.
1324 # We just want to test lexicographic ordering on the members datetime
1325 # has that date lacks.
1326 args = [2000, 11, 29, 20, 58, 16, 999998]
1327 t1 = self.theclass(*args)
1328 t2 = self.theclass(*args)
1329 self.failUnless(t1 == t2)
1330 self.failUnless(t1 <= t2)
1331 self.failUnless(t1 >= t2)
1332 self.failUnless(not t1 != t2)
1333 self.failUnless(not t1 < t2)
1334 self.failUnless(not t1 > t2)
1335 self.assertEqual(cmp(t1, t2), 0)
1336 self.assertEqual(cmp(t2, t1), 0)
1337
1338 for i in range(len(args)):
1339 newargs = args[:]
1340 newargs[i] = args[i] + 1
1341 t2 = self.theclass(*newargs) # this is larger than t1
1342 self.failUnless(t1 < t2)
1343 self.failUnless(t2 > t1)
1344 self.failUnless(t1 <= t2)
1345 self.failUnless(t2 >= t1)
1346 self.failUnless(t1 != t2)
1347 self.failUnless(t2 != t1)
1348 self.failUnless(not t1 == t2)
1349 self.failUnless(not t2 == t1)
1350 self.failUnless(not t1 > t2)
1351 self.failUnless(not t2 < t1)
1352 self.failUnless(not t1 >= t2)
1353 self.failUnless(not t2 <= t1)
1354 self.assertEqual(cmp(t1, t2), -1)
1355 self.assertEqual(cmp(t2, t1), 1)
1356
1357
1358 # A helper for timestamp constructor tests.
1359 def verify_field_equality(self, expected, got):
1360 self.assertEqual(expected.tm_year, got.year)
1361 self.assertEqual(expected.tm_mon, got.month)
1362 self.assertEqual(expected.tm_mday, got.day)
1363 self.assertEqual(expected.tm_hour, got.hour)
1364 self.assertEqual(expected.tm_min, got.minute)
1365 self.assertEqual(expected.tm_sec, got.second)
1366
1367 def test_fromtimestamp(self):
1368 import time
1369
1370 ts = time.time()
1371 expected = time.localtime(ts)
1372 got = self.theclass.fromtimestamp(ts)
1373 self.verify_field_equality(expected, got)
1374
1375 def test_utcfromtimestamp(self):
1376 import time
1377
1378 ts = time.time()
1379 expected = time.gmtime(ts)
1380 got = self.theclass.utcfromtimestamp(ts)
1381 self.verify_field_equality(expected, got)
1382
1383 def test_utcnow(self):
1384 import time
1385
1386 # Call it a success if utcnow() and utcfromtimestamp() are within
1387 # a second of each other.
1388 tolerance = timedelta(seconds=1)
1389 for dummy in range(3):
1390 from_now = self.theclass.utcnow()
1391 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1392 if abs(from_timestamp - from_now) <= tolerance:
1393 break
1394 # Else try again a few times.
1395 self.failUnless(abs(from_timestamp - from_now) <= tolerance)
1396
1397 def test_more_timetuple(self):
1398 # This tests fields beyond those tested by the TestDate.test_timetuple.
1399 t = self.theclass(2004, 12, 31, 6, 22, 33)
1400 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1401 self.assertEqual(t.timetuple(),
1402 (t.year, t.month, t.day,
1403 t.hour, t.minute, t.second,
1404 t.weekday(),
1405 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1406 -1))
1407 tt = t.timetuple()
1408 self.assertEqual(tt.tm_year, t.year)
1409 self.assertEqual(tt.tm_mon, t.month)
1410 self.assertEqual(tt.tm_mday, t.day)
1411 self.assertEqual(tt.tm_hour, t.hour)
1412 self.assertEqual(tt.tm_min, t.minute)
1413 self.assertEqual(tt.tm_sec, t.second)
1414 self.assertEqual(tt.tm_wday, t.weekday())
1415 self.assertEqual(tt.tm_yday, t.toordinal() -
1416 date(t.year, 1, 1).toordinal() + 1)
1417 self.assertEqual(tt.tm_isdst, -1)
1418
1419 def test_more_strftime(self):
1420 # This tests fields beyond those tested by the TestDate.test_strftime.
1421 t = self.theclass(2004, 12, 31, 6, 22, 33)
1422 self.assertEqual(t.strftime("%m %d %y %S %M %H %j"),
1423 "12 31 04 33 22 06 366")
1424
1425 def test_extract(self):
1426 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1427 self.assertEqual(dt.date(), date(2002, 3, 4))
1428 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1429
1430 def test_combine(self):
1431 d = date(2002, 3, 4)
1432 t = time(18, 45, 3, 1234)
1433 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1434 combine = self.theclass.combine
1435 dt = combine(d, t)
1436 self.assertEqual(dt, expected)
1437
1438 dt = combine(time=t, date=d)
1439 self.assertEqual(dt, expected)
1440
1441 self.assertEqual(d, dt.date())
1442 self.assertEqual(t, dt.time())
1443 self.assertEqual(dt, combine(dt.date(), dt.time()))
1444
1445 self.assertRaises(TypeError, combine) # need an arg
1446 self.assertRaises(TypeError, combine, d) # need two args
1447 self.assertRaises(TypeError, combine, t, d) # args reversed
1448 self.assertRaises(TypeError, combine, d, t, 1) # too many args
1449 self.assertRaises(TypeError, combine, "date", "time") # wrong types
1450
Tim Peters12bf3392002-12-24 05:41:27 +00001451 def test_replace(self):
1452 cls = self.theclass
1453 args = [1, 2, 3, 4, 5, 6, 7]
1454 base = cls(*args)
1455 self.assertEqual(base, base.replace())
1456
1457 i = 0
1458 for name, newval in (("year", 2),
1459 ("month", 3),
1460 ("day", 4),
1461 ("hour", 5),
1462 ("minute", 6),
1463 ("second", 7),
1464 ("microsecond", 8)):
1465 newargs = args[:]
1466 newargs[i] = newval
1467 expected = cls(*newargs)
1468 got = base.replace(**{name: newval})
1469 self.assertEqual(expected, got)
1470 i += 1
1471
1472 # Out of bounds.
1473 base = cls(2000, 2, 29)
1474 self.assertRaises(ValueError, base.replace, year=2001)
1475
Tim Peters80475bb2002-12-25 07:40:55 +00001476 def test_astimezone(self):
Tim Peters52dcce22003-01-23 16:36:11 +00001477 # Pretty boring! The TZ test is more interesting here. astimezone()
1478 # simply can't be applied to a naive object.
Tim Peters80475bb2002-12-25 07:40:55 +00001479 dt = self.theclass.now()
1480 f = FixedOffset(44, "")
Tim Peters80475bb2002-12-25 07:40:55 +00001481 self.assertRaises(TypeError, dt.astimezone) # not enough args
1482 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
1483 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Tim Peters52dcce22003-01-23 16:36:11 +00001484 self.assertRaises(ValueError, dt.astimezone, f) # naive
1485 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
Tim Peters80475bb2002-12-25 07:40:55 +00001486
Tim Peters52dcce22003-01-23 16:36:11 +00001487 class Bogus(tzinfo):
1488 def utcoffset(self, dt): return None
1489 def dst(self, dt): return timedelta(0)
1490 bog = Bogus()
1491 self.assertRaises(ValueError, dt.astimezone, bog) # naive
1492
1493 class AlsoBogus(tzinfo):
1494 def utcoffset(self, dt): return timedelta(0)
1495 def dst(self, dt): return None
1496 alsobog = AlsoBogus()
1497 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
Tim Peters12bf3392002-12-24 05:41:27 +00001498
Tim Petersa98924a2003-05-17 05:55:19 +00001499 def test_subclass_datetime(self):
1500
1501 class C(self.theclass):
1502 theAnswer = 42
1503
1504 def __new__(cls, *args, **kws):
1505 temp = kws.copy()
1506 extra = temp.pop('extra')
1507 result = self.theclass.__new__(cls, *args, **temp)
1508 result.extra = extra
1509 return result
1510
1511 def newmeth(self, start):
1512 return start + self.year + self.month + self.second
1513
1514 args = 2003, 4, 14, 12, 13, 41
1515
1516 dt1 = self.theclass(*args)
1517 dt2 = C(*args, **{'extra': 7})
1518
1519 self.assertEqual(dt2.__class__, C)
1520 self.assertEqual(dt2.theAnswer, 42)
1521 self.assertEqual(dt2.extra, 7)
1522 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1523 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
1524 dt1.second - 7)
1525
Tim Peters604c0132004-06-07 23:04:33 +00001526class SubclassTime(time):
1527 sub_var = 1
1528
Tim Peters07534a62003-02-07 22:50:28 +00001529class TestTime(HarmlessMixedComparison):
Tim Peters2a799bf2002-12-16 20:18:38 +00001530
1531 theclass = time
1532
1533 def test_basic_attributes(self):
1534 t = self.theclass(12, 0)
1535 self.assertEqual(t.hour, 12)
1536 self.assertEqual(t.minute, 0)
1537 self.assertEqual(t.second, 0)
1538 self.assertEqual(t.microsecond, 0)
1539
1540 def test_basic_attributes_nonzero(self):
1541 # Make sure all attributes are non-zero so bugs in
1542 # bit-shifting access show up.
1543 t = self.theclass(12, 59, 59, 8000)
1544 self.assertEqual(t.hour, 12)
1545 self.assertEqual(t.minute, 59)
1546 self.assertEqual(t.second, 59)
1547 self.assertEqual(t.microsecond, 8000)
1548
1549 def test_roundtrip(self):
1550 t = self.theclass(1, 2, 3, 4)
1551
1552 # Verify t -> string -> time identity.
1553 s = repr(t)
1554 self.failUnless(s.startswith('datetime.'))
1555 s = s[9:]
1556 t2 = eval(s)
1557 self.assertEqual(t, t2)
1558
1559 # Verify identity via reconstructing from pieces.
1560 t2 = self.theclass(t.hour, t.minute, t.second,
1561 t.microsecond)
1562 self.assertEqual(t, t2)
1563
1564 def test_comparing(self):
1565 args = [1, 2, 3, 4]
1566 t1 = self.theclass(*args)
1567 t2 = self.theclass(*args)
1568 self.failUnless(t1 == t2)
1569 self.failUnless(t1 <= t2)
1570 self.failUnless(t1 >= t2)
1571 self.failUnless(not t1 != t2)
1572 self.failUnless(not t1 < t2)
1573 self.failUnless(not t1 > t2)
1574 self.assertEqual(cmp(t1, t2), 0)
1575 self.assertEqual(cmp(t2, t1), 0)
1576
1577 for i in range(len(args)):
1578 newargs = args[:]
1579 newargs[i] = args[i] + 1
1580 t2 = self.theclass(*newargs) # this is larger than t1
1581 self.failUnless(t1 < t2)
1582 self.failUnless(t2 > t1)
1583 self.failUnless(t1 <= t2)
1584 self.failUnless(t2 >= t1)
1585 self.failUnless(t1 != t2)
1586 self.failUnless(t2 != t1)
1587 self.failUnless(not t1 == t2)
1588 self.failUnless(not t2 == t1)
1589 self.failUnless(not t1 > t2)
1590 self.failUnless(not t2 < t1)
1591 self.failUnless(not t1 >= t2)
1592 self.failUnless(not t2 <= t1)
1593 self.assertEqual(cmp(t1, t2), -1)
1594 self.assertEqual(cmp(t2, t1), 1)
1595
Tim Peters68124bb2003-02-08 03:46:31 +00001596 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +00001597 self.assertEqual(t1 == badarg, False)
1598 self.assertEqual(t1 != badarg, True)
1599 self.assertEqual(badarg == t1, False)
1600 self.assertEqual(badarg != t1, True)
1601
Tim Peters2a799bf2002-12-16 20:18:38 +00001602 self.assertRaises(TypeError, lambda: t1 <= badarg)
1603 self.assertRaises(TypeError, lambda: t1 < badarg)
1604 self.assertRaises(TypeError, lambda: t1 > badarg)
1605 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +00001606 self.assertRaises(TypeError, lambda: badarg <= t1)
1607 self.assertRaises(TypeError, lambda: badarg < t1)
1608 self.assertRaises(TypeError, lambda: badarg > t1)
1609 self.assertRaises(TypeError, lambda: badarg >= t1)
1610
1611 def test_bad_constructor_arguments(self):
1612 # bad hours
1613 self.theclass(0, 0) # no exception
1614 self.theclass(23, 0) # no exception
1615 self.assertRaises(ValueError, self.theclass, -1, 0)
1616 self.assertRaises(ValueError, self.theclass, 24, 0)
1617 # bad minutes
1618 self.theclass(23, 0) # no exception
1619 self.theclass(23, 59) # no exception
1620 self.assertRaises(ValueError, self.theclass, 23, -1)
1621 self.assertRaises(ValueError, self.theclass, 23, 60)
1622 # bad seconds
1623 self.theclass(23, 59, 0) # no exception
1624 self.theclass(23, 59, 59) # no exception
1625 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
1626 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
1627 # bad microseconds
1628 self.theclass(23, 59, 59, 0) # no exception
1629 self.theclass(23, 59, 59, 999999) # no exception
1630 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
1631 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
1632
1633 def test_hash_equality(self):
1634 d = self.theclass(23, 30, 17)
1635 e = self.theclass(23, 30, 17)
1636 self.assertEqual(d, e)
1637 self.assertEqual(hash(d), hash(e))
1638
1639 dic = {d: 1}
1640 dic[e] = 2
1641 self.assertEqual(len(dic), 1)
1642 self.assertEqual(dic[d], 2)
1643 self.assertEqual(dic[e], 2)
1644
1645 d = self.theclass(0, 5, 17)
1646 e = self.theclass(0, 5, 17)
1647 self.assertEqual(d, e)
1648 self.assertEqual(hash(d), hash(e))
1649
1650 dic = {d: 1}
1651 dic[e] = 2
1652 self.assertEqual(len(dic), 1)
1653 self.assertEqual(dic[d], 2)
1654 self.assertEqual(dic[e], 2)
1655
1656 def test_isoformat(self):
1657 t = self.theclass(4, 5, 1, 123)
1658 self.assertEqual(t.isoformat(), "04:05:01.000123")
1659 self.assertEqual(t.isoformat(), str(t))
1660
1661 t = self.theclass()
1662 self.assertEqual(t.isoformat(), "00:00:00")
1663 self.assertEqual(t.isoformat(), str(t))
1664
1665 t = self.theclass(microsecond=1)
1666 self.assertEqual(t.isoformat(), "00:00:00.000001")
1667 self.assertEqual(t.isoformat(), str(t))
1668
1669 t = self.theclass(microsecond=10)
1670 self.assertEqual(t.isoformat(), "00:00:00.000010")
1671 self.assertEqual(t.isoformat(), str(t))
1672
1673 t = self.theclass(microsecond=100)
1674 self.assertEqual(t.isoformat(), "00:00:00.000100")
1675 self.assertEqual(t.isoformat(), str(t))
1676
1677 t = self.theclass(microsecond=1000)
1678 self.assertEqual(t.isoformat(), "00:00:00.001000")
1679 self.assertEqual(t.isoformat(), str(t))
1680
1681 t = self.theclass(microsecond=10000)
1682 self.assertEqual(t.isoformat(), "00:00:00.010000")
1683 self.assertEqual(t.isoformat(), str(t))
1684
1685 t = self.theclass(microsecond=100000)
1686 self.assertEqual(t.isoformat(), "00:00:00.100000")
1687 self.assertEqual(t.isoformat(), str(t))
1688
1689 def test_strftime(self):
1690 t = self.theclass(1, 2, 3, 4)
1691 self.assertEqual(t.strftime('%H %M %S'), "01 02 03")
1692 # A naive object replaces %z and %Z with empty strings.
1693 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1694
1695 def test_str(self):
1696 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
1697 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
1698 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
1699 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
1700 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
1701
1702 def test_repr(self):
1703 name = 'datetime.' + self.theclass.__name__
1704 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
1705 "%s(1, 2, 3, 4)" % name)
1706 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
1707 "%s(10, 2, 3, 4000)" % name)
1708 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
1709 "%s(0, 2, 3, 400000)" % name)
1710 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
1711 "%s(12, 2, 3)" % name)
1712 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
1713 "%s(23, 15)" % name)
1714
1715 def test_resolution_info(self):
1716 self.assert_(isinstance(self.theclass.min, self.theclass))
1717 self.assert_(isinstance(self.theclass.max, self.theclass))
1718 self.assert_(isinstance(self.theclass.resolution, timedelta))
1719 self.assert_(self.theclass.max > self.theclass.min)
1720
1721 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001722 args = 20, 59, 16, 64**2
1723 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001724 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001725 green = pickler.dumps(orig, proto)
1726 derived = unpickler.loads(green)
1727 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001728
Tim Peters604c0132004-06-07 23:04:33 +00001729 def test_pickling_subclass_time(self):
1730 args = 20, 59, 16, 64**2
1731 orig = SubclassTime(*args)
1732 for pickler, unpickler, proto in pickle_choices:
1733 green = pickler.dumps(orig, proto)
1734 derived = unpickler.loads(green)
1735 self.assertEqual(orig, derived)
1736
Tim Peters2a799bf2002-12-16 20:18:38 +00001737 def test_bool(self):
1738 cls = self.theclass
1739 self.failUnless(cls(1))
1740 self.failUnless(cls(0, 1))
1741 self.failUnless(cls(0, 0, 1))
1742 self.failUnless(cls(0, 0, 0, 1))
1743 self.failUnless(not cls(0))
1744 self.failUnless(not cls())
1745
Tim Peters12bf3392002-12-24 05:41:27 +00001746 def test_replace(self):
1747 cls = self.theclass
1748 args = [1, 2, 3, 4]
1749 base = cls(*args)
1750 self.assertEqual(base, base.replace())
1751
1752 i = 0
1753 for name, newval in (("hour", 5),
1754 ("minute", 6),
1755 ("second", 7),
1756 ("microsecond", 8)):
1757 newargs = args[:]
1758 newargs[i] = newval
1759 expected = cls(*newargs)
1760 got = base.replace(**{name: newval})
1761 self.assertEqual(expected, got)
1762 i += 1
1763
1764 # Out of bounds.
1765 base = cls(1)
1766 self.assertRaises(ValueError, base.replace, hour=24)
1767 self.assertRaises(ValueError, base.replace, minute=-1)
1768 self.assertRaises(ValueError, base.replace, second=100)
1769 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1770
Tim Petersa98924a2003-05-17 05:55:19 +00001771 def test_subclass_time(self):
1772
1773 class C(self.theclass):
1774 theAnswer = 42
1775
1776 def __new__(cls, *args, **kws):
1777 temp = kws.copy()
1778 extra = temp.pop('extra')
1779 result = self.theclass.__new__(cls, *args, **temp)
1780 result.extra = extra
1781 return result
1782
1783 def newmeth(self, start):
1784 return start + self.hour + self.second
1785
1786 args = 4, 5, 6
1787
1788 dt1 = self.theclass(*args)
1789 dt2 = C(*args, **{'extra': 7})
1790
1791 self.assertEqual(dt2.__class__, C)
1792 self.assertEqual(dt2.theAnswer, 42)
1793 self.assertEqual(dt2.extra, 7)
1794 self.assertEqual(dt1.isoformat(), dt2.isoformat())
1795 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
1796
Tim Peters855fe882002-12-22 03:43:39 +00001797# A mixin for classes with a tzinfo= argument. Subclasses must define
1798# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001799# must be legit (which is true for time and datetime).
Tim Peters855fe882002-12-22 03:43:39 +00001800class TZInfoBase(unittest.TestCase):
Tim Peters2a799bf2002-12-16 20:18:38 +00001801
Tim Petersbad8ff02002-12-30 20:52:32 +00001802 def test_argument_passing(self):
1803 cls = self.theclass
Tim Peters0bf60bd2003-01-08 20:40:01 +00001804 # A datetime passes itself on, a time passes None.
Tim Petersbad8ff02002-12-30 20:52:32 +00001805 class introspective(tzinfo):
1806 def tzname(self, dt): return dt and "real" or "none"
Tim Peters397301e2003-01-02 21:28:08 +00001807 def utcoffset(self, dt):
1808 return timedelta(minutes = dt and 42 or -42)
Tim Petersbad8ff02002-12-30 20:52:32 +00001809 dst = utcoffset
1810
1811 obj = cls(1, 2, 3, tzinfo=introspective())
1812
Tim Peters0bf60bd2003-01-08 20:40:01 +00001813 expected = cls is time and "none" or "real"
Tim Petersbad8ff02002-12-30 20:52:32 +00001814 self.assertEqual(obj.tzname(), expected)
1815
Tim Peters0bf60bd2003-01-08 20:40:01 +00001816 expected = timedelta(minutes=(cls is time and -42 or 42))
Tim Petersbad8ff02002-12-30 20:52:32 +00001817 self.assertEqual(obj.utcoffset(), expected)
1818 self.assertEqual(obj.dst(), expected)
1819
Tim Peters855fe882002-12-22 03:43:39 +00001820 def test_bad_tzinfo_classes(self):
1821 cls = self.theclass
1822 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
Tim Peters2a799bf2002-12-16 20:18:38 +00001823
Tim Peters855fe882002-12-22 03:43:39 +00001824 class NiceTry(object):
1825 def __init__(self): pass
1826 def utcoffset(self, dt): pass
1827 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
1828
1829 class BetterTry(tzinfo):
1830 def __init__(self): pass
1831 def utcoffset(self, dt): pass
1832 b = BetterTry()
1833 t = cls(1, 1, 1, tzinfo=b)
1834 self.failUnless(t.tzinfo is b)
1835
1836 def test_utc_offset_out_of_bounds(self):
1837 class Edgy(tzinfo):
1838 def __init__(self, offset):
Tim Peters397301e2003-01-02 21:28:08 +00001839 self.offset = timedelta(minutes=offset)
Tim Peters855fe882002-12-22 03:43:39 +00001840 def utcoffset(self, dt):
1841 return self.offset
1842
1843 cls = self.theclass
1844 for offset, legit in ((-1440, False),
1845 (-1439, True),
1846 (1439, True),
1847 (1440, False)):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001848 if cls is time:
Tim Peters855fe882002-12-22 03:43:39 +00001849 t = cls(1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001850 elif cls is datetime:
Tim Peters855fe882002-12-22 03:43:39 +00001851 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001852 else:
1853 assert 0, "impossible"
Tim Peters855fe882002-12-22 03:43:39 +00001854 if legit:
1855 aofs = abs(offset)
1856 h, m = divmod(aofs, 60)
1857 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001858 if isinstance(t, datetime):
Tim Peters855fe882002-12-22 03:43:39 +00001859 t = t.timetz()
1860 self.assertEqual(str(t), "01:02:03" + tag)
1861 else:
1862 self.assertRaises(ValueError, str, t)
1863
1864 def test_tzinfo_classes(self):
1865 cls = self.theclass
1866 class C1(tzinfo):
1867 def utcoffset(self, dt): return None
1868 def dst(self, dt): return None
1869 def tzname(self, dt): return None
1870 for t in (cls(1, 1, 1),
1871 cls(1, 1, 1, tzinfo=None),
1872 cls(1, 1, 1, tzinfo=C1())):
1873 self.failUnless(t.utcoffset() is None)
1874 self.failUnless(t.dst() is None)
1875 self.failUnless(t.tzname() is None)
1876
Tim Peters855fe882002-12-22 03:43:39 +00001877 class C3(tzinfo):
1878 def utcoffset(self, dt): return timedelta(minutes=-1439)
1879 def dst(self, dt): return timedelta(minutes=1439)
1880 def tzname(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001881 t = cls(1, 1, 1, tzinfo=C3())
1882 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
1883 self.assertEqual(t.dst(), timedelta(minutes=1439))
1884 self.assertEqual(t.tzname(), "aname")
Tim Peters855fe882002-12-22 03:43:39 +00001885
1886 # Wrong types.
1887 class C4(tzinfo):
1888 def utcoffset(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001889 def dst(self, dt): return 7
Tim Peters855fe882002-12-22 03:43:39 +00001890 def tzname(self, dt): return 0
1891 t = cls(1, 1, 1, tzinfo=C4())
1892 self.assertRaises(TypeError, t.utcoffset)
1893 self.assertRaises(TypeError, t.dst)
1894 self.assertRaises(TypeError, t.tzname)
1895
1896 # Offset out of range.
Tim Peters855fe882002-12-22 03:43:39 +00001897 class C6(tzinfo):
1898 def utcoffset(self, dt): return timedelta(hours=-24)
1899 def dst(self, dt): return timedelta(hours=24)
Tim Peters397301e2003-01-02 21:28:08 +00001900 t = cls(1, 1, 1, tzinfo=C6())
1901 self.assertRaises(ValueError, t.utcoffset)
1902 self.assertRaises(ValueError, t.dst)
Tim Peters855fe882002-12-22 03:43:39 +00001903
1904 # Not a whole number of minutes.
1905 class C7(tzinfo):
1906 def utcoffset(self, dt): return timedelta(seconds=61)
1907 def dst(self, dt): return timedelta(microseconds=-81)
1908 t = cls(1, 1, 1, tzinfo=C7())
1909 self.assertRaises(ValueError, t.utcoffset)
1910 self.assertRaises(ValueError, t.dst)
1911
Tim Peters4c0db782002-12-26 05:01:19 +00001912 def test_aware_compare(self):
1913 cls = self.theclass
1914
Tim Peters60c76e42002-12-27 00:41:11 +00001915 # Ensure that utcoffset() gets ignored if the comparands have
1916 # the same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00001917 class OperandDependentOffset(tzinfo):
1918 def utcoffset(self, t):
1919 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00001920 # d0 and d1 equal after adjustment
1921 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00001922 else:
Tim Peters397301e2003-01-02 21:28:08 +00001923 # d2 off in the weeds
1924 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00001925
1926 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
1927 d0 = base.replace(minute=3)
1928 d1 = base.replace(minute=9)
1929 d2 = base.replace(minute=11)
1930 for x in d0, d1, d2:
1931 for y in d0, d1, d2:
1932 got = cmp(x, y)
Tim Peters60c76e42002-12-27 00:41:11 +00001933 expected = cmp(x.minute, y.minute)
1934 self.assertEqual(got, expected)
1935
1936 # However, if they're different members, uctoffset is not ignored.
Tim Peters0bf60bd2003-01-08 20:40:01 +00001937 # Note that a time can't actually have an operand-depedent offset,
1938 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
1939 # so skip this test for time.
1940 if cls is not time:
Tim Petersbad8ff02002-12-30 20:52:32 +00001941 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
1942 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
1943 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
1944 for x in d0, d1, d2:
1945 for y in d0, d1, d2:
1946 got = cmp(x, y)
1947 if (x is d0 or x is d1) and (y is d0 or y is d1):
1948 expected = 0
1949 elif x is y is d2:
1950 expected = 0
1951 elif x is d2:
1952 expected = -1
1953 else:
1954 assert y is d2
1955 expected = 1
1956 self.assertEqual(got, expected)
Tim Peters4c0db782002-12-26 05:01:19 +00001957
Tim Peters855fe882002-12-22 03:43:39 +00001958
Tim Peters0bf60bd2003-01-08 20:40:01 +00001959# Testing time objects with a non-None tzinfo.
Tim Peters855fe882002-12-22 03:43:39 +00001960class TestTimeTZ(TestTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001961 theclass = time
Tim Peters2a799bf2002-12-16 20:18:38 +00001962
1963 def test_empty(self):
1964 t = self.theclass()
1965 self.assertEqual(t.hour, 0)
1966 self.assertEqual(t.minute, 0)
1967 self.assertEqual(t.second, 0)
1968 self.assertEqual(t.microsecond, 0)
1969 self.failUnless(t.tzinfo is None)
1970
Tim Peters2a799bf2002-12-16 20:18:38 +00001971 def test_zones(self):
1972 est = FixedOffset(-300, "EST", 1)
1973 utc = FixedOffset(0, "UTC", -2)
1974 met = FixedOffset(60, "MET", 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001975 t1 = time( 7, 47, tzinfo=est)
1976 t2 = time(12, 47, tzinfo=utc)
1977 t3 = time(13, 47, tzinfo=met)
1978 t4 = time(microsecond=40)
1979 t5 = time(microsecond=40, tzinfo=utc)
Tim Peters2a799bf2002-12-16 20:18:38 +00001980
1981 self.assertEqual(t1.tzinfo, est)
1982 self.assertEqual(t2.tzinfo, utc)
1983 self.assertEqual(t3.tzinfo, met)
1984 self.failUnless(t4.tzinfo is None)
1985 self.assertEqual(t5.tzinfo, utc)
1986
Tim Peters855fe882002-12-22 03:43:39 +00001987 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
1988 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
1989 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00001990 self.failUnless(t4.utcoffset() is None)
1991 self.assertRaises(TypeError, t1.utcoffset, "no args")
1992
1993 self.assertEqual(t1.tzname(), "EST")
1994 self.assertEqual(t2.tzname(), "UTC")
1995 self.assertEqual(t3.tzname(), "MET")
1996 self.failUnless(t4.tzname() is None)
1997 self.assertRaises(TypeError, t1.tzname, "no args")
1998
Tim Peters855fe882002-12-22 03:43:39 +00001999 self.assertEqual(t1.dst(), timedelta(minutes=1))
2000 self.assertEqual(t2.dst(), timedelta(minutes=-2))
2001 self.assertEqual(t3.dst(), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +00002002 self.failUnless(t4.dst() is None)
2003 self.assertRaises(TypeError, t1.dst, "no args")
2004
2005 self.assertEqual(hash(t1), hash(t2))
2006 self.assertEqual(hash(t1), hash(t3))
2007 self.assertEqual(hash(t2), hash(t3))
2008
2009 self.assertEqual(t1, t2)
2010 self.assertEqual(t1, t3)
2011 self.assertEqual(t2, t3)
2012 self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
2013 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
2014 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
2015
2016 self.assertEqual(str(t1), "07:47:00-05:00")
2017 self.assertEqual(str(t2), "12:47:00+00:00")
2018 self.assertEqual(str(t3), "13:47:00+01:00")
2019 self.assertEqual(str(t4), "00:00:00.000040")
2020 self.assertEqual(str(t5), "00:00:00.000040+00:00")
2021
2022 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
2023 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
2024 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
2025 self.assertEqual(t4.isoformat(), "00:00:00.000040")
2026 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
2027
Tim Peters0bf60bd2003-01-08 20:40:01 +00002028 d = 'datetime.time'
Tim Peters2a799bf2002-12-16 20:18:38 +00002029 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
2030 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
2031 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
2032 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
2033 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2034
2035 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2036 "07:47:00 %Z=EST %z=-0500")
2037 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2038 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2039
2040 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002041 t1 = time(23, 59, tzinfo=yuck)
Tim Peters2a799bf2002-12-16 20:18:38 +00002042 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2043 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2044
Tim Petersb92bb712002-12-21 17:44:07 +00002045 # Check that an invalid tzname result raises an exception.
2046 class Badtzname(tzinfo):
2047 def tzname(self, dt): return 42
Tim Peters0bf60bd2003-01-08 20:40:01 +00002048 t = time(2, 3, 4, tzinfo=Badtzname())
Tim Petersb92bb712002-12-21 17:44:07 +00002049 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2050 self.assertRaises(TypeError, t.strftime, "%Z")
Tim Peters2a799bf2002-12-16 20:18:38 +00002051
2052 def test_hash_edge_cases(self):
2053 # Offsets that overflow a basic time.
2054 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2055 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2056 self.assertEqual(hash(t1), hash(t2))
2057
2058 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2059 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2060 self.assertEqual(hash(t1), hash(t2))
2061
Tim Peters2a799bf2002-12-16 20:18:38 +00002062 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002063 # Try one without a tzinfo.
2064 args = 20, 59, 16, 64**2
2065 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002066 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002067 green = pickler.dumps(orig, proto)
2068 derived = unpickler.loads(green)
2069 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002070
2071 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002072 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002073 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002074 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002075 green = pickler.dumps(orig, proto)
2076 derived = unpickler.loads(green)
2077 self.assertEqual(orig, derived)
2078 self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
2079 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2080 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002081
2082 def test_more_bool(self):
2083 # Test cases with non-None tzinfo.
2084 cls = self.theclass
2085
2086 t = cls(0, tzinfo=FixedOffset(-300, ""))
2087 self.failUnless(t)
2088
2089 t = cls(5, tzinfo=FixedOffset(-300, ""))
2090 self.failUnless(t)
2091
2092 t = cls(5, tzinfo=FixedOffset(300, ""))
2093 self.failUnless(not t)
2094
2095 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2096 self.failUnless(not t)
2097
2098 # Mostly ensuring this doesn't overflow internally.
2099 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
2100 self.failUnless(t)
2101
2102 # But this should yield a value error -- the utcoffset is bogus.
2103 t = cls(0, tzinfo=FixedOffset(24*60, ""))
2104 self.assertRaises(ValueError, lambda: bool(t))
2105
2106 # Likewise.
2107 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
2108 self.assertRaises(ValueError, lambda: bool(t))
2109
Tim Peters12bf3392002-12-24 05:41:27 +00002110 def test_replace(self):
2111 cls = self.theclass
2112 z100 = FixedOffset(100, "+100")
2113 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2114 args = [1, 2, 3, 4, z100]
2115 base = cls(*args)
2116 self.assertEqual(base, base.replace())
2117
2118 i = 0
2119 for name, newval in (("hour", 5),
2120 ("minute", 6),
2121 ("second", 7),
2122 ("microsecond", 8),
2123 ("tzinfo", zm200)):
2124 newargs = args[:]
2125 newargs[i] = newval
2126 expected = cls(*newargs)
2127 got = base.replace(**{name: newval})
2128 self.assertEqual(expected, got)
2129 i += 1
2130
2131 # Ensure we can get rid of a tzinfo.
2132 self.assertEqual(base.tzname(), "+100")
2133 base2 = base.replace(tzinfo=None)
2134 self.failUnless(base2.tzinfo is None)
2135 self.failUnless(base2.tzname() is None)
2136
2137 # Ensure we can add one.
2138 base3 = base2.replace(tzinfo=z100)
2139 self.assertEqual(base, base3)
2140 self.failUnless(base.tzinfo is base3.tzinfo)
2141
2142 # Out of bounds.
2143 base = cls(1)
2144 self.assertRaises(ValueError, base.replace, hour=24)
2145 self.assertRaises(ValueError, base.replace, minute=-1)
2146 self.assertRaises(ValueError, base.replace, second=100)
2147 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2148
Tim Peters60c76e42002-12-27 00:41:11 +00002149 def test_mixed_compare(self):
2150 t1 = time(1, 2, 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002151 t2 = time(1, 2, 3)
Tim Peters60c76e42002-12-27 00:41:11 +00002152 self.assertEqual(t1, t2)
2153 t2 = t2.replace(tzinfo=None)
2154 self.assertEqual(t1, t2)
2155 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2156 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002157 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2158 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002159
Tim Peters0bf60bd2003-01-08 20:40:01 +00002160 # In time w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002161 class Varies(tzinfo):
2162 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002163 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002164 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002165 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002166 return self.offset
2167
2168 v = Varies()
2169 t1 = t2.replace(tzinfo=v)
2170 t2 = t2.replace(tzinfo=v)
2171 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2172 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2173 self.assertEqual(t1, t2)
2174
2175 # But if they're not identical, it isn't ignored.
2176 t2 = t2.replace(tzinfo=Varies())
2177 self.failUnless(t1 < t2) # t1's offset counter still going up
2178
Tim Petersa98924a2003-05-17 05:55:19 +00002179 def test_subclass_timetz(self):
2180
2181 class C(self.theclass):
2182 theAnswer = 42
2183
2184 def __new__(cls, *args, **kws):
2185 temp = kws.copy()
2186 extra = temp.pop('extra')
2187 result = self.theclass.__new__(cls, *args, **temp)
2188 result.extra = extra
2189 return result
2190
2191 def newmeth(self, start):
2192 return start + self.hour + self.second
2193
2194 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2195
2196 dt1 = self.theclass(*args)
2197 dt2 = C(*args, **{'extra': 7})
2198
2199 self.assertEqual(dt2.__class__, C)
2200 self.assertEqual(dt2.theAnswer, 42)
2201 self.assertEqual(dt2.extra, 7)
2202 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2203 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2204
Tim Peters4c0db782002-12-26 05:01:19 +00002205
Tim Peters0bf60bd2003-01-08 20:40:01 +00002206# Testing datetime objects with a non-None tzinfo.
2207
Tim Peters855fe882002-12-22 03:43:39 +00002208class TestDateTimeTZ(TestDateTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002209 theclass = datetime
Tim Peters2a799bf2002-12-16 20:18:38 +00002210
2211 def test_trivial(self):
2212 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2213 self.assertEqual(dt.year, 1)
2214 self.assertEqual(dt.month, 2)
2215 self.assertEqual(dt.day, 3)
2216 self.assertEqual(dt.hour, 4)
2217 self.assertEqual(dt.minute, 5)
2218 self.assertEqual(dt.second, 6)
2219 self.assertEqual(dt.microsecond, 7)
2220 self.assertEqual(dt.tzinfo, None)
2221
2222 def test_even_more_compare(self):
2223 # The test_compare() and test_more_compare() inherited from TestDate
2224 # and TestDateTime covered non-tzinfo cases.
2225
2226 # Smallest possible after UTC adjustment.
2227 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2228 # Largest possible after UTC adjustment.
2229 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2230 tzinfo=FixedOffset(-1439, ""))
2231
2232 # Make sure those compare correctly, and w/o overflow.
2233 self.failUnless(t1 < t2)
2234 self.failUnless(t1 != t2)
2235 self.failUnless(t2 > t1)
2236
2237 self.failUnless(t1 == t1)
2238 self.failUnless(t2 == t2)
2239
2240 # Equal afer adjustment.
2241 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2242 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2243 self.assertEqual(t1, t2)
2244
2245 # Change t1 not to subtract a minute, and t1 should be larger.
2246 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2247 self.failUnless(t1 > t2)
2248
2249 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2250 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2251 self.failUnless(t1 < t2)
2252
2253 # Back to the original t1, but make seconds resolve it.
2254 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2255 second=1)
2256 self.failUnless(t1 > t2)
2257
2258 # Likewise, but make microseconds resolve it.
2259 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2260 microsecond=1)
2261 self.failUnless(t1 > t2)
2262
2263 # Make t2 naive and it should fail.
2264 t2 = self.theclass.min
2265 self.assertRaises(TypeError, lambda: t1 == t2)
2266 self.assertEqual(t2, t2)
2267
2268 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2269 class Naive(tzinfo):
2270 def utcoffset(self, dt): return None
2271 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
2272 self.assertRaises(TypeError, lambda: t1 == t2)
2273 self.assertEqual(t2, t2)
2274
2275 # OTOH, it's OK to compare two of these mixing the two ways of being
2276 # naive.
2277 t1 = self.theclass(5, 6, 7)
2278 self.assertEqual(t1, t2)
2279
2280 # Try a bogus uctoffset.
2281 class Bogus(tzinfo):
Tim Peters397301e2003-01-02 21:28:08 +00002282 def utcoffset(self, dt):
2283 return timedelta(minutes=1440) # out of bounds
Tim Peters2a799bf2002-12-16 20:18:38 +00002284 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2285 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
Tim Peters60c76e42002-12-27 00:41:11 +00002286 self.assertRaises(ValueError, lambda: t1 == t2)
Tim Peters2a799bf2002-12-16 20:18:38 +00002287
Tim Peters2a799bf2002-12-16 20:18:38 +00002288 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002289 # Try one without a tzinfo.
2290 args = 6, 7, 23, 20, 59, 1, 64**2
2291 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002292 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002293 green = pickler.dumps(orig, proto)
2294 derived = unpickler.loads(green)
2295 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002296
2297 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002298 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002299 orig = self.theclass(*args, **{'tzinfo': tinfo})
Tim Petersa9bc1682003-01-11 03:39:11 +00002300 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
Guido van Rossum177e41a2003-01-30 22:06:23 +00002301 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002302 green = pickler.dumps(orig, proto)
2303 derived = unpickler.loads(green)
2304 self.assertEqual(orig, derived)
2305 self.failUnless(isinstance(derived.tzinfo,
2306 PicklableFixedOffset))
2307 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2308 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002309
2310 def test_extreme_hashes(self):
2311 # If an attempt is made to hash these via subtracting the offset
2312 # then hashing a datetime object, OverflowError results. The
2313 # Python implementation used to blow up here.
2314 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2315 hash(t)
2316 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2317 tzinfo=FixedOffset(-1439, ""))
2318 hash(t)
2319
2320 # OTOH, an OOB offset should blow up.
2321 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2322 self.assertRaises(ValueError, hash, t)
2323
2324 def test_zones(self):
2325 est = FixedOffset(-300, "EST")
2326 utc = FixedOffset(0, "UTC")
2327 met = FixedOffset(60, "MET")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002328 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2329 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2330 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
Tim Peters2a799bf2002-12-16 20:18:38 +00002331 self.assertEqual(t1.tzinfo, est)
2332 self.assertEqual(t2.tzinfo, utc)
2333 self.assertEqual(t3.tzinfo, met)
Tim Peters855fe882002-12-22 03:43:39 +00002334 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2335 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2336 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002337 self.assertEqual(t1.tzname(), "EST")
2338 self.assertEqual(t2.tzname(), "UTC")
2339 self.assertEqual(t3.tzname(), "MET")
2340 self.assertEqual(hash(t1), hash(t2))
2341 self.assertEqual(hash(t1), hash(t3))
2342 self.assertEqual(hash(t2), hash(t3))
2343 self.assertEqual(t1, t2)
2344 self.assertEqual(t1, t3)
2345 self.assertEqual(t2, t3)
2346 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2347 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2348 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002349 d = 'datetime.datetime(2002, 3, 19, '
Tim Peters2a799bf2002-12-16 20:18:38 +00002350 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2351 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2352 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2353
2354 def test_combine(self):
2355 met = FixedOffset(60, "MET")
2356 d = date(2002, 3, 4)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002357 tz = time(18, 45, 3, 1234, tzinfo=met)
2358 dt = datetime.combine(d, tz)
2359 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
Tim Peters2a799bf2002-12-16 20:18:38 +00002360 tzinfo=met))
2361
2362 def test_extract(self):
2363 met = FixedOffset(60, "MET")
2364 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2365 self.assertEqual(dt.date(), date(2002, 3, 4))
2366 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
Tim Peters0bf60bd2003-01-08 20:40:01 +00002367 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
Tim Peters2a799bf2002-12-16 20:18:38 +00002368
2369 def test_tz_aware_arithmetic(self):
2370 import random
2371
2372 now = self.theclass.now()
2373 tz55 = FixedOffset(-330, "west 5:30")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002374 timeaware = now.time().replace(tzinfo=tz55)
Tim Peters2a799bf2002-12-16 20:18:38 +00002375 nowaware = self.theclass.combine(now.date(), timeaware)
2376 self.failUnless(nowaware.tzinfo is tz55)
2377 self.assertEqual(nowaware.timetz(), timeaware)
2378
2379 # Can't mix aware and non-aware.
2380 self.assertRaises(TypeError, lambda: now - nowaware)
2381 self.assertRaises(TypeError, lambda: nowaware - now)
2382
Tim Peters0bf60bd2003-01-08 20:40:01 +00002383 # And adding datetime's doesn't make sense, aware or not.
Tim Peters2a799bf2002-12-16 20:18:38 +00002384 self.assertRaises(TypeError, lambda: now + nowaware)
2385 self.assertRaises(TypeError, lambda: nowaware + now)
2386 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2387
2388 # Subtracting should yield 0.
2389 self.assertEqual(now - now, timedelta(0))
2390 self.assertEqual(nowaware - nowaware, timedelta(0))
2391
2392 # Adding a delta should preserve tzinfo.
2393 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2394 nowawareplus = nowaware + delta
2395 self.failUnless(nowaware.tzinfo is tz55)
2396 nowawareplus2 = delta + nowaware
2397 self.failUnless(nowawareplus2.tzinfo is tz55)
2398 self.assertEqual(nowawareplus, nowawareplus2)
2399
2400 # that - delta should be what we started with, and that - what we
2401 # started with should be delta.
2402 diff = nowawareplus - delta
2403 self.failUnless(diff.tzinfo is tz55)
2404 self.assertEqual(nowaware, diff)
2405 self.assertRaises(TypeError, lambda: delta - nowawareplus)
2406 self.assertEqual(nowawareplus - nowaware, delta)
2407
2408 # Make up a random timezone.
2409 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
Tim Peters4c0db782002-12-26 05:01:19 +00002410 # Attach it to nowawareplus.
2411 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Tim Peters2a799bf2002-12-16 20:18:38 +00002412 self.failUnless(nowawareplus.tzinfo is tzr)
2413 # Make sure the difference takes the timezone adjustments into account.
2414 got = nowaware - nowawareplus
2415 # Expected: (nowaware base - nowaware offset) -
2416 # (nowawareplus base - nowawareplus offset) =
2417 # (nowaware base - nowawareplus base) +
2418 # (nowawareplus offset - nowaware offset) =
2419 # -delta + nowawareplus offset - nowaware offset
Tim Peters855fe882002-12-22 03:43:39 +00002420 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
Tim Peters2a799bf2002-12-16 20:18:38 +00002421 self.assertEqual(got, expected)
2422
2423 # Try max possible difference.
2424 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2425 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2426 tzinfo=FixedOffset(-1439, "max"))
2427 maxdiff = max - min
2428 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2429 timedelta(minutes=2*1439))
2430
2431 def test_tzinfo_now(self):
2432 meth = self.theclass.now
2433 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2434 base = meth()
2435 # Try with and without naming the keyword.
2436 off42 = FixedOffset(42, "42")
2437 another = meth(off42)
Tim Peters10cadce2003-01-23 19:58:02 +00002438 again = meth(tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002439 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002440 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002441 # Bad argument with and w/o naming the keyword.
2442 self.assertRaises(TypeError, meth, 16)
2443 self.assertRaises(TypeError, meth, tzinfo=16)
2444 # Bad keyword name.
2445 self.assertRaises(TypeError, meth, tinfo=off42)
2446 # Too many args.
2447 self.assertRaises(TypeError, meth, off42, off42)
2448
Tim Peters10cadce2003-01-23 19:58:02 +00002449 # We don't know which time zone we're in, and don't have a tzinfo
2450 # class to represent it, so seeing whether a tz argument actually
2451 # does a conversion is tricky.
2452 weirdtz = FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0)
2453 utc = FixedOffset(0, "utc", 0)
2454 for dummy in range(3):
2455 now = datetime.now(weirdtz)
2456 self.failUnless(now.tzinfo is weirdtz)
2457 utcnow = datetime.utcnow().replace(tzinfo=utc)
2458 now2 = utcnow.astimezone(weirdtz)
2459 if abs(now - now2) < timedelta(seconds=30):
2460 break
2461 # Else the code is broken, or more than 30 seconds passed between
2462 # calls; assuming the latter, just try again.
2463 else:
2464 # Three strikes and we're out.
2465 self.fail("utcnow(), now(tz), or astimezone() may be broken")
2466
Tim Peters2a799bf2002-12-16 20:18:38 +00002467 def test_tzinfo_fromtimestamp(self):
2468 import time
2469 meth = self.theclass.fromtimestamp
2470 ts = time.time()
2471 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2472 base = meth(ts)
2473 # Try with and without naming the keyword.
2474 off42 = FixedOffset(42, "42")
2475 another = meth(ts, off42)
Tim Peters2a44a8d2003-01-23 20:53:10 +00002476 again = meth(ts, tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002477 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002478 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002479 # Bad argument with and w/o naming the keyword.
2480 self.assertRaises(TypeError, meth, ts, 16)
2481 self.assertRaises(TypeError, meth, ts, tzinfo=16)
2482 # Bad keyword name.
2483 self.assertRaises(TypeError, meth, ts, tinfo=off42)
2484 # Too many args.
2485 self.assertRaises(TypeError, meth, ts, off42, off42)
2486 # Too few args.
2487 self.assertRaises(TypeError, meth)
2488
Tim Peters2a44a8d2003-01-23 20:53:10 +00002489 # Try to make sure tz= actually does some conversion.
Tim Peters84407612003-02-06 16:42:14 +00002490 timestamp = 1000000000
2491 utcdatetime = datetime.utcfromtimestamp(timestamp)
2492 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
2493 # But on some flavor of Mac, it's nowhere near that. So we can't have
2494 # any idea here what time that actually is, we can only test that
2495 # relative changes match.
2496 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
2497 tz = FixedOffset(utcoffset, "tz", 0)
2498 expected = utcdatetime + utcoffset
2499 got = datetime.fromtimestamp(timestamp, tz)
2500 self.assertEqual(expected, got.replace(tzinfo=None))
Tim Peters2a44a8d2003-01-23 20:53:10 +00002501
Tim Peters2a799bf2002-12-16 20:18:38 +00002502 def test_tzinfo_utcnow(self):
2503 meth = self.theclass.utcnow
2504 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2505 base = meth()
2506 # Try with and without naming the keyword; for whatever reason,
2507 # utcnow() doesn't accept a tzinfo argument.
2508 off42 = FixedOffset(42, "42")
2509 self.assertRaises(TypeError, meth, off42)
2510 self.assertRaises(TypeError, meth, tzinfo=off42)
2511
2512 def test_tzinfo_utcfromtimestamp(self):
2513 import time
2514 meth = self.theclass.utcfromtimestamp
2515 ts = time.time()
2516 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2517 base = meth(ts)
2518 # Try with and without naming the keyword; for whatever reason,
2519 # utcfromtimestamp() doesn't accept a tzinfo argument.
2520 off42 = FixedOffset(42, "42")
2521 self.assertRaises(TypeError, meth, ts, off42)
2522 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
2523
2524 def test_tzinfo_timetuple(self):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002525 # TestDateTime tested most of this. datetime adds a twist to the
Tim Peters2a799bf2002-12-16 20:18:38 +00002526 # DST flag.
2527 class DST(tzinfo):
2528 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002529 if isinstance(dstvalue, int):
2530 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002531 self.dstvalue = dstvalue
2532 def dst(self, dt):
2533 return self.dstvalue
2534
2535 cls = self.theclass
2536 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
2537 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
2538 t = d.timetuple()
2539 self.assertEqual(1, t.tm_year)
2540 self.assertEqual(1, t.tm_mon)
2541 self.assertEqual(1, t.tm_mday)
2542 self.assertEqual(10, t.tm_hour)
2543 self.assertEqual(20, t.tm_min)
2544 self.assertEqual(30, t.tm_sec)
2545 self.assertEqual(0, t.tm_wday)
2546 self.assertEqual(1, t.tm_yday)
2547 self.assertEqual(flag, t.tm_isdst)
2548
2549 # dst() returns wrong type.
2550 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
2551
2552 # dst() at the edge.
2553 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
2554 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
2555
2556 # dst() out of range.
2557 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
2558 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
2559
2560 def test_utctimetuple(self):
2561 class DST(tzinfo):
2562 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002563 if isinstance(dstvalue, int):
2564 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002565 self.dstvalue = dstvalue
2566 def dst(self, dt):
2567 return self.dstvalue
2568
2569 cls = self.theclass
2570 # This can't work: DST didn't implement utcoffset.
2571 self.assertRaises(NotImplementedError,
2572 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
2573
2574 class UOFS(DST):
2575 def __init__(self, uofs, dofs=None):
2576 DST.__init__(self, dofs)
Tim Peters397301e2003-01-02 21:28:08 +00002577 self.uofs = timedelta(minutes=uofs)
Tim Peters2a799bf2002-12-16 20:18:38 +00002578 def utcoffset(self, dt):
2579 return self.uofs
2580
2581 # Ensure tm_isdst is 0 regardless of what dst() says: DST is never
2582 # in effect for a UTC time.
2583 for dstvalue in -33, 33, 0, None:
2584 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
2585 t = d.utctimetuple()
2586 self.assertEqual(d.year, t.tm_year)
2587 self.assertEqual(d.month, t.tm_mon)
2588 self.assertEqual(d.day, t.tm_mday)
2589 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
2590 self.assertEqual(13, t.tm_min)
2591 self.assertEqual(d.second, t.tm_sec)
2592 self.assertEqual(d.weekday(), t.tm_wday)
2593 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
2594 t.tm_yday)
2595 self.assertEqual(0, t.tm_isdst)
2596
2597 # At the edges, UTC adjustment can normalize into years out-of-range
2598 # for a datetime object. Ensure that a correct timetuple is
2599 # created anyway.
2600 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
2601 # That goes back 1 minute less than a full day.
2602 t = tiny.utctimetuple()
2603 self.assertEqual(t.tm_year, MINYEAR-1)
2604 self.assertEqual(t.tm_mon, 12)
2605 self.assertEqual(t.tm_mday, 31)
2606 self.assertEqual(t.tm_hour, 0)
2607 self.assertEqual(t.tm_min, 1)
2608 self.assertEqual(t.tm_sec, 37)
2609 self.assertEqual(t.tm_yday, 366) # "year 0" is a leap year
2610 self.assertEqual(t.tm_isdst, 0)
2611
2612 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
2613 # That goes forward 1 minute less than a full day.
2614 t = huge.utctimetuple()
2615 self.assertEqual(t.tm_year, MAXYEAR+1)
2616 self.assertEqual(t.tm_mon, 1)
2617 self.assertEqual(t.tm_mday, 1)
2618 self.assertEqual(t.tm_hour, 23)
2619 self.assertEqual(t.tm_min, 58)
2620 self.assertEqual(t.tm_sec, 37)
2621 self.assertEqual(t.tm_yday, 1)
2622 self.assertEqual(t.tm_isdst, 0)
2623
2624 def test_tzinfo_isoformat(self):
2625 zero = FixedOffset(0, "+00:00")
2626 plus = FixedOffset(220, "+03:40")
2627 minus = FixedOffset(-231, "-03:51")
2628 unknown = FixedOffset(None, "")
2629
2630 cls = self.theclass
2631 datestr = '0001-02-03'
2632 for ofs in None, zero, plus, minus, unknown:
Tim Peters6578dc92002-12-24 18:31:27 +00002633 for us in 0, 987001:
Tim Peters2a799bf2002-12-16 20:18:38 +00002634 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
2635 timestr = '04:05:59' + (us and '.987001' or '')
2636 ofsstr = ofs is not None and d.tzname() or ''
2637 tailstr = timestr + ofsstr
2638 iso = d.isoformat()
2639 self.assertEqual(iso, datestr + 'T' + tailstr)
2640 self.assertEqual(iso, d.isoformat('T'))
2641 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
2642 self.assertEqual(str(d), datestr + ' ' + tailstr)
2643
Tim Peters12bf3392002-12-24 05:41:27 +00002644 def test_replace(self):
2645 cls = self.theclass
2646 z100 = FixedOffset(100, "+100")
2647 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2648 args = [1, 2, 3, 4, 5, 6, 7, z100]
2649 base = cls(*args)
2650 self.assertEqual(base, base.replace())
2651
2652 i = 0
2653 for name, newval in (("year", 2),
2654 ("month", 3),
2655 ("day", 4),
2656 ("hour", 5),
2657 ("minute", 6),
2658 ("second", 7),
2659 ("microsecond", 8),
2660 ("tzinfo", zm200)):
2661 newargs = args[:]
2662 newargs[i] = newval
2663 expected = cls(*newargs)
2664 got = base.replace(**{name: newval})
2665 self.assertEqual(expected, got)
2666 i += 1
2667
2668 # Ensure we can get rid of a tzinfo.
2669 self.assertEqual(base.tzname(), "+100")
2670 base2 = base.replace(tzinfo=None)
2671 self.failUnless(base2.tzinfo is None)
2672 self.failUnless(base2.tzname() is None)
2673
2674 # Ensure we can add one.
2675 base3 = base2.replace(tzinfo=z100)
2676 self.assertEqual(base, base3)
2677 self.failUnless(base.tzinfo is base3.tzinfo)
2678
2679 # Out of bounds.
2680 base = cls(2000, 2, 29)
2681 self.assertRaises(ValueError, base.replace, year=2001)
Tim Peters2a799bf2002-12-16 20:18:38 +00002682
Tim Peters80475bb2002-12-25 07:40:55 +00002683 def test_more_astimezone(self):
2684 # The inherited test_astimezone covered some trivial and error cases.
2685 fnone = FixedOffset(None, "None")
2686 f44m = FixedOffset(44, "44")
2687 fm5h = FixedOffset(-timedelta(hours=5), "m300")
2688
Tim Peters10cadce2003-01-23 19:58:02 +00002689 dt = self.theclass.now(tz=f44m)
Tim Peters80475bb2002-12-25 07:40:55 +00002690 self.failUnless(dt.tzinfo is f44m)
Tim Peters52dcce22003-01-23 16:36:11 +00002691 # Replacing with degenerate tzinfo raises an exception.
2692 self.assertRaises(ValueError, dt.astimezone, fnone)
2693 # Ditto with None tz.
2694 self.assertRaises(TypeError, dt.astimezone, None)
2695 # Replacing with same tzinfo makes no change.
Tim Peters80475bb2002-12-25 07:40:55 +00002696 x = dt.astimezone(dt.tzinfo)
2697 self.failUnless(x.tzinfo is f44m)
2698 self.assertEqual(x.date(), dt.date())
2699 self.assertEqual(x.time(), dt.time())
2700
2701 # Replacing with different tzinfo does adjust.
2702 got = dt.astimezone(fm5h)
2703 self.failUnless(got.tzinfo is fm5h)
2704 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
2705 expected = dt - dt.utcoffset() # in effect, convert to UTC
2706 expected += fm5h.utcoffset(dt) # and from there to local time
2707 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
2708 self.assertEqual(got.date(), expected.date())
2709 self.assertEqual(got.time(), expected.time())
2710 self.assertEqual(got.timetz(), expected.timetz())
2711 self.failUnless(got.tzinfo is expected.tzinfo)
2712 self.assertEqual(got, expected)
2713
Tim Peters4c0db782002-12-26 05:01:19 +00002714 def test_aware_subtract(self):
2715 cls = self.theclass
2716
Tim Peters60c76e42002-12-27 00:41:11 +00002717 # Ensure that utcoffset() is ignored when the operands have the
2718 # same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00002719 class OperandDependentOffset(tzinfo):
2720 def utcoffset(self, t):
2721 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00002722 # d0 and d1 equal after adjustment
2723 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00002724 else:
Tim Peters397301e2003-01-02 21:28:08 +00002725 # d2 off in the weeds
2726 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00002727
2728 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
2729 d0 = base.replace(minute=3)
2730 d1 = base.replace(minute=9)
2731 d2 = base.replace(minute=11)
2732 for x in d0, d1, d2:
2733 for y in d0, d1, d2:
2734 got = x - y
Tim Peters60c76e42002-12-27 00:41:11 +00002735 expected = timedelta(minutes=x.minute - y.minute)
2736 self.assertEqual(got, expected)
2737
2738 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
2739 # ignored.
2740 base = cls(8, 9, 10, 11, 12, 13, 14)
2741 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2742 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2743 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2744 for x in d0, d1, d2:
2745 for y in d0, d1, d2:
2746 got = x - y
Tim Peters4c0db782002-12-26 05:01:19 +00002747 if (x is d0 or x is d1) and (y is d0 or y is d1):
2748 expected = timedelta(0)
2749 elif x is y is d2:
2750 expected = timedelta(0)
2751 elif x is d2:
2752 expected = timedelta(minutes=(11-59)-0)
2753 else:
2754 assert y is d2
2755 expected = timedelta(minutes=0-(11-59))
2756 self.assertEqual(got, expected)
2757
Tim Peters60c76e42002-12-27 00:41:11 +00002758 def test_mixed_compare(self):
2759 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002760 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters60c76e42002-12-27 00:41:11 +00002761 self.assertEqual(t1, t2)
2762 t2 = t2.replace(tzinfo=None)
2763 self.assertEqual(t1, t2)
2764 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2765 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002766 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2767 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002768
Tim Peters0bf60bd2003-01-08 20:40:01 +00002769 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002770 class Varies(tzinfo):
2771 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002772 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002773 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002774 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002775 return self.offset
2776
2777 v = Varies()
2778 t1 = t2.replace(tzinfo=v)
2779 t2 = t2.replace(tzinfo=v)
2780 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2781 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2782 self.assertEqual(t1, t2)
2783
2784 # But if they're not identical, it isn't ignored.
2785 t2 = t2.replace(tzinfo=Varies())
2786 self.failUnless(t1 < t2) # t1's offset counter still going up
Tim Peters80475bb2002-12-25 07:40:55 +00002787
Tim Petersa98924a2003-05-17 05:55:19 +00002788 def test_subclass_datetimetz(self):
2789
2790 class C(self.theclass):
2791 theAnswer = 42
2792
2793 def __new__(cls, *args, **kws):
2794 temp = kws.copy()
2795 extra = temp.pop('extra')
2796 result = self.theclass.__new__(cls, *args, **temp)
2797 result.extra = extra
2798 return result
2799
2800 def newmeth(self, start):
2801 return start + self.hour + self.year
2802
2803 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2804
2805 dt1 = self.theclass(*args)
2806 dt2 = C(*args, **{'extra': 7})
2807
2808 self.assertEqual(dt2.__class__, C)
2809 self.assertEqual(dt2.theAnswer, 42)
2810 self.assertEqual(dt2.extra, 7)
2811 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2812 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
2813
Tim Peters621818b2002-12-29 23:44:49 +00002814# Pain to set up DST-aware tzinfo classes.
2815
2816def first_sunday_on_or_after(dt):
2817 days_to_go = 6 - dt.weekday()
2818 if days_to_go:
2819 dt += timedelta(days_to_go)
2820 return dt
2821
2822ZERO = timedelta(0)
2823HOUR = timedelta(hours=1)
2824DAY = timedelta(days=1)
2825# In the US, DST starts at 2am (standard time) on the first Sunday in April.
2826DSTSTART = datetime(1, 4, 1, 2)
2827# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
Tim Peters327098a2003-01-20 22:54:38 +00002828# which is the first Sunday on or after Oct 25. Because we view 1:MM as
2829# being standard time on that day, there is no spelling in local time of
2830# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
2831DSTEND = datetime(1, 10, 25, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002832
2833class USTimeZone(tzinfo):
2834
2835 def __init__(self, hours, reprname, stdname, dstname):
2836 self.stdoffset = timedelta(hours=hours)
2837 self.reprname = reprname
2838 self.stdname = stdname
2839 self.dstname = dstname
2840
2841 def __repr__(self):
2842 return self.reprname
2843
2844 def tzname(self, dt):
2845 if self.dst(dt):
2846 return self.dstname
2847 else:
2848 return self.stdname
2849
2850 def utcoffset(self, dt):
2851 return self.stdoffset + self.dst(dt)
2852
2853 def dst(self, dt):
Tim Petersbad8ff02002-12-30 20:52:32 +00002854 if dt is None or dt.tzinfo is None:
Tim Peters621818b2002-12-29 23:44:49 +00002855 # An exception instead may be sensible here, in one or more of
2856 # the cases.
2857 return ZERO
Tim Peters521fc152002-12-31 17:36:56 +00002858 assert dt.tzinfo is self
Tim Peters621818b2002-12-29 23:44:49 +00002859
2860 # Find first Sunday in April.
2861 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
2862 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
2863
2864 # Find last Sunday in October.
2865 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
2866 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
2867
Tim Peters621818b2002-12-29 23:44:49 +00002868 # Can't compare naive to aware objects, so strip the timezone from
2869 # dt first.
Tim Peters52dcce22003-01-23 16:36:11 +00002870 if start <= dt.replace(tzinfo=None) < end:
Tim Peters621818b2002-12-29 23:44:49 +00002871 return HOUR
2872 else:
2873 return ZERO
2874
Tim Peters521fc152002-12-31 17:36:56 +00002875Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
2876Central = USTimeZone(-6, "Central", "CST", "CDT")
2877Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
2878Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
Tim Peters1024bf82002-12-30 17:09:40 +00002879utc_real = FixedOffset(0, "UTC", 0)
2880# For better test coverage, we want another flavor of UTC that's west of
2881# the Eastern and Pacific timezones.
Tim Petersadf64202003-01-04 06:03:15 +00002882utc_fake = FixedOffset(-12*60, "UTCfake", 0)
Tim Peters621818b2002-12-29 23:44:49 +00002883
2884class TestTimezoneConversions(unittest.TestCase):
Tim Peters327098a2003-01-20 22:54:38 +00002885 # The DST switch times for 2002, in std time.
Tim Peters0bf60bd2003-01-08 20:40:01 +00002886 dston = datetime(2002, 4, 7, 2)
Tim Peters327098a2003-01-20 22:54:38 +00002887 dstoff = datetime(2002, 10, 27, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002888
Tim Peters0bf60bd2003-01-08 20:40:01 +00002889 theclass = datetime
Tim Peters710fb152003-01-02 19:35:54 +00002890
Tim Peters521fc152002-12-31 17:36:56 +00002891 # Check a time that's inside DST.
2892 def checkinside(self, dt, tz, utc, dston, dstoff):
2893 self.assertEqual(dt.dst(), HOUR)
2894
2895 # Conversion to our own timezone is always an identity.
2896 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters521fc152002-12-31 17:36:56 +00002897
2898 asutc = dt.astimezone(utc)
2899 there_and_back = asutc.astimezone(tz)
2900
2901 # Conversion to UTC and back isn't always an identity here,
2902 # because there are redundant spellings (in local time) of
2903 # UTC time when DST begins: the clock jumps from 1:59:59
2904 # to 3:00:00, and a local time of 2:MM:SS doesn't really
2905 # make sense then. The classes above treat 2:MM:SS as
2906 # daylight time then (it's "after 2am"), really an alias
2907 # for 1:MM:SS standard time. The latter form is what
2908 # conversion back from UTC produces.
2909 if dt.date() == dston.date() and dt.hour == 2:
2910 # We're in the redundant hour, and coming back from
2911 # UTC gives the 1:MM:SS standard-time spelling.
2912 self.assertEqual(there_and_back + HOUR, dt)
2913 # Although during was considered to be in daylight
2914 # time, there_and_back is not.
2915 self.assertEqual(there_and_back.dst(), ZERO)
2916 # They're the same times in UTC.
2917 self.assertEqual(there_and_back.astimezone(utc),
2918 dt.astimezone(utc))
2919 else:
2920 # We're not in the redundant hour.
2921 self.assertEqual(dt, there_and_back)
2922
Tim Peters327098a2003-01-20 22:54:38 +00002923 # Because we have a redundant spelling when DST begins, there is
2924 # (unforunately) an hour when DST ends that can't be spelled at all in
2925 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
2926 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
2927 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
2928 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
2929 # expressed in local time. Nevertheless, we want conversion back
2930 # from UTC to mimic the local clock's "repeat an hour" behavior.
Tim Peters521fc152002-12-31 17:36:56 +00002931 nexthour_utc = asutc + HOUR
Tim Petersadf64202003-01-04 06:03:15 +00002932 nexthour_tz = nexthour_utc.astimezone(tz)
Tim Peters327098a2003-01-20 22:54:38 +00002933 if dt.date() == dstoff.date() and dt.hour == 0:
2934 # We're in the hour before the last DST hour. The last DST hour
Tim Petersadf64202003-01-04 06:03:15 +00002935 # is ineffable. We want the conversion back to repeat 1:MM.
Tim Peters327098a2003-01-20 22:54:38 +00002936 self.assertEqual(nexthour_tz, dt.replace(hour=1))
2937 nexthour_utc += HOUR
2938 nexthour_tz = nexthour_utc.astimezone(tz)
2939 self.assertEqual(nexthour_tz, dt.replace(hour=1))
Tim Peters521fc152002-12-31 17:36:56 +00002940 else:
Tim Peters327098a2003-01-20 22:54:38 +00002941 self.assertEqual(nexthour_tz - dt, HOUR)
Tim Peters521fc152002-12-31 17:36:56 +00002942
2943 # Check a time that's outside DST.
2944 def checkoutside(self, dt, tz, utc):
2945 self.assertEqual(dt.dst(), ZERO)
2946
2947 # Conversion to our own timezone is always an identity.
2948 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters52dcce22003-01-23 16:36:11 +00002949
2950 # Converting to UTC and back is an identity too.
2951 asutc = dt.astimezone(utc)
2952 there_and_back = asutc.astimezone(tz)
2953 self.assertEqual(dt, there_and_back)
Tim Peters521fc152002-12-31 17:36:56 +00002954
Tim Peters1024bf82002-12-30 17:09:40 +00002955 def convert_between_tz_and_utc(self, tz, utc):
2956 dston = self.dston.replace(tzinfo=tz)
Tim Peters327098a2003-01-20 22:54:38 +00002957 # Because 1:MM on the day DST ends is taken as being standard time,
2958 # there is no spelling in tz for the last hour of daylight time.
2959 # For purposes of the test, the last hour of DST is 0:MM, which is
2960 # taken as being daylight time (and 1:MM is taken as being standard
2961 # time).
Tim Peters1024bf82002-12-30 17:09:40 +00002962 dstoff = self.dstoff.replace(tzinfo=tz)
2963 for delta in (timedelta(weeks=13),
2964 DAY,
2965 HOUR,
2966 timedelta(minutes=1),
2967 timedelta(microseconds=1)):
2968
Tim Peters521fc152002-12-31 17:36:56 +00002969 self.checkinside(dston, tz, utc, dston, dstoff)
2970 for during in dston + delta, dstoff - delta:
2971 self.checkinside(during, tz, utc, dston, dstoff)
Tim Peters31cc3152002-12-30 17:37:30 +00002972
Tim Peters521fc152002-12-31 17:36:56 +00002973 self.checkoutside(dstoff, tz, utc)
2974 for outside in dston - delta, dstoff + delta:
2975 self.checkoutside(outside, tz, utc)
Tim Peters31cc3152002-12-30 17:37:30 +00002976
Tim Peters621818b2002-12-29 23:44:49 +00002977 def test_easy(self):
2978 # Despite the name of this test, the endcases are excruciating.
Tim Peters1024bf82002-12-30 17:09:40 +00002979 self.convert_between_tz_and_utc(Eastern, utc_real)
2980 self.convert_between_tz_and_utc(Pacific, utc_real)
2981 self.convert_between_tz_and_utc(Eastern, utc_fake)
2982 self.convert_between_tz_and_utc(Pacific, utc_fake)
2983 # The next is really dancing near the edge. It works because
2984 # Pacific and Eastern are far enough apart that their "problem
2985 # hours" don't overlap.
2986 self.convert_between_tz_and_utc(Eastern, Pacific)
2987 self.convert_between_tz_and_utc(Pacific, Eastern)
Tim Peters36087ed2003-01-01 04:18:51 +00002988 # OTOH, these fail! Don't enable them. The difficulty is that
2989 # the edge case tests assume that every hour is representable in
2990 # the "utc" class. This is always true for a fixed-offset tzinfo
2991 # class (lke utc_real and utc_fake), but not for Eastern or Central.
2992 # For these adjacent DST-aware time zones, the range of time offsets
2993 # tested ends up creating hours in the one that aren't representable
2994 # in the other. For the same reason, we would see failures in the
2995 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
2996 # offset deltas in convert_between_tz_and_utc().
2997 #
2998 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
2999 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
Tim Peters621818b2002-12-29 23:44:49 +00003000
Tim Petersf3615152003-01-01 21:51:37 +00003001 def test_tricky(self):
3002 # 22:00 on day before daylight starts.
3003 fourback = self.dston - timedelta(hours=4)
3004 ninewest = FixedOffset(-9*60, "-0900", 0)
Tim Peters52dcce22003-01-23 16:36:11 +00003005 fourback = fourback.replace(tzinfo=ninewest)
Tim Petersf3615152003-01-01 21:51:37 +00003006 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
3007 # 2", we should get the 3 spelling.
3008 # If we plug 22:00 the day before into Eastern, it "looks like std
3009 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
3010 # to 22:00 lands on 2:00, which makes no sense in local time (the
3011 # local clock jumps from 1 to 3). The point here is to make sure we
3012 # get the 3 spelling.
3013 expected = self.dston.replace(hour=3)
Tim Peters52dcce22003-01-23 16:36:11 +00003014 got = fourback.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00003015 self.assertEqual(expected, got)
3016
3017 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
3018 # case we want the 1:00 spelling.
Tim Peters52dcce22003-01-23 16:36:11 +00003019 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
Tim Petersf3615152003-01-01 21:51:37 +00003020 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
3021 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
3022 # spelling.
3023 expected = self.dston.replace(hour=1)
Tim Peters52dcce22003-01-23 16:36:11 +00003024 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00003025 self.assertEqual(expected, got)
Tim Peters621818b2002-12-29 23:44:49 +00003026
Tim Petersadf64202003-01-04 06:03:15 +00003027 # Now on the day DST ends, we want "repeat an hour" behavior.
3028 # UTC 4:MM 5:MM 6:MM 7:MM checking these
3029 # EST 23:MM 0:MM 1:MM 2:MM
3030 # EDT 0:MM 1:MM 2:MM 3:MM
3031 # wall 0:MM 1:MM 1:MM 2:MM against these
3032 for utc in utc_real, utc_fake:
3033 for tz in Eastern, Pacific:
Tim Peters327098a2003-01-20 22:54:38 +00003034 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
Tim Petersadf64202003-01-04 06:03:15 +00003035 # Convert that to UTC.
3036 first_std_hour -= tz.utcoffset(None)
3037 # Adjust for possibly fake UTC.
3038 asutc = first_std_hour + utc.utcoffset(None)
3039 # First UTC hour to convert; this is 4:00 when utc=utc_real &
3040 # tz=Eastern.
3041 asutcbase = asutc.replace(tzinfo=utc)
3042 for tzhour in (0, 1, 1, 2):
3043 expectedbase = self.dstoff.replace(hour=tzhour)
3044 for minute in 0, 30, 59:
3045 expected = expectedbase.replace(minute=minute)
3046 asutc = asutcbase.replace(minute=minute)
3047 astz = asutc.astimezone(tz)
3048 self.assertEqual(astz.replace(tzinfo=None), expected)
3049 asutcbase += HOUR
3050
3051
Tim Peters710fb152003-01-02 19:35:54 +00003052 def test_bogus_dst(self):
3053 class ok(tzinfo):
3054 def utcoffset(self, dt): return HOUR
3055 def dst(self, dt): return HOUR
3056
3057 now = self.theclass.now().replace(tzinfo=utc_real)
3058 # Doesn't blow up.
3059 now.astimezone(ok())
3060
3061 # Does blow up.
3062 class notok(ok):
3063 def dst(self, dt): return None
3064 self.assertRaises(ValueError, now.astimezone, notok())
3065
Tim Peters52dcce22003-01-23 16:36:11 +00003066 def test_fromutc(self):
3067 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
3068 now = datetime.utcnow().replace(tzinfo=utc_real)
3069 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3070 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
3071 enow = Eastern.fromutc(now) # doesn't blow up
3072 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3073 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3074 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3075
3076 # Always converts UTC to standard time.
3077 class FauxUSTimeZone(USTimeZone):
3078 def fromutc(self, dt):
3079 return dt + self.stdoffset
3080 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
3081
3082 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
3083 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
3084 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
3085
3086 # Check around DST start.
3087 start = self.dston.replace(hour=4, tzinfo=Eastern)
3088 fstart = start.replace(tzinfo=FEastern)
3089 for wall in 23, 0, 1, 3, 4, 5:
3090 expected = start.replace(hour=wall)
3091 if wall == 23:
3092 expected -= timedelta(days=1)
3093 got = Eastern.fromutc(start)
3094 self.assertEqual(expected, got)
3095
3096 expected = fstart + FEastern.stdoffset
3097 got = FEastern.fromutc(fstart)
3098 self.assertEqual(expected, got)
3099
3100 # Ensure astimezone() calls fromutc() too.
3101 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3102 self.assertEqual(expected, got)
3103
3104 start += HOUR
3105 fstart += HOUR
3106
3107 # Check around DST end.
3108 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
3109 fstart = start.replace(tzinfo=FEastern)
3110 for wall in 0, 1, 1, 2, 3, 4:
3111 expected = start.replace(hour=wall)
3112 got = Eastern.fromutc(start)
3113 self.assertEqual(expected, got)
3114
3115 expected = fstart + FEastern.stdoffset
3116 got = FEastern.fromutc(fstart)
3117 self.assertEqual(expected, got)
3118
3119 # Ensure astimezone() calls fromutc() too.
3120 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3121 self.assertEqual(expected, got)
3122
3123 start += HOUR
3124 fstart += HOUR
3125
Tim Peters710fb152003-01-02 19:35:54 +00003126
Tim Peterscfd4a8b2002-12-16 21:12:37 +00003127def test_suite():
Tim Peters2a799bf2002-12-16 20:18:38 +00003128 allsuites = [unittest.makeSuite(klass, 'test')
3129 for klass in (TestModule,
3130 TestTZInfo,
3131 TestTimeDelta,
3132 TestDateOnly,
3133 TestDate,
3134 TestDateTime,
3135 TestTime,
3136 TestTimeTZ,
3137 TestDateTimeTZ,
Tim Peters621818b2002-12-29 23:44:49 +00003138 TestTimezoneConversions,
Tim Peters2a799bf2002-12-16 20:18:38 +00003139 )
3140 ]
3141 return unittest.TestSuite(allsuites)
3142
3143def test_main():
3144 import gc
3145 import sys
3146
Tim Peterscfd4a8b2002-12-16 21:12:37 +00003147 thesuite = test_suite()
Tim Peters2a799bf2002-12-16 20:18:38 +00003148 lastrc = None
3149 while True:
3150 test_support.run_suite(thesuite)
3151 if 1: # change to 0, under a debug build, for some leak detection
3152 break
3153 gc.collect()
3154 if gc.garbage:
3155 raise SystemError("gc.garbage not empty after test run: %r" %
3156 gc.garbage)
3157 if hasattr(sys, 'gettotalrefcount'):
3158 thisrc = sys.gettotalrefcount()
3159 print >> sys.stderr, '*' * 10, 'total refs:', thisrc,
3160 if lastrc:
3161 print >> sys.stderr, 'delta:', thisrc - lastrc
3162 else:
3163 print >> sys.stderr
3164 lastrc = thisrc
3165
3166if __name__ == "__main__":
3167 test_main()