blob: 51b5f4fce3baa56f9c708878c6ab16d84a681a63 [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
446#############################################################################
447# date tests
448
449class TestDateOnly(unittest.TestCase):
450 # Tests here won't pass if also run on datetime objects, so don't
451 # subclass this to test datetimes too.
452
453 def test_delta_non_days_ignored(self):
454 dt = date(2000, 1, 2)
455 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
456 microseconds=5)
457 days = timedelta(delta.days)
458 self.assertEqual(days, timedelta(1))
459
460 dt2 = dt + delta
461 self.assertEqual(dt2, dt + days)
462
463 dt2 = delta + dt
464 self.assertEqual(dt2, dt + days)
465
466 dt2 = dt - delta
467 self.assertEqual(dt2, dt - days)
468
469 delta = -delta
470 days = timedelta(delta.days)
471 self.assertEqual(days, timedelta(-2))
472
473 dt2 = dt + delta
474 self.assertEqual(dt2, dt + days)
475
476 dt2 = delta + dt
477 self.assertEqual(dt2, dt + days)
478
479 dt2 = dt - delta
480 self.assertEqual(dt2, dt - days)
481
Guido van Rossum8b7a9a32003-04-14 22:01:58 +0000482 def test_subclass_date(self):
Tim Peterseb1a4962003-05-17 02:25:20 +0000483
484 # XXX When datetime becomes usably subclassable, uncomment the
485 # XXX "self.theclass" lines and move this into TestDate.
486 # class C(self.theclass):
Guido van Rossum8b7a9a32003-04-14 22:01:58 +0000487 class C(date):
488 theAnswer = 42
Tim Peterseb1a4962003-05-17 02:25:20 +0000489
490 def __new__(cls, *args, **kws):
491 temp = kws.copy()
492 extra = temp.pop('extra')
493 # result = self.theclass.__new__(cls, *args, **temp)
494 result = date.__new__(cls, *args, **temp)
495 result.extra = extra
496 return result
497
498 def newmeth(self, start):
499 return start + self.year + self.month
500
501 args = 2003, 4, 14
502
503 # dt1 = self.theclass(*args)
504 dt1 = date(*args)
505 dt2 = C(*args, **{'extra': 7})
506
507 self.assertEqual(dt2.__class__, C)
508 self.assertEqual(dt2.theAnswer, 42)
509 self.assertEqual(dt2.extra, 7)
510 self.assertEqual(dt1.toordinal(), dt2.toordinal())
511 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
Guido van Rossum8b7a9a32003-04-14 22:01:58 +0000512
Tim Peters07534a62003-02-07 22:50:28 +0000513class TestDate(HarmlessMixedComparison):
Tim Peters2a799bf2002-12-16 20:18:38 +0000514 # Tests here should pass for both dates and datetimes, except for a
515 # few tests that TestDateTime overrides.
516
517 theclass = date
518
519 def test_basic_attributes(self):
520 dt = self.theclass(2002, 3, 1)
521 self.assertEqual(dt.year, 2002)
522 self.assertEqual(dt.month, 3)
523 self.assertEqual(dt.day, 1)
524
525 def test_roundtrip(self):
526 for dt in (self.theclass(1, 2, 3),
527 self.theclass.today()):
528 # Verify dt -> string -> date identity.
529 s = repr(dt)
530 self.failUnless(s.startswith('datetime.'))
531 s = s[9:]
532 dt2 = eval(s)
533 self.assertEqual(dt, dt2)
534
535 # Verify identity via reconstructing from pieces.
536 dt2 = self.theclass(dt.year, dt.month, dt.day)
537 self.assertEqual(dt, dt2)
538
539 def test_ordinal_conversions(self):
540 # Check some fixed values.
541 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
542 (1, 12, 31, 365),
543 (2, 1, 1, 366),
544 # first example from "Calendrical Calculations"
545 (1945, 11, 12, 710347)]:
546 d = self.theclass(y, m, d)
547 self.assertEqual(n, d.toordinal())
548 fromord = self.theclass.fromordinal(n)
549 self.assertEqual(d, fromord)
550 if hasattr(fromord, "hour"):
Tim Petersf2715e02003-02-19 02:35:07 +0000551 # if we're checking something fancier than a date, verify
552 # the extra fields have been zeroed out
Tim Peters2a799bf2002-12-16 20:18:38 +0000553 self.assertEqual(fromord.hour, 0)
554 self.assertEqual(fromord.minute, 0)
555 self.assertEqual(fromord.second, 0)
556 self.assertEqual(fromord.microsecond, 0)
557
Tim Peters0bf60bd2003-01-08 20:40:01 +0000558 # Check first and last days of year spottily across the whole
559 # range of years supported.
560 for year in xrange(MINYEAR, MAXYEAR+1, 7):
Tim Peters2a799bf2002-12-16 20:18:38 +0000561 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
562 d = self.theclass(year, 1, 1)
563 n = d.toordinal()
564 d2 = self.theclass.fromordinal(n)
565 self.assertEqual(d, d2)
Tim Peters0bf60bd2003-01-08 20:40:01 +0000566 # Verify that moving back a day gets to the end of year-1.
567 if year > 1:
568 d = self.theclass.fromordinal(n-1)
569 d2 = self.theclass(year-1, 12, 31)
570 self.assertEqual(d, d2)
571 self.assertEqual(d2.toordinal(), n-1)
Tim Peters2a799bf2002-12-16 20:18:38 +0000572
573 # Test every day in a leap-year and a non-leap year.
574 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
575 for year, isleap in (2000, True), (2002, False):
576 n = self.theclass(year, 1, 1).toordinal()
577 for month, maxday in zip(range(1, 13), dim):
578 if month == 2 and isleap:
579 maxday += 1
580 for day in range(1, maxday+1):
581 d = self.theclass(year, month, day)
582 self.assertEqual(d.toordinal(), n)
583 self.assertEqual(d, self.theclass.fromordinal(n))
584 n += 1
585
586 def test_extreme_ordinals(self):
587 a = self.theclass.min
588 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
589 aord = a.toordinal()
590 b = a.fromordinal(aord)
591 self.assertEqual(a, b)
592
593 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
594
595 b = a + timedelta(days=1)
596 self.assertEqual(b.toordinal(), aord + 1)
597 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
598
599 a = self.theclass.max
600 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
601 aord = a.toordinal()
602 b = a.fromordinal(aord)
603 self.assertEqual(a, b)
604
605 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
606
607 b = a - timedelta(days=1)
608 self.assertEqual(b.toordinal(), aord - 1)
609 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
610
611 def test_bad_constructor_arguments(self):
612 # bad years
613 self.theclass(MINYEAR, 1, 1) # no exception
614 self.theclass(MAXYEAR, 1, 1) # no exception
615 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
616 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
617 # bad months
618 self.theclass(2000, 1, 1) # no exception
619 self.theclass(2000, 12, 1) # no exception
620 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
621 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
622 # bad days
623 self.theclass(2000, 2, 29) # no exception
624 self.theclass(2004, 2, 29) # no exception
625 self.theclass(2400, 2, 29) # no exception
626 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
627 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
628 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
629 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
630 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
631 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
632
633 def test_hash_equality(self):
634 d = self.theclass(2000, 12, 31)
635 # same thing
636 e = self.theclass(2000, 12, 31)
637 self.assertEqual(d, e)
638 self.assertEqual(hash(d), hash(e))
639
640 dic = {d: 1}
641 dic[e] = 2
642 self.assertEqual(len(dic), 1)
643 self.assertEqual(dic[d], 2)
644 self.assertEqual(dic[e], 2)
645
646 d = self.theclass(2001, 1, 1)
647 # same thing
648 e = self.theclass(2001, 1, 1)
649 self.assertEqual(d, e)
650 self.assertEqual(hash(d), hash(e))
651
652 dic = {d: 1}
653 dic[e] = 2
654 self.assertEqual(len(dic), 1)
655 self.assertEqual(dic[d], 2)
656 self.assertEqual(dic[e], 2)
657
658 def test_computations(self):
659 a = self.theclass(2002, 1, 31)
660 b = self.theclass(1956, 1, 31)
661
662 diff = a-b
663 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
664 self.assertEqual(diff.seconds, 0)
665 self.assertEqual(diff.microseconds, 0)
666
667 day = timedelta(1)
668 week = timedelta(7)
669 a = self.theclass(2002, 3, 2)
670 self.assertEqual(a + day, self.theclass(2002, 3, 3))
671 self.assertEqual(day + a, self.theclass(2002, 3, 3))
672 self.assertEqual(a - day, self.theclass(2002, 3, 1))
673 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
674 self.assertEqual(a + week, self.theclass(2002, 3, 9))
675 self.assertEqual(a - week, self.theclass(2002, 2, 23))
676 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
677 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
678 self.assertEqual((a + week) - a, week)
679 self.assertEqual((a + day) - a, day)
680 self.assertEqual((a - week) - a, -week)
681 self.assertEqual((a - day) - a, -day)
682 self.assertEqual(a - (a + week), -week)
683 self.assertEqual(a - (a + day), -day)
684 self.assertEqual(a - (a - week), week)
685 self.assertEqual(a - (a - day), day)
686
687 # Add/sub ints, longs, floats should be illegal
688 for i in 1, 1L, 1.0:
689 self.assertRaises(TypeError, lambda: a+i)
690 self.assertRaises(TypeError, lambda: a-i)
691 self.assertRaises(TypeError, lambda: i+a)
692 self.assertRaises(TypeError, lambda: i-a)
693
694 # delta - date is senseless.
695 self.assertRaises(TypeError, lambda: day - a)
696 # mixing date and (delta or date) via * or // is senseless
697 self.assertRaises(TypeError, lambda: day * a)
698 self.assertRaises(TypeError, lambda: a * day)
699 self.assertRaises(TypeError, lambda: day // a)
700 self.assertRaises(TypeError, lambda: a // day)
701 self.assertRaises(TypeError, lambda: a * a)
702 self.assertRaises(TypeError, lambda: a // a)
703 # date + date is senseless
704 self.assertRaises(TypeError, lambda: a + a)
705
706 def test_overflow(self):
707 tiny = self.theclass.resolution
708
709 dt = self.theclass.min + tiny
710 dt -= tiny # no problem
711 self.assertRaises(OverflowError, dt.__sub__, tiny)
712 self.assertRaises(OverflowError, dt.__add__, -tiny)
713
714 dt = self.theclass.max - tiny
715 dt += tiny # no problem
716 self.assertRaises(OverflowError, dt.__add__, tiny)
717 self.assertRaises(OverflowError, dt.__sub__, -tiny)
718
719 def test_fromtimestamp(self):
720 import time
721
722 # Try an arbitrary fixed value.
723 year, month, day = 1999, 9, 19
724 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
725 d = self.theclass.fromtimestamp(ts)
726 self.assertEqual(d.year, year)
727 self.assertEqual(d.month, month)
728 self.assertEqual(d.day, day)
729
730 def test_today(self):
731 import time
732
733 # We claim that today() is like fromtimestamp(time.time()), so
734 # prove it.
735 for dummy in range(3):
736 today = self.theclass.today()
737 ts = time.time()
738 todayagain = self.theclass.fromtimestamp(ts)
739 if today == todayagain:
740 break
741 # There are several legit reasons that could fail:
742 # 1. It recently became midnight, between the today() and the
743 # time() calls.
744 # 2. The platform time() has such fine resolution that we'll
745 # never get the same value twice.
746 # 3. The platform time() has poor resolution, and we just
747 # happened to call today() right before a resolution quantum
748 # boundary.
749 # 4. The system clock got fiddled between calls.
750 # In any case, wait a little while and try again.
751 time.sleep(0.1)
752
753 # It worked or it didn't. If it didn't, assume it's reason #2, and
754 # let the test pass if they're within half a second of each other.
755 self.failUnless(today == todayagain or
756 abs(todayagain - today) < timedelta(seconds=0.5))
757
758 def test_weekday(self):
759 for i in range(7):
760 # March 4, 2002 is a Monday
761 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
762 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
763 # January 2, 1956 is a Monday
764 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
765 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
766
767 def test_isocalendar(self):
768 # Check examples from
769 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
770 for i in range(7):
771 d = self.theclass(2003, 12, 22+i)
772 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
773 d = self.theclass(2003, 12, 29) + timedelta(i)
774 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
775 d = self.theclass(2004, 1, 5+i)
776 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
777 d = self.theclass(2009, 12, 21+i)
778 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
779 d = self.theclass(2009, 12, 28) + timedelta(i)
780 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
781 d = self.theclass(2010, 1, 4+i)
782 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
783
784 def test_iso_long_years(self):
785 # Calculate long ISO years and compare to table from
786 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
787 ISO_LONG_YEARS_TABLE = """
788 4 32 60 88
789 9 37 65 93
790 15 43 71 99
791 20 48 76
792 26 54 82
793
794 105 133 161 189
795 111 139 167 195
796 116 144 172
797 122 150 178
798 128 156 184
799
800 201 229 257 285
801 207 235 263 291
802 212 240 268 296
803 218 246 274
804 224 252 280
805
806 303 331 359 387
807 308 336 364 392
808 314 342 370 398
809 320 348 376
810 325 353 381
811 """
812 iso_long_years = map(int, ISO_LONG_YEARS_TABLE.split())
813 iso_long_years.sort()
814 L = []
815 for i in range(400):
816 d = self.theclass(2000+i, 12, 31)
817 d1 = self.theclass(1600+i, 12, 31)
818 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
819 if d.isocalendar()[1] == 53:
820 L.append(i)
821 self.assertEqual(L, iso_long_years)
822
823 def test_isoformat(self):
824 t = self.theclass(2, 3, 2)
825 self.assertEqual(t.isoformat(), "0002-03-02")
826
827 def test_ctime(self):
828 t = self.theclass(2002, 3, 2)
829 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
830
831 def test_strftime(self):
832 t = self.theclass(2005, 3, 2)
833 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
834
835 self.assertRaises(TypeError, t.strftime) # needs an arg
836 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
837 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
838
839 # A naive object replaces %z and %Z w/ empty strings.
840 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
841
842 def test_resolution_info(self):
843 self.assert_(isinstance(self.theclass.min, self.theclass))
844 self.assert_(isinstance(self.theclass.max, self.theclass))
845 self.assert_(isinstance(self.theclass.resolution, timedelta))
846 self.assert_(self.theclass.max > self.theclass.min)
847
848 def test_extreme_timedelta(self):
849 big = self.theclass.max - self.theclass.min
850 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
851 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
852 # n == 315537897599999999 ~= 2**58.13
853 justasbig = timedelta(0, 0, n)
854 self.assertEqual(big, justasbig)
855 self.assertEqual(self.theclass.min + big, self.theclass.max)
856 self.assertEqual(self.theclass.max - big, self.theclass.min)
857
858 def test_timetuple(self):
859 for i in range(7):
860 # January 2, 1956 is a Monday (0)
861 d = self.theclass(1956, 1, 2+i)
862 t = d.timetuple()
863 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
864 # February 1, 1956 is a Wednesday (2)
865 d = self.theclass(1956, 2, 1+i)
866 t = d.timetuple()
867 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
868 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
869 # of the year.
870 d = self.theclass(1956, 3, 1+i)
871 t = d.timetuple()
872 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
873 self.assertEqual(t.tm_year, 1956)
874 self.assertEqual(t.tm_mon, 3)
875 self.assertEqual(t.tm_mday, 1+i)
876 self.assertEqual(t.tm_hour, 0)
877 self.assertEqual(t.tm_min, 0)
878 self.assertEqual(t.tm_sec, 0)
879 self.assertEqual(t.tm_wday, (3+i)%7)
880 self.assertEqual(t.tm_yday, 61+i)
881 self.assertEqual(t.tm_isdst, -1)
882
883 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000884 args = 6, 7, 23
885 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000886 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +0000887 green = pickler.dumps(orig, proto)
888 derived = unpickler.loads(green)
889 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +0000890
891 def test_compare(self):
892 t1 = self.theclass(2, 3, 4)
893 t2 = self.theclass(2, 3, 4)
894 self.failUnless(t1 == t2)
895 self.failUnless(t1 <= t2)
896 self.failUnless(t1 >= t2)
897 self.failUnless(not t1 != t2)
898 self.failUnless(not t1 < t2)
899 self.failUnless(not t1 > t2)
900 self.assertEqual(cmp(t1, t2), 0)
901 self.assertEqual(cmp(t2, t1), 0)
902
903 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
904 t2 = self.theclass(*args) # this is larger than t1
905 self.failUnless(t1 < t2)
906 self.failUnless(t2 > t1)
907 self.failUnless(t1 <= t2)
908 self.failUnless(t2 >= t1)
909 self.failUnless(t1 != t2)
910 self.failUnless(t2 != t1)
911 self.failUnless(not t1 == t2)
912 self.failUnless(not t2 == t1)
913 self.failUnless(not t1 > t2)
914 self.failUnless(not t2 < t1)
915 self.failUnless(not t1 >= t2)
916 self.failUnless(not t2 <= t1)
917 self.assertEqual(cmp(t1, t2), -1)
918 self.assertEqual(cmp(t2, t1), 1)
919
Tim Peters68124bb2003-02-08 03:46:31 +0000920 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +0000921 self.assertEqual(t1 == badarg, False)
922 self.assertEqual(t1 != badarg, True)
923 self.assertEqual(badarg == t1, False)
924 self.assertEqual(badarg != t1, True)
925
Tim Peters2a799bf2002-12-16 20:18:38 +0000926 self.assertRaises(TypeError, lambda: t1 < badarg)
927 self.assertRaises(TypeError, lambda: t1 > badarg)
928 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +0000929 self.assertRaises(TypeError, lambda: badarg <= t1)
930 self.assertRaises(TypeError, lambda: badarg < t1)
931 self.assertRaises(TypeError, lambda: badarg > t1)
932 self.assertRaises(TypeError, lambda: badarg >= t1)
933
Tim Peters8d81a012003-01-24 22:36:34 +0000934 def test_mixed_compare(self):
935 our = self.theclass(2000, 4, 5)
936 self.assertRaises(TypeError, cmp, our, 1)
937 self.assertRaises(TypeError, cmp, 1, our)
938
939 class AnotherDateTimeClass(object):
940 def __cmp__(self, other):
941 # Return "equal" so calling this can't be confused with
942 # compare-by-address (which never says "equal" for distinct
943 # objects).
944 return 0
945
946 # This still errors, because date and datetime comparison raise
947 # TypeError instead of NotImplemented when they don't know what to
948 # do, in order to stop comparison from falling back to the default
949 # compare-by-address.
950 their = AnotherDateTimeClass()
951 self.assertRaises(TypeError, cmp, our, their)
952 # Oops: The next stab raises TypeError in the C implementation,
953 # but not in the Python implementation of datetime. The difference
954 # is due to that the Python implementation defines __cmp__ but
955 # the C implementation defines tp_richcompare. This is more pain
956 # to fix than it's worth, so commenting out the test.
957 # self.assertEqual(cmp(their, our), 0)
958
959 # But date and datetime comparison return NotImplemented instead if the
960 # other object has a timetuple attr. This gives the other object a
961 # chance to do the comparison.
962 class Comparable(AnotherDateTimeClass):
963 def timetuple(self):
964 return ()
965
966 their = Comparable()
967 self.assertEqual(cmp(our, their), 0)
968 self.assertEqual(cmp(their, our), 0)
969 self.failUnless(our == their)
970 self.failUnless(their == our)
971
Tim Peters2a799bf2002-12-16 20:18:38 +0000972 def test_bool(self):
973 # All dates are considered true.
974 self.failUnless(self.theclass.min)
975 self.failUnless(self.theclass.max)
976
Tim Petersd6844152002-12-22 20:58:42 +0000977 def test_srftime_out_of_range(self):
978 # For nasty technical reasons, we can't handle years before 1900.
979 cls = self.theclass
980 self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900")
981 for y in 1, 49, 51, 99, 100, 1000, 1899:
982 self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y")
Tim Peters12bf3392002-12-24 05:41:27 +0000983
984 def test_replace(self):
985 cls = self.theclass
986 args = [1, 2, 3]
987 base = cls(*args)
988 self.assertEqual(base, base.replace())
989
990 i = 0
991 for name, newval in (("year", 2),
992 ("month", 3),
993 ("day", 4)):
994 newargs = args[:]
995 newargs[i] = newval
996 expected = cls(*newargs)
997 got = base.replace(**{name: newval})
998 self.assertEqual(expected, got)
999 i += 1
1000
1001 # Out of bounds.
1002 base = cls(2000, 2, 29)
1003 self.assertRaises(ValueError, base.replace, year=2001)
1004
Tim Peterseb1a4962003-05-17 02:25:20 +00001005
Tim Peters2a799bf2002-12-16 20:18:38 +00001006#############################################################################
1007# datetime tests
1008
1009class TestDateTime(TestDate):
1010
1011 theclass = datetime
1012
1013 def test_basic_attributes(self):
1014 dt = self.theclass(2002, 3, 1, 12, 0)
1015 self.assertEqual(dt.year, 2002)
1016 self.assertEqual(dt.month, 3)
1017 self.assertEqual(dt.day, 1)
1018 self.assertEqual(dt.hour, 12)
1019 self.assertEqual(dt.minute, 0)
1020 self.assertEqual(dt.second, 0)
1021 self.assertEqual(dt.microsecond, 0)
1022
1023 def test_basic_attributes_nonzero(self):
1024 # Make sure all attributes are non-zero so bugs in
1025 # bit-shifting access show up.
1026 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1027 self.assertEqual(dt.year, 2002)
1028 self.assertEqual(dt.month, 3)
1029 self.assertEqual(dt.day, 1)
1030 self.assertEqual(dt.hour, 12)
1031 self.assertEqual(dt.minute, 59)
1032 self.assertEqual(dt.second, 59)
1033 self.assertEqual(dt.microsecond, 8000)
1034
1035 def test_roundtrip(self):
1036 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1037 self.theclass.now()):
1038 # Verify dt -> string -> datetime identity.
1039 s = repr(dt)
1040 self.failUnless(s.startswith('datetime.'))
1041 s = s[9:]
1042 dt2 = eval(s)
1043 self.assertEqual(dt, dt2)
1044
1045 # Verify identity via reconstructing from pieces.
1046 dt2 = self.theclass(dt.year, dt.month, dt.day,
1047 dt.hour, dt.minute, dt.second,
1048 dt.microsecond)
1049 self.assertEqual(dt, dt2)
1050
1051 def test_isoformat(self):
1052 t = self.theclass(2, 3, 2, 4, 5, 1, 123)
1053 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123")
1054 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
1055 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
1056 # str is ISO format with the separator forced to a blank.
1057 self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
1058
1059 t = self.theclass(2, 3, 2)
1060 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1061 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1062 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1063 # str is ISO format with the separator forced to a blank.
1064 self.assertEqual(str(t), "0002-03-02 00:00:00")
1065
1066 def test_more_ctime(self):
1067 # Test fields that TestDate doesn't touch.
1068 import time
1069
1070 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1071 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1072 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1073 # out. The difference is that t.ctime() produces " 2" for the day,
1074 # but platform ctime() produces "02" for the day. According to
1075 # C99, t.ctime() is correct here.
1076 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1077
1078 # So test a case where that difference doesn't matter.
1079 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1080 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1081
1082 def test_tz_independent_comparing(self):
1083 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1084 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1085 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1086 self.assertEqual(dt1, dt3)
1087 self.assert_(dt2 > dt3)
1088
1089 # Make sure comparison doesn't forget microseconds, and isn't done
1090 # via comparing a float timestamp (an IEEE double doesn't have enough
1091 # precision to span microsecond resolution across years 1 thru 9999,
1092 # so comparing via timestamp necessarily calls some distinct values
1093 # equal).
1094 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1095 us = timedelta(microseconds=1)
1096 dt2 = dt1 + us
1097 self.assertEqual(dt2 - dt1, us)
1098 self.assert_(dt1 < dt2)
1099
1100 def test_bad_constructor_arguments(self):
1101 # bad years
1102 self.theclass(MINYEAR, 1, 1) # no exception
1103 self.theclass(MAXYEAR, 1, 1) # no exception
1104 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1105 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1106 # bad months
1107 self.theclass(2000, 1, 1) # no exception
1108 self.theclass(2000, 12, 1) # no exception
1109 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1110 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1111 # bad days
1112 self.theclass(2000, 2, 29) # no exception
1113 self.theclass(2004, 2, 29) # no exception
1114 self.theclass(2400, 2, 29) # no exception
1115 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1116 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1117 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1118 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1119 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1120 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1121 # bad hours
1122 self.theclass(2000, 1, 31, 0) # no exception
1123 self.theclass(2000, 1, 31, 23) # no exception
1124 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1125 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1126 # bad minutes
1127 self.theclass(2000, 1, 31, 23, 0) # no exception
1128 self.theclass(2000, 1, 31, 23, 59) # no exception
1129 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1130 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1131 # bad seconds
1132 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1133 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1134 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1135 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1136 # bad microseconds
1137 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1138 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1139 self.assertRaises(ValueError, self.theclass,
1140 2000, 1, 31, 23, 59, 59, -1)
1141 self.assertRaises(ValueError, self.theclass,
1142 2000, 1, 31, 23, 59, 59,
1143 1000000)
1144
1145 def test_hash_equality(self):
1146 d = self.theclass(2000, 12, 31, 23, 30, 17)
1147 e = self.theclass(2000, 12, 31, 23, 30, 17)
1148 self.assertEqual(d, e)
1149 self.assertEqual(hash(d), hash(e))
1150
1151 dic = {d: 1}
1152 dic[e] = 2
1153 self.assertEqual(len(dic), 1)
1154 self.assertEqual(dic[d], 2)
1155 self.assertEqual(dic[e], 2)
1156
1157 d = self.theclass(2001, 1, 1, 0, 5, 17)
1158 e = self.theclass(2001, 1, 1, 0, 5, 17)
1159 self.assertEqual(d, e)
1160 self.assertEqual(hash(d), hash(e))
1161
1162 dic = {d: 1}
1163 dic[e] = 2
1164 self.assertEqual(len(dic), 1)
1165 self.assertEqual(dic[d], 2)
1166 self.assertEqual(dic[e], 2)
1167
1168 def test_computations(self):
1169 a = self.theclass(2002, 1, 31)
1170 b = self.theclass(1956, 1, 31)
1171 diff = a-b
1172 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1173 self.assertEqual(diff.seconds, 0)
1174 self.assertEqual(diff.microseconds, 0)
1175 a = self.theclass(2002, 3, 2, 17, 6)
1176 millisec = timedelta(0, 0, 1000)
1177 hour = timedelta(0, 3600)
1178 day = timedelta(1)
1179 week = timedelta(7)
1180 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1181 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1182 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1183 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1184 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1185 self.assertEqual(a - hour, a + -hour)
1186 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1187 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1188 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1189 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1190 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1191 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1192 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1193 self.assertEqual((a + week) - a, week)
1194 self.assertEqual((a + day) - a, day)
1195 self.assertEqual((a + hour) - a, hour)
1196 self.assertEqual((a + millisec) - a, millisec)
1197 self.assertEqual((a - week) - a, -week)
1198 self.assertEqual((a - day) - a, -day)
1199 self.assertEqual((a - hour) - a, -hour)
1200 self.assertEqual((a - millisec) - a, -millisec)
1201 self.assertEqual(a - (a + week), -week)
1202 self.assertEqual(a - (a + day), -day)
1203 self.assertEqual(a - (a + hour), -hour)
1204 self.assertEqual(a - (a + millisec), -millisec)
1205 self.assertEqual(a - (a - week), week)
1206 self.assertEqual(a - (a - day), day)
1207 self.assertEqual(a - (a - hour), hour)
1208 self.assertEqual(a - (a - millisec), millisec)
1209 self.assertEqual(a + (week + day + hour + millisec),
1210 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1211 self.assertEqual(a + (week + day + hour + millisec),
1212 (((a + week) + day) + hour) + millisec)
1213 self.assertEqual(a - (week + day + hour + millisec),
1214 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1215 self.assertEqual(a - (week + day + hour + millisec),
1216 (((a - week) - day) - hour) - millisec)
1217 # Add/sub ints, longs, floats should be illegal
1218 for i in 1, 1L, 1.0:
1219 self.assertRaises(TypeError, lambda: a+i)
1220 self.assertRaises(TypeError, lambda: a-i)
1221 self.assertRaises(TypeError, lambda: i+a)
1222 self.assertRaises(TypeError, lambda: i-a)
1223
1224 # delta - datetime is senseless.
1225 self.assertRaises(TypeError, lambda: day - a)
1226 # mixing datetime and (delta or datetime) via * or // is senseless
1227 self.assertRaises(TypeError, lambda: day * a)
1228 self.assertRaises(TypeError, lambda: a * day)
1229 self.assertRaises(TypeError, lambda: day // a)
1230 self.assertRaises(TypeError, lambda: a // day)
1231 self.assertRaises(TypeError, lambda: a * a)
1232 self.assertRaises(TypeError, lambda: a // a)
1233 # datetime + datetime is senseless
1234 self.assertRaises(TypeError, lambda: a + a)
1235
1236 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001237 args = 6, 7, 23, 20, 59, 1, 64**2
1238 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001239 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001240 green = pickler.dumps(orig, proto)
1241 derived = unpickler.loads(green)
1242 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001243
Guido van Rossum275666f2003-02-07 21:49:01 +00001244 def test_more_pickling(self):
1245 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
1246 s = pickle.dumps(a)
1247 b = pickle.loads(s)
1248 self.assertEqual(b.year, 2003)
1249 self.assertEqual(b.month, 2)
1250 self.assertEqual(b.day, 7)
1251
Tim Peters2a799bf2002-12-16 20:18:38 +00001252 def test_more_compare(self):
1253 # The test_compare() inherited from TestDate covers the error cases.
1254 # We just want to test lexicographic ordering on the members datetime
1255 # has that date lacks.
1256 args = [2000, 11, 29, 20, 58, 16, 999998]
1257 t1 = self.theclass(*args)
1258 t2 = self.theclass(*args)
1259 self.failUnless(t1 == t2)
1260 self.failUnless(t1 <= t2)
1261 self.failUnless(t1 >= t2)
1262 self.failUnless(not t1 != t2)
1263 self.failUnless(not t1 < t2)
1264 self.failUnless(not t1 > t2)
1265 self.assertEqual(cmp(t1, t2), 0)
1266 self.assertEqual(cmp(t2, t1), 0)
1267
1268 for i in range(len(args)):
1269 newargs = args[:]
1270 newargs[i] = args[i] + 1
1271 t2 = self.theclass(*newargs) # this is larger than t1
1272 self.failUnless(t1 < t2)
1273 self.failUnless(t2 > t1)
1274 self.failUnless(t1 <= t2)
1275 self.failUnless(t2 >= t1)
1276 self.failUnless(t1 != t2)
1277 self.failUnless(t2 != t1)
1278 self.failUnless(not t1 == t2)
1279 self.failUnless(not t2 == t1)
1280 self.failUnless(not t1 > t2)
1281 self.failUnless(not t2 < t1)
1282 self.failUnless(not t1 >= t2)
1283 self.failUnless(not t2 <= t1)
1284 self.assertEqual(cmp(t1, t2), -1)
1285 self.assertEqual(cmp(t2, t1), 1)
1286
1287
1288 # A helper for timestamp constructor tests.
1289 def verify_field_equality(self, expected, got):
1290 self.assertEqual(expected.tm_year, got.year)
1291 self.assertEqual(expected.tm_mon, got.month)
1292 self.assertEqual(expected.tm_mday, got.day)
1293 self.assertEqual(expected.tm_hour, got.hour)
1294 self.assertEqual(expected.tm_min, got.minute)
1295 self.assertEqual(expected.tm_sec, got.second)
1296
1297 def test_fromtimestamp(self):
1298 import time
1299
1300 ts = time.time()
1301 expected = time.localtime(ts)
1302 got = self.theclass.fromtimestamp(ts)
1303 self.verify_field_equality(expected, got)
1304
1305 def test_utcfromtimestamp(self):
1306 import time
1307
1308 ts = time.time()
1309 expected = time.gmtime(ts)
1310 got = self.theclass.utcfromtimestamp(ts)
1311 self.verify_field_equality(expected, got)
1312
1313 def test_utcnow(self):
1314 import time
1315
1316 # Call it a success if utcnow() and utcfromtimestamp() are within
1317 # a second of each other.
1318 tolerance = timedelta(seconds=1)
1319 for dummy in range(3):
1320 from_now = self.theclass.utcnow()
1321 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1322 if abs(from_timestamp - from_now) <= tolerance:
1323 break
1324 # Else try again a few times.
1325 self.failUnless(abs(from_timestamp - from_now) <= tolerance)
1326
1327 def test_more_timetuple(self):
1328 # This tests fields beyond those tested by the TestDate.test_timetuple.
1329 t = self.theclass(2004, 12, 31, 6, 22, 33)
1330 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1331 self.assertEqual(t.timetuple(),
1332 (t.year, t.month, t.day,
1333 t.hour, t.minute, t.second,
1334 t.weekday(),
1335 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1336 -1))
1337 tt = t.timetuple()
1338 self.assertEqual(tt.tm_year, t.year)
1339 self.assertEqual(tt.tm_mon, t.month)
1340 self.assertEqual(tt.tm_mday, t.day)
1341 self.assertEqual(tt.tm_hour, t.hour)
1342 self.assertEqual(tt.tm_min, t.minute)
1343 self.assertEqual(tt.tm_sec, t.second)
1344 self.assertEqual(tt.tm_wday, t.weekday())
1345 self.assertEqual(tt.tm_yday, t.toordinal() -
1346 date(t.year, 1, 1).toordinal() + 1)
1347 self.assertEqual(tt.tm_isdst, -1)
1348
1349 def test_more_strftime(self):
1350 # This tests fields beyond those tested by the TestDate.test_strftime.
1351 t = self.theclass(2004, 12, 31, 6, 22, 33)
1352 self.assertEqual(t.strftime("%m %d %y %S %M %H %j"),
1353 "12 31 04 33 22 06 366")
1354
1355 def test_extract(self):
1356 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1357 self.assertEqual(dt.date(), date(2002, 3, 4))
1358 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1359
1360 def test_combine(self):
1361 d = date(2002, 3, 4)
1362 t = time(18, 45, 3, 1234)
1363 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1364 combine = self.theclass.combine
1365 dt = combine(d, t)
1366 self.assertEqual(dt, expected)
1367
1368 dt = combine(time=t, date=d)
1369 self.assertEqual(dt, expected)
1370
1371 self.assertEqual(d, dt.date())
1372 self.assertEqual(t, dt.time())
1373 self.assertEqual(dt, combine(dt.date(), dt.time()))
1374
1375 self.assertRaises(TypeError, combine) # need an arg
1376 self.assertRaises(TypeError, combine, d) # need two args
1377 self.assertRaises(TypeError, combine, t, d) # args reversed
1378 self.assertRaises(TypeError, combine, d, t, 1) # too many args
1379 self.assertRaises(TypeError, combine, "date", "time") # wrong types
1380
Tim Peters12bf3392002-12-24 05:41:27 +00001381 def test_replace(self):
1382 cls = self.theclass
1383 args = [1, 2, 3, 4, 5, 6, 7]
1384 base = cls(*args)
1385 self.assertEqual(base, base.replace())
1386
1387 i = 0
1388 for name, newval in (("year", 2),
1389 ("month", 3),
1390 ("day", 4),
1391 ("hour", 5),
1392 ("minute", 6),
1393 ("second", 7),
1394 ("microsecond", 8)):
1395 newargs = args[:]
1396 newargs[i] = newval
1397 expected = cls(*newargs)
1398 got = base.replace(**{name: newval})
1399 self.assertEqual(expected, got)
1400 i += 1
1401
1402 # Out of bounds.
1403 base = cls(2000, 2, 29)
1404 self.assertRaises(ValueError, base.replace, year=2001)
1405
Tim Peters80475bb2002-12-25 07:40:55 +00001406 def test_astimezone(self):
Tim Peters52dcce22003-01-23 16:36:11 +00001407 # Pretty boring! The TZ test is more interesting here. astimezone()
1408 # simply can't be applied to a naive object.
Tim Peters80475bb2002-12-25 07:40:55 +00001409 dt = self.theclass.now()
1410 f = FixedOffset(44, "")
Tim Peters80475bb2002-12-25 07:40:55 +00001411 self.assertRaises(TypeError, dt.astimezone) # not enough args
1412 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
1413 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Tim Peters52dcce22003-01-23 16:36:11 +00001414 self.assertRaises(ValueError, dt.astimezone, f) # naive
1415 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
Tim Peters80475bb2002-12-25 07:40:55 +00001416
Tim Peters52dcce22003-01-23 16:36:11 +00001417 class Bogus(tzinfo):
1418 def utcoffset(self, dt): return None
1419 def dst(self, dt): return timedelta(0)
1420 bog = Bogus()
1421 self.assertRaises(ValueError, dt.astimezone, bog) # naive
1422
1423 class AlsoBogus(tzinfo):
1424 def utcoffset(self, dt): return timedelta(0)
1425 def dst(self, dt): return None
1426 alsobog = AlsoBogus()
1427 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
Tim Peters12bf3392002-12-24 05:41:27 +00001428
Tim Peters07534a62003-02-07 22:50:28 +00001429class TestTime(HarmlessMixedComparison):
Tim Peters2a799bf2002-12-16 20:18:38 +00001430
1431 theclass = time
1432
1433 def test_basic_attributes(self):
1434 t = self.theclass(12, 0)
1435 self.assertEqual(t.hour, 12)
1436 self.assertEqual(t.minute, 0)
1437 self.assertEqual(t.second, 0)
1438 self.assertEqual(t.microsecond, 0)
1439
1440 def test_basic_attributes_nonzero(self):
1441 # Make sure all attributes are non-zero so bugs in
1442 # bit-shifting access show up.
1443 t = self.theclass(12, 59, 59, 8000)
1444 self.assertEqual(t.hour, 12)
1445 self.assertEqual(t.minute, 59)
1446 self.assertEqual(t.second, 59)
1447 self.assertEqual(t.microsecond, 8000)
1448
1449 def test_roundtrip(self):
1450 t = self.theclass(1, 2, 3, 4)
1451
1452 # Verify t -> string -> time identity.
1453 s = repr(t)
1454 self.failUnless(s.startswith('datetime.'))
1455 s = s[9:]
1456 t2 = eval(s)
1457 self.assertEqual(t, t2)
1458
1459 # Verify identity via reconstructing from pieces.
1460 t2 = self.theclass(t.hour, t.minute, t.second,
1461 t.microsecond)
1462 self.assertEqual(t, t2)
1463
1464 def test_comparing(self):
1465 args = [1, 2, 3, 4]
1466 t1 = self.theclass(*args)
1467 t2 = self.theclass(*args)
1468 self.failUnless(t1 == t2)
1469 self.failUnless(t1 <= t2)
1470 self.failUnless(t1 >= t2)
1471 self.failUnless(not t1 != t2)
1472 self.failUnless(not t1 < t2)
1473 self.failUnless(not t1 > t2)
1474 self.assertEqual(cmp(t1, t2), 0)
1475 self.assertEqual(cmp(t2, t1), 0)
1476
1477 for i in range(len(args)):
1478 newargs = args[:]
1479 newargs[i] = args[i] + 1
1480 t2 = self.theclass(*newargs) # this is larger than t1
1481 self.failUnless(t1 < t2)
1482 self.failUnless(t2 > t1)
1483 self.failUnless(t1 <= t2)
1484 self.failUnless(t2 >= t1)
1485 self.failUnless(t1 != t2)
1486 self.failUnless(t2 != t1)
1487 self.failUnless(not t1 == t2)
1488 self.failUnless(not t2 == t1)
1489 self.failUnless(not t1 > t2)
1490 self.failUnless(not t2 < t1)
1491 self.failUnless(not t1 >= t2)
1492 self.failUnless(not t2 <= t1)
1493 self.assertEqual(cmp(t1, t2), -1)
1494 self.assertEqual(cmp(t2, t1), 1)
1495
Tim Peters68124bb2003-02-08 03:46:31 +00001496 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +00001497 self.assertEqual(t1 == badarg, False)
1498 self.assertEqual(t1 != badarg, True)
1499 self.assertEqual(badarg == t1, False)
1500 self.assertEqual(badarg != t1, True)
1501
Tim Peters2a799bf2002-12-16 20:18:38 +00001502 self.assertRaises(TypeError, lambda: t1 <= badarg)
1503 self.assertRaises(TypeError, lambda: t1 < badarg)
1504 self.assertRaises(TypeError, lambda: t1 > badarg)
1505 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +00001506 self.assertRaises(TypeError, lambda: badarg <= t1)
1507 self.assertRaises(TypeError, lambda: badarg < t1)
1508 self.assertRaises(TypeError, lambda: badarg > t1)
1509 self.assertRaises(TypeError, lambda: badarg >= t1)
1510
1511 def test_bad_constructor_arguments(self):
1512 # bad hours
1513 self.theclass(0, 0) # no exception
1514 self.theclass(23, 0) # no exception
1515 self.assertRaises(ValueError, self.theclass, -1, 0)
1516 self.assertRaises(ValueError, self.theclass, 24, 0)
1517 # bad minutes
1518 self.theclass(23, 0) # no exception
1519 self.theclass(23, 59) # no exception
1520 self.assertRaises(ValueError, self.theclass, 23, -1)
1521 self.assertRaises(ValueError, self.theclass, 23, 60)
1522 # bad seconds
1523 self.theclass(23, 59, 0) # no exception
1524 self.theclass(23, 59, 59) # no exception
1525 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
1526 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
1527 # bad microseconds
1528 self.theclass(23, 59, 59, 0) # no exception
1529 self.theclass(23, 59, 59, 999999) # no exception
1530 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
1531 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
1532
1533 def test_hash_equality(self):
1534 d = self.theclass(23, 30, 17)
1535 e = self.theclass(23, 30, 17)
1536 self.assertEqual(d, e)
1537 self.assertEqual(hash(d), hash(e))
1538
1539 dic = {d: 1}
1540 dic[e] = 2
1541 self.assertEqual(len(dic), 1)
1542 self.assertEqual(dic[d], 2)
1543 self.assertEqual(dic[e], 2)
1544
1545 d = self.theclass(0, 5, 17)
1546 e = self.theclass(0, 5, 17)
1547 self.assertEqual(d, e)
1548 self.assertEqual(hash(d), hash(e))
1549
1550 dic = {d: 1}
1551 dic[e] = 2
1552 self.assertEqual(len(dic), 1)
1553 self.assertEqual(dic[d], 2)
1554 self.assertEqual(dic[e], 2)
1555
1556 def test_isoformat(self):
1557 t = self.theclass(4, 5, 1, 123)
1558 self.assertEqual(t.isoformat(), "04:05:01.000123")
1559 self.assertEqual(t.isoformat(), str(t))
1560
1561 t = self.theclass()
1562 self.assertEqual(t.isoformat(), "00:00:00")
1563 self.assertEqual(t.isoformat(), str(t))
1564
1565 t = self.theclass(microsecond=1)
1566 self.assertEqual(t.isoformat(), "00:00:00.000001")
1567 self.assertEqual(t.isoformat(), str(t))
1568
1569 t = self.theclass(microsecond=10)
1570 self.assertEqual(t.isoformat(), "00:00:00.000010")
1571 self.assertEqual(t.isoformat(), str(t))
1572
1573 t = self.theclass(microsecond=100)
1574 self.assertEqual(t.isoformat(), "00:00:00.000100")
1575 self.assertEqual(t.isoformat(), str(t))
1576
1577 t = self.theclass(microsecond=1000)
1578 self.assertEqual(t.isoformat(), "00:00:00.001000")
1579 self.assertEqual(t.isoformat(), str(t))
1580
1581 t = self.theclass(microsecond=10000)
1582 self.assertEqual(t.isoformat(), "00:00:00.010000")
1583 self.assertEqual(t.isoformat(), str(t))
1584
1585 t = self.theclass(microsecond=100000)
1586 self.assertEqual(t.isoformat(), "00:00:00.100000")
1587 self.assertEqual(t.isoformat(), str(t))
1588
1589 def test_strftime(self):
1590 t = self.theclass(1, 2, 3, 4)
1591 self.assertEqual(t.strftime('%H %M %S'), "01 02 03")
1592 # A naive object replaces %z and %Z with empty strings.
1593 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1594
1595 def test_str(self):
1596 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
1597 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
1598 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
1599 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
1600 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
1601
1602 def test_repr(self):
1603 name = 'datetime.' + self.theclass.__name__
1604 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
1605 "%s(1, 2, 3, 4)" % name)
1606 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
1607 "%s(10, 2, 3, 4000)" % name)
1608 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
1609 "%s(0, 2, 3, 400000)" % name)
1610 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
1611 "%s(12, 2, 3)" % name)
1612 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
1613 "%s(23, 15)" % name)
1614
1615 def test_resolution_info(self):
1616 self.assert_(isinstance(self.theclass.min, self.theclass))
1617 self.assert_(isinstance(self.theclass.max, self.theclass))
1618 self.assert_(isinstance(self.theclass.resolution, timedelta))
1619 self.assert_(self.theclass.max > self.theclass.min)
1620
1621 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001622 args = 20, 59, 16, 64**2
1623 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001624 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001625 green = pickler.dumps(orig, proto)
1626 derived = unpickler.loads(green)
1627 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001628
1629 def test_bool(self):
1630 cls = self.theclass
1631 self.failUnless(cls(1))
1632 self.failUnless(cls(0, 1))
1633 self.failUnless(cls(0, 0, 1))
1634 self.failUnless(cls(0, 0, 0, 1))
1635 self.failUnless(not cls(0))
1636 self.failUnless(not cls())
1637
Tim Peters12bf3392002-12-24 05:41:27 +00001638 def test_replace(self):
1639 cls = self.theclass
1640 args = [1, 2, 3, 4]
1641 base = cls(*args)
1642 self.assertEqual(base, base.replace())
1643
1644 i = 0
1645 for name, newval in (("hour", 5),
1646 ("minute", 6),
1647 ("second", 7),
1648 ("microsecond", 8)):
1649 newargs = args[:]
1650 newargs[i] = newval
1651 expected = cls(*newargs)
1652 got = base.replace(**{name: newval})
1653 self.assertEqual(expected, got)
1654 i += 1
1655
1656 # Out of bounds.
1657 base = cls(1)
1658 self.assertRaises(ValueError, base.replace, hour=24)
1659 self.assertRaises(ValueError, base.replace, minute=-1)
1660 self.assertRaises(ValueError, base.replace, second=100)
1661 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1662
Tim Peters855fe882002-12-22 03:43:39 +00001663# A mixin for classes with a tzinfo= argument. Subclasses must define
1664# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001665# must be legit (which is true for time and datetime).
Tim Peters855fe882002-12-22 03:43:39 +00001666class TZInfoBase(unittest.TestCase):
Tim Peters2a799bf2002-12-16 20:18:38 +00001667
Tim Petersbad8ff02002-12-30 20:52:32 +00001668 def test_argument_passing(self):
1669 cls = self.theclass
Tim Peters0bf60bd2003-01-08 20:40:01 +00001670 # A datetime passes itself on, a time passes None.
Tim Petersbad8ff02002-12-30 20:52:32 +00001671 class introspective(tzinfo):
1672 def tzname(self, dt): return dt and "real" or "none"
Tim Peters397301e2003-01-02 21:28:08 +00001673 def utcoffset(self, dt):
1674 return timedelta(minutes = dt and 42 or -42)
Tim Petersbad8ff02002-12-30 20:52:32 +00001675 dst = utcoffset
1676
1677 obj = cls(1, 2, 3, tzinfo=introspective())
1678
Tim Peters0bf60bd2003-01-08 20:40:01 +00001679 expected = cls is time and "none" or "real"
Tim Petersbad8ff02002-12-30 20:52:32 +00001680 self.assertEqual(obj.tzname(), expected)
1681
Tim Peters0bf60bd2003-01-08 20:40:01 +00001682 expected = timedelta(minutes=(cls is time and -42 or 42))
Tim Petersbad8ff02002-12-30 20:52:32 +00001683 self.assertEqual(obj.utcoffset(), expected)
1684 self.assertEqual(obj.dst(), expected)
1685
Tim Peters855fe882002-12-22 03:43:39 +00001686 def test_bad_tzinfo_classes(self):
1687 cls = self.theclass
1688 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
Tim Peters2a799bf2002-12-16 20:18:38 +00001689
Tim Peters855fe882002-12-22 03:43:39 +00001690 class NiceTry(object):
1691 def __init__(self): pass
1692 def utcoffset(self, dt): pass
1693 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
1694
1695 class BetterTry(tzinfo):
1696 def __init__(self): pass
1697 def utcoffset(self, dt): pass
1698 b = BetterTry()
1699 t = cls(1, 1, 1, tzinfo=b)
1700 self.failUnless(t.tzinfo is b)
1701
1702 def test_utc_offset_out_of_bounds(self):
1703 class Edgy(tzinfo):
1704 def __init__(self, offset):
Tim Peters397301e2003-01-02 21:28:08 +00001705 self.offset = timedelta(minutes=offset)
Tim Peters855fe882002-12-22 03:43:39 +00001706 def utcoffset(self, dt):
1707 return self.offset
1708
1709 cls = self.theclass
1710 for offset, legit in ((-1440, False),
1711 (-1439, True),
1712 (1439, True),
1713 (1440, False)):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001714 if cls is time:
Tim Peters855fe882002-12-22 03:43:39 +00001715 t = cls(1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001716 elif cls is datetime:
Tim Peters855fe882002-12-22 03:43:39 +00001717 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001718 else:
1719 assert 0, "impossible"
Tim Peters855fe882002-12-22 03:43:39 +00001720 if legit:
1721 aofs = abs(offset)
1722 h, m = divmod(aofs, 60)
1723 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001724 if isinstance(t, datetime):
Tim Peters855fe882002-12-22 03:43:39 +00001725 t = t.timetz()
1726 self.assertEqual(str(t), "01:02:03" + tag)
1727 else:
1728 self.assertRaises(ValueError, str, t)
1729
1730 def test_tzinfo_classes(self):
1731 cls = self.theclass
1732 class C1(tzinfo):
1733 def utcoffset(self, dt): return None
1734 def dst(self, dt): return None
1735 def tzname(self, dt): return None
1736 for t in (cls(1, 1, 1),
1737 cls(1, 1, 1, tzinfo=None),
1738 cls(1, 1, 1, tzinfo=C1())):
1739 self.failUnless(t.utcoffset() is None)
1740 self.failUnless(t.dst() is None)
1741 self.failUnless(t.tzname() is None)
1742
Tim Peters855fe882002-12-22 03:43:39 +00001743 class C3(tzinfo):
1744 def utcoffset(self, dt): return timedelta(minutes=-1439)
1745 def dst(self, dt): return timedelta(minutes=1439)
1746 def tzname(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001747 t = cls(1, 1, 1, tzinfo=C3())
1748 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
1749 self.assertEqual(t.dst(), timedelta(minutes=1439))
1750 self.assertEqual(t.tzname(), "aname")
Tim Peters855fe882002-12-22 03:43:39 +00001751
1752 # Wrong types.
1753 class C4(tzinfo):
1754 def utcoffset(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001755 def dst(self, dt): return 7
Tim Peters855fe882002-12-22 03:43:39 +00001756 def tzname(self, dt): return 0
1757 t = cls(1, 1, 1, tzinfo=C4())
1758 self.assertRaises(TypeError, t.utcoffset)
1759 self.assertRaises(TypeError, t.dst)
1760 self.assertRaises(TypeError, t.tzname)
1761
1762 # Offset out of range.
Tim Peters855fe882002-12-22 03:43:39 +00001763 class C6(tzinfo):
1764 def utcoffset(self, dt): return timedelta(hours=-24)
1765 def dst(self, dt): return timedelta(hours=24)
Tim Peters397301e2003-01-02 21:28:08 +00001766 t = cls(1, 1, 1, tzinfo=C6())
1767 self.assertRaises(ValueError, t.utcoffset)
1768 self.assertRaises(ValueError, t.dst)
Tim Peters855fe882002-12-22 03:43:39 +00001769
1770 # Not a whole number of minutes.
1771 class C7(tzinfo):
1772 def utcoffset(self, dt): return timedelta(seconds=61)
1773 def dst(self, dt): return timedelta(microseconds=-81)
1774 t = cls(1, 1, 1, tzinfo=C7())
1775 self.assertRaises(ValueError, t.utcoffset)
1776 self.assertRaises(ValueError, t.dst)
1777
Tim Peters4c0db782002-12-26 05:01:19 +00001778 def test_aware_compare(self):
1779 cls = self.theclass
1780
Tim Peters60c76e42002-12-27 00:41:11 +00001781 # Ensure that utcoffset() gets ignored if the comparands have
1782 # the same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00001783 class OperandDependentOffset(tzinfo):
1784 def utcoffset(self, t):
1785 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00001786 # d0 and d1 equal after adjustment
1787 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00001788 else:
Tim Peters397301e2003-01-02 21:28:08 +00001789 # d2 off in the weeds
1790 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00001791
1792 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
1793 d0 = base.replace(minute=3)
1794 d1 = base.replace(minute=9)
1795 d2 = base.replace(minute=11)
1796 for x in d0, d1, d2:
1797 for y in d0, d1, d2:
1798 got = cmp(x, y)
Tim Peters60c76e42002-12-27 00:41:11 +00001799 expected = cmp(x.minute, y.minute)
1800 self.assertEqual(got, expected)
1801
1802 # However, if they're different members, uctoffset is not ignored.
Tim Peters0bf60bd2003-01-08 20:40:01 +00001803 # Note that a time can't actually have an operand-depedent offset,
1804 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
1805 # so skip this test for time.
1806 if cls is not time:
Tim Petersbad8ff02002-12-30 20:52:32 +00001807 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
1808 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
1809 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
1810 for x in d0, d1, d2:
1811 for y in d0, d1, d2:
1812 got = cmp(x, y)
1813 if (x is d0 or x is d1) and (y is d0 or y is d1):
1814 expected = 0
1815 elif x is y is d2:
1816 expected = 0
1817 elif x is d2:
1818 expected = -1
1819 else:
1820 assert y is d2
1821 expected = 1
1822 self.assertEqual(got, expected)
Tim Peters4c0db782002-12-26 05:01:19 +00001823
Tim Peters855fe882002-12-22 03:43:39 +00001824
Tim Peters0bf60bd2003-01-08 20:40:01 +00001825# Testing time objects with a non-None tzinfo.
Tim Peters855fe882002-12-22 03:43:39 +00001826class TestTimeTZ(TestTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001827 theclass = time
Tim Peters2a799bf2002-12-16 20:18:38 +00001828
1829 def test_empty(self):
1830 t = self.theclass()
1831 self.assertEqual(t.hour, 0)
1832 self.assertEqual(t.minute, 0)
1833 self.assertEqual(t.second, 0)
1834 self.assertEqual(t.microsecond, 0)
1835 self.failUnless(t.tzinfo is None)
1836
Tim Peters2a799bf2002-12-16 20:18:38 +00001837 def test_zones(self):
1838 est = FixedOffset(-300, "EST", 1)
1839 utc = FixedOffset(0, "UTC", -2)
1840 met = FixedOffset(60, "MET", 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001841 t1 = time( 7, 47, tzinfo=est)
1842 t2 = time(12, 47, tzinfo=utc)
1843 t3 = time(13, 47, tzinfo=met)
1844 t4 = time(microsecond=40)
1845 t5 = time(microsecond=40, tzinfo=utc)
Tim Peters2a799bf2002-12-16 20:18:38 +00001846
1847 self.assertEqual(t1.tzinfo, est)
1848 self.assertEqual(t2.tzinfo, utc)
1849 self.assertEqual(t3.tzinfo, met)
1850 self.failUnless(t4.tzinfo is None)
1851 self.assertEqual(t5.tzinfo, utc)
1852
Tim Peters855fe882002-12-22 03:43:39 +00001853 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
1854 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
1855 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00001856 self.failUnless(t4.utcoffset() is None)
1857 self.assertRaises(TypeError, t1.utcoffset, "no args")
1858
1859 self.assertEqual(t1.tzname(), "EST")
1860 self.assertEqual(t2.tzname(), "UTC")
1861 self.assertEqual(t3.tzname(), "MET")
1862 self.failUnless(t4.tzname() is None)
1863 self.assertRaises(TypeError, t1.tzname, "no args")
1864
Tim Peters855fe882002-12-22 03:43:39 +00001865 self.assertEqual(t1.dst(), timedelta(minutes=1))
1866 self.assertEqual(t2.dst(), timedelta(minutes=-2))
1867 self.assertEqual(t3.dst(), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +00001868 self.failUnless(t4.dst() is None)
1869 self.assertRaises(TypeError, t1.dst, "no args")
1870
1871 self.assertEqual(hash(t1), hash(t2))
1872 self.assertEqual(hash(t1), hash(t3))
1873 self.assertEqual(hash(t2), hash(t3))
1874
1875 self.assertEqual(t1, t2)
1876 self.assertEqual(t1, t3)
1877 self.assertEqual(t2, t3)
1878 self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
1879 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
1880 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
1881
1882 self.assertEqual(str(t1), "07:47:00-05:00")
1883 self.assertEqual(str(t2), "12:47:00+00:00")
1884 self.assertEqual(str(t3), "13:47:00+01:00")
1885 self.assertEqual(str(t4), "00:00:00.000040")
1886 self.assertEqual(str(t5), "00:00:00.000040+00:00")
1887
1888 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
1889 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
1890 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
1891 self.assertEqual(t4.isoformat(), "00:00:00.000040")
1892 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
1893
Tim Peters0bf60bd2003-01-08 20:40:01 +00001894 d = 'datetime.time'
Tim Peters2a799bf2002-12-16 20:18:38 +00001895 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
1896 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
1897 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
1898 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
1899 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
1900
1901 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
1902 "07:47:00 %Z=EST %z=-0500")
1903 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
1904 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
1905
1906 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
Tim Peters0bf60bd2003-01-08 20:40:01 +00001907 t1 = time(23, 59, tzinfo=yuck)
Tim Peters2a799bf2002-12-16 20:18:38 +00001908 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
1909 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
1910
Tim Petersb92bb712002-12-21 17:44:07 +00001911 # Check that an invalid tzname result raises an exception.
1912 class Badtzname(tzinfo):
1913 def tzname(self, dt): return 42
Tim Peters0bf60bd2003-01-08 20:40:01 +00001914 t = time(2, 3, 4, tzinfo=Badtzname())
Tim Petersb92bb712002-12-21 17:44:07 +00001915 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
1916 self.assertRaises(TypeError, t.strftime, "%Z")
Tim Peters2a799bf2002-12-16 20:18:38 +00001917
1918 def test_hash_edge_cases(self):
1919 # Offsets that overflow a basic time.
1920 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
1921 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
1922 self.assertEqual(hash(t1), hash(t2))
1923
1924 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
1925 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
1926 self.assertEqual(hash(t1), hash(t2))
1927
Tim Peters2a799bf2002-12-16 20:18:38 +00001928 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001929 # Try one without a tzinfo.
1930 args = 20, 59, 16, 64**2
1931 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001932 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001933 green = pickler.dumps(orig, proto)
1934 derived = unpickler.loads(green)
1935 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001936
1937 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00001938 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00001939 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001940 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001941 green = pickler.dumps(orig, proto)
1942 derived = unpickler.loads(green)
1943 self.assertEqual(orig, derived)
1944 self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
1945 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
1946 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00001947
1948 def test_more_bool(self):
1949 # Test cases with non-None tzinfo.
1950 cls = self.theclass
1951
1952 t = cls(0, tzinfo=FixedOffset(-300, ""))
1953 self.failUnless(t)
1954
1955 t = cls(5, tzinfo=FixedOffset(-300, ""))
1956 self.failUnless(t)
1957
1958 t = cls(5, tzinfo=FixedOffset(300, ""))
1959 self.failUnless(not t)
1960
1961 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
1962 self.failUnless(not t)
1963
1964 # Mostly ensuring this doesn't overflow internally.
1965 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
1966 self.failUnless(t)
1967
1968 # But this should yield a value error -- the utcoffset is bogus.
1969 t = cls(0, tzinfo=FixedOffset(24*60, ""))
1970 self.assertRaises(ValueError, lambda: bool(t))
1971
1972 # Likewise.
1973 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
1974 self.assertRaises(ValueError, lambda: bool(t))
1975
Tim Peters12bf3392002-12-24 05:41:27 +00001976 def test_replace(self):
1977 cls = self.theclass
1978 z100 = FixedOffset(100, "+100")
1979 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
1980 args = [1, 2, 3, 4, z100]
1981 base = cls(*args)
1982 self.assertEqual(base, base.replace())
1983
1984 i = 0
1985 for name, newval in (("hour", 5),
1986 ("minute", 6),
1987 ("second", 7),
1988 ("microsecond", 8),
1989 ("tzinfo", zm200)):
1990 newargs = args[:]
1991 newargs[i] = newval
1992 expected = cls(*newargs)
1993 got = base.replace(**{name: newval})
1994 self.assertEqual(expected, got)
1995 i += 1
1996
1997 # Ensure we can get rid of a tzinfo.
1998 self.assertEqual(base.tzname(), "+100")
1999 base2 = base.replace(tzinfo=None)
2000 self.failUnless(base2.tzinfo is None)
2001 self.failUnless(base2.tzname() is None)
2002
2003 # Ensure we can add one.
2004 base3 = base2.replace(tzinfo=z100)
2005 self.assertEqual(base, base3)
2006 self.failUnless(base.tzinfo is base3.tzinfo)
2007
2008 # Out of bounds.
2009 base = cls(1)
2010 self.assertRaises(ValueError, base.replace, hour=24)
2011 self.assertRaises(ValueError, base.replace, minute=-1)
2012 self.assertRaises(ValueError, base.replace, second=100)
2013 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2014
Tim Peters60c76e42002-12-27 00:41:11 +00002015 def test_mixed_compare(self):
2016 t1 = time(1, 2, 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002017 t2 = time(1, 2, 3)
Tim Peters60c76e42002-12-27 00:41:11 +00002018 self.assertEqual(t1, t2)
2019 t2 = t2.replace(tzinfo=None)
2020 self.assertEqual(t1, t2)
2021 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2022 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002023 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2024 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002025
Tim Peters0bf60bd2003-01-08 20:40:01 +00002026 # In time w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002027 class Varies(tzinfo):
2028 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002029 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002030 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002031 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002032 return self.offset
2033
2034 v = Varies()
2035 t1 = t2.replace(tzinfo=v)
2036 t2 = t2.replace(tzinfo=v)
2037 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2038 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2039 self.assertEqual(t1, t2)
2040
2041 # But if they're not identical, it isn't ignored.
2042 t2 = t2.replace(tzinfo=Varies())
2043 self.failUnless(t1 < t2) # t1's offset counter still going up
2044
Tim Peters4c0db782002-12-26 05:01:19 +00002045
Tim Peters0bf60bd2003-01-08 20:40:01 +00002046# Testing datetime objects with a non-None tzinfo.
2047
Tim Peters855fe882002-12-22 03:43:39 +00002048class TestDateTimeTZ(TestDateTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002049 theclass = datetime
Tim Peters2a799bf2002-12-16 20:18:38 +00002050
2051 def test_trivial(self):
2052 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2053 self.assertEqual(dt.year, 1)
2054 self.assertEqual(dt.month, 2)
2055 self.assertEqual(dt.day, 3)
2056 self.assertEqual(dt.hour, 4)
2057 self.assertEqual(dt.minute, 5)
2058 self.assertEqual(dt.second, 6)
2059 self.assertEqual(dt.microsecond, 7)
2060 self.assertEqual(dt.tzinfo, None)
2061
2062 def test_even_more_compare(self):
2063 # The test_compare() and test_more_compare() inherited from TestDate
2064 # and TestDateTime covered non-tzinfo cases.
2065
2066 # Smallest possible after UTC adjustment.
2067 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2068 # Largest possible after UTC adjustment.
2069 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2070 tzinfo=FixedOffset(-1439, ""))
2071
2072 # Make sure those compare correctly, and w/o overflow.
2073 self.failUnless(t1 < t2)
2074 self.failUnless(t1 != t2)
2075 self.failUnless(t2 > t1)
2076
2077 self.failUnless(t1 == t1)
2078 self.failUnless(t2 == t2)
2079
2080 # Equal afer adjustment.
2081 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2082 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2083 self.assertEqual(t1, t2)
2084
2085 # Change t1 not to subtract a minute, and t1 should be larger.
2086 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2087 self.failUnless(t1 > t2)
2088
2089 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2090 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2091 self.failUnless(t1 < t2)
2092
2093 # Back to the original t1, but make seconds resolve it.
2094 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2095 second=1)
2096 self.failUnless(t1 > t2)
2097
2098 # Likewise, but make microseconds resolve it.
2099 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2100 microsecond=1)
2101 self.failUnless(t1 > t2)
2102
2103 # Make t2 naive and it should fail.
2104 t2 = self.theclass.min
2105 self.assertRaises(TypeError, lambda: t1 == t2)
2106 self.assertEqual(t2, t2)
2107
2108 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2109 class Naive(tzinfo):
2110 def utcoffset(self, dt): return None
2111 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
2112 self.assertRaises(TypeError, lambda: t1 == t2)
2113 self.assertEqual(t2, t2)
2114
2115 # OTOH, it's OK to compare two of these mixing the two ways of being
2116 # naive.
2117 t1 = self.theclass(5, 6, 7)
2118 self.assertEqual(t1, t2)
2119
2120 # Try a bogus uctoffset.
2121 class Bogus(tzinfo):
Tim Peters397301e2003-01-02 21:28:08 +00002122 def utcoffset(self, dt):
2123 return timedelta(minutes=1440) # out of bounds
Tim Peters2a799bf2002-12-16 20:18:38 +00002124 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2125 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
Tim Peters60c76e42002-12-27 00:41:11 +00002126 self.assertRaises(ValueError, lambda: t1 == t2)
Tim Peters2a799bf2002-12-16 20:18:38 +00002127
Tim Peters2a799bf2002-12-16 20:18:38 +00002128 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002129 # Try one without a tzinfo.
2130 args = 6, 7, 23, 20, 59, 1, 64**2
2131 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002132 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002133 green = pickler.dumps(orig, proto)
2134 derived = unpickler.loads(green)
2135 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002136
2137 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002138 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002139 orig = self.theclass(*args, **{'tzinfo': tinfo})
Tim Petersa9bc1682003-01-11 03:39:11 +00002140 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
Guido van Rossum177e41a2003-01-30 22:06:23 +00002141 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002142 green = pickler.dumps(orig, proto)
2143 derived = unpickler.loads(green)
2144 self.assertEqual(orig, derived)
2145 self.failUnless(isinstance(derived.tzinfo,
2146 PicklableFixedOffset))
2147 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2148 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002149
2150 def test_extreme_hashes(self):
2151 # If an attempt is made to hash these via subtracting the offset
2152 # then hashing a datetime object, OverflowError results. The
2153 # Python implementation used to blow up here.
2154 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2155 hash(t)
2156 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2157 tzinfo=FixedOffset(-1439, ""))
2158 hash(t)
2159
2160 # OTOH, an OOB offset should blow up.
2161 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2162 self.assertRaises(ValueError, hash, t)
2163
2164 def test_zones(self):
2165 est = FixedOffset(-300, "EST")
2166 utc = FixedOffset(0, "UTC")
2167 met = FixedOffset(60, "MET")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002168 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2169 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2170 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
Tim Peters2a799bf2002-12-16 20:18:38 +00002171 self.assertEqual(t1.tzinfo, est)
2172 self.assertEqual(t2.tzinfo, utc)
2173 self.assertEqual(t3.tzinfo, met)
Tim Peters855fe882002-12-22 03:43:39 +00002174 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2175 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2176 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002177 self.assertEqual(t1.tzname(), "EST")
2178 self.assertEqual(t2.tzname(), "UTC")
2179 self.assertEqual(t3.tzname(), "MET")
2180 self.assertEqual(hash(t1), hash(t2))
2181 self.assertEqual(hash(t1), hash(t3))
2182 self.assertEqual(hash(t2), hash(t3))
2183 self.assertEqual(t1, t2)
2184 self.assertEqual(t1, t3)
2185 self.assertEqual(t2, t3)
2186 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2187 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2188 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002189 d = 'datetime.datetime(2002, 3, 19, '
Tim Peters2a799bf2002-12-16 20:18:38 +00002190 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2191 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2192 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2193
2194 def test_combine(self):
2195 met = FixedOffset(60, "MET")
2196 d = date(2002, 3, 4)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002197 tz = time(18, 45, 3, 1234, tzinfo=met)
2198 dt = datetime.combine(d, tz)
2199 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
Tim Peters2a799bf2002-12-16 20:18:38 +00002200 tzinfo=met))
2201
2202 def test_extract(self):
2203 met = FixedOffset(60, "MET")
2204 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2205 self.assertEqual(dt.date(), date(2002, 3, 4))
2206 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
Tim Peters0bf60bd2003-01-08 20:40:01 +00002207 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
Tim Peters2a799bf2002-12-16 20:18:38 +00002208
2209 def test_tz_aware_arithmetic(self):
2210 import random
2211
2212 now = self.theclass.now()
2213 tz55 = FixedOffset(-330, "west 5:30")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002214 timeaware = now.time().replace(tzinfo=tz55)
Tim Peters2a799bf2002-12-16 20:18:38 +00002215 nowaware = self.theclass.combine(now.date(), timeaware)
2216 self.failUnless(nowaware.tzinfo is tz55)
2217 self.assertEqual(nowaware.timetz(), timeaware)
2218
2219 # Can't mix aware and non-aware.
2220 self.assertRaises(TypeError, lambda: now - nowaware)
2221 self.assertRaises(TypeError, lambda: nowaware - now)
2222
Tim Peters0bf60bd2003-01-08 20:40:01 +00002223 # And adding datetime's doesn't make sense, aware or not.
Tim Peters2a799bf2002-12-16 20:18:38 +00002224 self.assertRaises(TypeError, lambda: now + nowaware)
2225 self.assertRaises(TypeError, lambda: nowaware + now)
2226 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2227
2228 # Subtracting should yield 0.
2229 self.assertEqual(now - now, timedelta(0))
2230 self.assertEqual(nowaware - nowaware, timedelta(0))
2231
2232 # Adding a delta should preserve tzinfo.
2233 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2234 nowawareplus = nowaware + delta
2235 self.failUnless(nowaware.tzinfo is tz55)
2236 nowawareplus2 = delta + nowaware
2237 self.failUnless(nowawareplus2.tzinfo is tz55)
2238 self.assertEqual(nowawareplus, nowawareplus2)
2239
2240 # that - delta should be what we started with, and that - what we
2241 # started with should be delta.
2242 diff = nowawareplus - delta
2243 self.failUnless(diff.tzinfo is tz55)
2244 self.assertEqual(nowaware, diff)
2245 self.assertRaises(TypeError, lambda: delta - nowawareplus)
2246 self.assertEqual(nowawareplus - nowaware, delta)
2247
2248 # Make up a random timezone.
2249 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
Tim Peters4c0db782002-12-26 05:01:19 +00002250 # Attach it to nowawareplus.
2251 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Tim Peters2a799bf2002-12-16 20:18:38 +00002252 self.failUnless(nowawareplus.tzinfo is tzr)
2253 # Make sure the difference takes the timezone adjustments into account.
2254 got = nowaware - nowawareplus
2255 # Expected: (nowaware base - nowaware offset) -
2256 # (nowawareplus base - nowawareplus offset) =
2257 # (nowaware base - nowawareplus base) +
2258 # (nowawareplus offset - nowaware offset) =
2259 # -delta + nowawareplus offset - nowaware offset
Tim Peters855fe882002-12-22 03:43:39 +00002260 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
Tim Peters2a799bf2002-12-16 20:18:38 +00002261 self.assertEqual(got, expected)
2262
2263 # Try max possible difference.
2264 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2265 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2266 tzinfo=FixedOffset(-1439, "max"))
2267 maxdiff = max - min
2268 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2269 timedelta(minutes=2*1439))
2270
2271 def test_tzinfo_now(self):
2272 meth = self.theclass.now
2273 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2274 base = meth()
2275 # Try with and without naming the keyword.
2276 off42 = FixedOffset(42, "42")
2277 another = meth(off42)
Tim Peters10cadce2003-01-23 19:58:02 +00002278 again = meth(tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002279 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002280 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002281 # Bad argument with and w/o naming the keyword.
2282 self.assertRaises(TypeError, meth, 16)
2283 self.assertRaises(TypeError, meth, tzinfo=16)
2284 # Bad keyword name.
2285 self.assertRaises(TypeError, meth, tinfo=off42)
2286 # Too many args.
2287 self.assertRaises(TypeError, meth, off42, off42)
2288
Tim Peters10cadce2003-01-23 19:58:02 +00002289 # We don't know which time zone we're in, and don't have a tzinfo
2290 # class to represent it, so seeing whether a tz argument actually
2291 # does a conversion is tricky.
2292 weirdtz = FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0)
2293 utc = FixedOffset(0, "utc", 0)
2294 for dummy in range(3):
2295 now = datetime.now(weirdtz)
2296 self.failUnless(now.tzinfo is weirdtz)
2297 utcnow = datetime.utcnow().replace(tzinfo=utc)
2298 now2 = utcnow.astimezone(weirdtz)
2299 if abs(now - now2) < timedelta(seconds=30):
2300 break
2301 # Else the code is broken, or more than 30 seconds passed between
2302 # calls; assuming the latter, just try again.
2303 else:
2304 # Three strikes and we're out.
2305 self.fail("utcnow(), now(tz), or astimezone() may be broken")
2306
Tim Peters2a799bf2002-12-16 20:18:38 +00002307 def test_tzinfo_fromtimestamp(self):
2308 import time
2309 meth = self.theclass.fromtimestamp
2310 ts = time.time()
2311 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2312 base = meth(ts)
2313 # Try with and without naming the keyword.
2314 off42 = FixedOffset(42, "42")
2315 another = meth(ts, off42)
Tim Peters2a44a8d2003-01-23 20:53:10 +00002316 again = meth(ts, tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002317 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002318 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002319 # Bad argument with and w/o naming the keyword.
2320 self.assertRaises(TypeError, meth, ts, 16)
2321 self.assertRaises(TypeError, meth, ts, tzinfo=16)
2322 # Bad keyword name.
2323 self.assertRaises(TypeError, meth, ts, tinfo=off42)
2324 # Too many args.
2325 self.assertRaises(TypeError, meth, ts, off42, off42)
2326 # Too few args.
2327 self.assertRaises(TypeError, meth)
2328
Tim Peters2a44a8d2003-01-23 20:53:10 +00002329 # Try to make sure tz= actually does some conversion.
Tim Peters84407612003-02-06 16:42:14 +00002330 timestamp = 1000000000
2331 utcdatetime = datetime.utcfromtimestamp(timestamp)
2332 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
2333 # But on some flavor of Mac, it's nowhere near that. So we can't have
2334 # any idea here what time that actually is, we can only test that
2335 # relative changes match.
2336 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
2337 tz = FixedOffset(utcoffset, "tz", 0)
2338 expected = utcdatetime + utcoffset
2339 got = datetime.fromtimestamp(timestamp, tz)
2340 self.assertEqual(expected, got.replace(tzinfo=None))
Tim Peters2a44a8d2003-01-23 20:53:10 +00002341
Tim Peters2a799bf2002-12-16 20:18:38 +00002342 def test_tzinfo_utcnow(self):
2343 meth = self.theclass.utcnow
2344 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2345 base = meth()
2346 # Try with and without naming the keyword; for whatever reason,
2347 # utcnow() doesn't accept a tzinfo argument.
2348 off42 = FixedOffset(42, "42")
2349 self.assertRaises(TypeError, meth, off42)
2350 self.assertRaises(TypeError, meth, tzinfo=off42)
2351
2352 def test_tzinfo_utcfromtimestamp(self):
2353 import time
2354 meth = self.theclass.utcfromtimestamp
2355 ts = time.time()
2356 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2357 base = meth(ts)
2358 # Try with and without naming the keyword; for whatever reason,
2359 # utcfromtimestamp() doesn't accept a tzinfo argument.
2360 off42 = FixedOffset(42, "42")
2361 self.assertRaises(TypeError, meth, ts, off42)
2362 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
2363
2364 def test_tzinfo_timetuple(self):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002365 # TestDateTime tested most of this. datetime adds a twist to the
Tim Peters2a799bf2002-12-16 20:18:38 +00002366 # DST flag.
2367 class DST(tzinfo):
2368 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002369 if isinstance(dstvalue, int):
2370 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002371 self.dstvalue = dstvalue
2372 def dst(self, dt):
2373 return self.dstvalue
2374
2375 cls = self.theclass
2376 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
2377 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
2378 t = d.timetuple()
2379 self.assertEqual(1, t.tm_year)
2380 self.assertEqual(1, t.tm_mon)
2381 self.assertEqual(1, t.tm_mday)
2382 self.assertEqual(10, t.tm_hour)
2383 self.assertEqual(20, t.tm_min)
2384 self.assertEqual(30, t.tm_sec)
2385 self.assertEqual(0, t.tm_wday)
2386 self.assertEqual(1, t.tm_yday)
2387 self.assertEqual(flag, t.tm_isdst)
2388
2389 # dst() returns wrong type.
2390 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
2391
2392 # dst() at the edge.
2393 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
2394 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
2395
2396 # dst() out of range.
2397 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
2398 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
2399
2400 def test_utctimetuple(self):
2401 class DST(tzinfo):
2402 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002403 if isinstance(dstvalue, int):
2404 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002405 self.dstvalue = dstvalue
2406 def dst(self, dt):
2407 return self.dstvalue
2408
2409 cls = self.theclass
2410 # This can't work: DST didn't implement utcoffset.
2411 self.assertRaises(NotImplementedError,
2412 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
2413
2414 class UOFS(DST):
2415 def __init__(self, uofs, dofs=None):
2416 DST.__init__(self, dofs)
Tim Peters397301e2003-01-02 21:28:08 +00002417 self.uofs = timedelta(minutes=uofs)
Tim Peters2a799bf2002-12-16 20:18:38 +00002418 def utcoffset(self, dt):
2419 return self.uofs
2420
2421 # Ensure tm_isdst is 0 regardless of what dst() says: DST is never
2422 # in effect for a UTC time.
2423 for dstvalue in -33, 33, 0, None:
2424 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
2425 t = d.utctimetuple()
2426 self.assertEqual(d.year, t.tm_year)
2427 self.assertEqual(d.month, t.tm_mon)
2428 self.assertEqual(d.day, t.tm_mday)
2429 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
2430 self.assertEqual(13, t.tm_min)
2431 self.assertEqual(d.second, t.tm_sec)
2432 self.assertEqual(d.weekday(), t.tm_wday)
2433 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
2434 t.tm_yday)
2435 self.assertEqual(0, t.tm_isdst)
2436
2437 # At the edges, UTC adjustment can normalize into years out-of-range
2438 # for a datetime object. Ensure that a correct timetuple is
2439 # created anyway.
2440 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
2441 # That goes back 1 minute less than a full day.
2442 t = tiny.utctimetuple()
2443 self.assertEqual(t.tm_year, MINYEAR-1)
2444 self.assertEqual(t.tm_mon, 12)
2445 self.assertEqual(t.tm_mday, 31)
2446 self.assertEqual(t.tm_hour, 0)
2447 self.assertEqual(t.tm_min, 1)
2448 self.assertEqual(t.tm_sec, 37)
2449 self.assertEqual(t.tm_yday, 366) # "year 0" is a leap year
2450 self.assertEqual(t.tm_isdst, 0)
2451
2452 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
2453 # That goes forward 1 minute less than a full day.
2454 t = huge.utctimetuple()
2455 self.assertEqual(t.tm_year, MAXYEAR+1)
2456 self.assertEqual(t.tm_mon, 1)
2457 self.assertEqual(t.tm_mday, 1)
2458 self.assertEqual(t.tm_hour, 23)
2459 self.assertEqual(t.tm_min, 58)
2460 self.assertEqual(t.tm_sec, 37)
2461 self.assertEqual(t.tm_yday, 1)
2462 self.assertEqual(t.tm_isdst, 0)
2463
2464 def test_tzinfo_isoformat(self):
2465 zero = FixedOffset(0, "+00:00")
2466 plus = FixedOffset(220, "+03:40")
2467 minus = FixedOffset(-231, "-03:51")
2468 unknown = FixedOffset(None, "")
2469
2470 cls = self.theclass
2471 datestr = '0001-02-03'
2472 for ofs in None, zero, plus, minus, unknown:
Tim Peters6578dc92002-12-24 18:31:27 +00002473 for us in 0, 987001:
Tim Peters2a799bf2002-12-16 20:18:38 +00002474 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
2475 timestr = '04:05:59' + (us and '.987001' or '')
2476 ofsstr = ofs is not None and d.tzname() or ''
2477 tailstr = timestr + ofsstr
2478 iso = d.isoformat()
2479 self.assertEqual(iso, datestr + 'T' + tailstr)
2480 self.assertEqual(iso, d.isoformat('T'))
2481 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
2482 self.assertEqual(str(d), datestr + ' ' + tailstr)
2483
Tim Peters12bf3392002-12-24 05:41:27 +00002484 def test_replace(self):
2485 cls = self.theclass
2486 z100 = FixedOffset(100, "+100")
2487 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2488 args = [1, 2, 3, 4, 5, 6, 7, z100]
2489 base = cls(*args)
2490 self.assertEqual(base, base.replace())
2491
2492 i = 0
2493 for name, newval in (("year", 2),
2494 ("month", 3),
2495 ("day", 4),
2496 ("hour", 5),
2497 ("minute", 6),
2498 ("second", 7),
2499 ("microsecond", 8),
2500 ("tzinfo", zm200)):
2501 newargs = args[:]
2502 newargs[i] = newval
2503 expected = cls(*newargs)
2504 got = base.replace(**{name: newval})
2505 self.assertEqual(expected, got)
2506 i += 1
2507
2508 # Ensure we can get rid of a tzinfo.
2509 self.assertEqual(base.tzname(), "+100")
2510 base2 = base.replace(tzinfo=None)
2511 self.failUnless(base2.tzinfo is None)
2512 self.failUnless(base2.tzname() is None)
2513
2514 # Ensure we can add one.
2515 base3 = base2.replace(tzinfo=z100)
2516 self.assertEqual(base, base3)
2517 self.failUnless(base.tzinfo is base3.tzinfo)
2518
2519 # Out of bounds.
2520 base = cls(2000, 2, 29)
2521 self.assertRaises(ValueError, base.replace, year=2001)
Tim Peters2a799bf2002-12-16 20:18:38 +00002522
Tim Peters80475bb2002-12-25 07:40:55 +00002523 def test_more_astimezone(self):
2524 # The inherited test_astimezone covered some trivial and error cases.
2525 fnone = FixedOffset(None, "None")
2526 f44m = FixedOffset(44, "44")
2527 fm5h = FixedOffset(-timedelta(hours=5), "m300")
2528
Tim Peters10cadce2003-01-23 19:58:02 +00002529 dt = self.theclass.now(tz=f44m)
Tim Peters80475bb2002-12-25 07:40:55 +00002530 self.failUnless(dt.tzinfo is f44m)
Tim Peters52dcce22003-01-23 16:36:11 +00002531 # Replacing with degenerate tzinfo raises an exception.
2532 self.assertRaises(ValueError, dt.astimezone, fnone)
2533 # Ditto with None tz.
2534 self.assertRaises(TypeError, dt.astimezone, None)
2535 # Replacing with same tzinfo makes no change.
Tim Peters80475bb2002-12-25 07:40:55 +00002536 x = dt.astimezone(dt.tzinfo)
2537 self.failUnless(x.tzinfo is f44m)
2538 self.assertEqual(x.date(), dt.date())
2539 self.assertEqual(x.time(), dt.time())
2540
2541 # Replacing with different tzinfo does adjust.
2542 got = dt.astimezone(fm5h)
2543 self.failUnless(got.tzinfo is fm5h)
2544 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
2545 expected = dt - dt.utcoffset() # in effect, convert to UTC
2546 expected += fm5h.utcoffset(dt) # and from there to local time
2547 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
2548 self.assertEqual(got.date(), expected.date())
2549 self.assertEqual(got.time(), expected.time())
2550 self.assertEqual(got.timetz(), expected.timetz())
2551 self.failUnless(got.tzinfo is expected.tzinfo)
2552 self.assertEqual(got, expected)
2553
Tim Peters4c0db782002-12-26 05:01:19 +00002554 def test_aware_subtract(self):
2555 cls = self.theclass
2556
Tim Peters60c76e42002-12-27 00:41:11 +00002557 # Ensure that utcoffset() is ignored when the operands have the
2558 # same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00002559 class OperandDependentOffset(tzinfo):
2560 def utcoffset(self, t):
2561 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00002562 # d0 and d1 equal after adjustment
2563 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00002564 else:
Tim Peters397301e2003-01-02 21:28:08 +00002565 # d2 off in the weeds
2566 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00002567
2568 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
2569 d0 = base.replace(minute=3)
2570 d1 = base.replace(minute=9)
2571 d2 = base.replace(minute=11)
2572 for x in d0, d1, d2:
2573 for y in d0, d1, d2:
2574 got = x - y
Tim Peters60c76e42002-12-27 00:41:11 +00002575 expected = timedelta(minutes=x.minute - y.minute)
2576 self.assertEqual(got, expected)
2577
2578 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
2579 # ignored.
2580 base = cls(8, 9, 10, 11, 12, 13, 14)
2581 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2582 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2583 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2584 for x in d0, d1, d2:
2585 for y in d0, d1, d2:
2586 got = x - y
Tim Peters4c0db782002-12-26 05:01:19 +00002587 if (x is d0 or x is d1) and (y is d0 or y is d1):
2588 expected = timedelta(0)
2589 elif x is y is d2:
2590 expected = timedelta(0)
2591 elif x is d2:
2592 expected = timedelta(minutes=(11-59)-0)
2593 else:
2594 assert y is d2
2595 expected = timedelta(minutes=0-(11-59))
2596 self.assertEqual(got, expected)
2597
Tim Peters60c76e42002-12-27 00:41:11 +00002598 def test_mixed_compare(self):
2599 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002600 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters60c76e42002-12-27 00:41:11 +00002601 self.assertEqual(t1, t2)
2602 t2 = t2.replace(tzinfo=None)
2603 self.assertEqual(t1, t2)
2604 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2605 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002606 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2607 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002608
Tim Peters0bf60bd2003-01-08 20:40:01 +00002609 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002610 class Varies(tzinfo):
2611 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002612 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002613 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002614 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002615 return self.offset
2616
2617 v = Varies()
2618 t1 = t2.replace(tzinfo=v)
2619 t2 = t2.replace(tzinfo=v)
2620 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2621 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2622 self.assertEqual(t1, t2)
2623
2624 # But if they're not identical, it isn't ignored.
2625 t2 = t2.replace(tzinfo=Varies())
2626 self.failUnless(t1 < t2) # t1's offset counter still going up
Tim Peters80475bb2002-12-25 07:40:55 +00002627
Tim Peters621818b2002-12-29 23:44:49 +00002628# Pain to set up DST-aware tzinfo classes.
2629
2630def first_sunday_on_or_after(dt):
2631 days_to_go = 6 - dt.weekday()
2632 if days_to_go:
2633 dt += timedelta(days_to_go)
2634 return dt
2635
2636ZERO = timedelta(0)
2637HOUR = timedelta(hours=1)
2638DAY = timedelta(days=1)
2639# In the US, DST starts at 2am (standard time) on the first Sunday in April.
2640DSTSTART = datetime(1, 4, 1, 2)
2641# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
Tim Peters327098a2003-01-20 22:54:38 +00002642# which is the first Sunday on or after Oct 25. Because we view 1:MM as
2643# being standard time on that day, there is no spelling in local time of
2644# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
2645DSTEND = datetime(1, 10, 25, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002646
2647class USTimeZone(tzinfo):
2648
2649 def __init__(self, hours, reprname, stdname, dstname):
2650 self.stdoffset = timedelta(hours=hours)
2651 self.reprname = reprname
2652 self.stdname = stdname
2653 self.dstname = dstname
2654
2655 def __repr__(self):
2656 return self.reprname
2657
2658 def tzname(self, dt):
2659 if self.dst(dt):
2660 return self.dstname
2661 else:
2662 return self.stdname
2663
2664 def utcoffset(self, dt):
2665 return self.stdoffset + self.dst(dt)
2666
2667 def dst(self, dt):
Tim Petersbad8ff02002-12-30 20:52:32 +00002668 if dt is None or dt.tzinfo is None:
Tim Peters621818b2002-12-29 23:44:49 +00002669 # An exception instead may be sensible here, in one or more of
2670 # the cases.
2671 return ZERO
Tim Peters521fc152002-12-31 17:36:56 +00002672 assert dt.tzinfo is self
Tim Peters621818b2002-12-29 23:44:49 +00002673
2674 # Find first Sunday in April.
2675 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
2676 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
2677
2678 # Find last Sunday in October.
2679 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
2680 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
2681
Tim Peters621818b2002-12-29 23:44:49 +00002682 # Can't compare naive to aware objects, so strip the timezone from
2683 # dt first.
Tim Peters52dcce22003-01-23 16:36:11 +00002684 if start <= dt.replace(tzinfo=None) < end:
Tim Peters621818b2002-12-29 23:44:49 +00002685 return HOUR
2686 else:
2687 return ZERO
2688
Tim Peters521fc152002-12-31 17:36:56 +00002689Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
2690Central = USTimeZone(-6, "Central", "CST", "CDT")
2691Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
2692Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
Tim Peters1024bf82002-12-30 17:09:40 +00002693utc_real = FixedOffset(0, "UTC", 0)
2694# For better test coverage, we want another flavor of UTC that's west of
2695# the Eastern and Pacific timezones.
Tim Petersadf64202003-01-04 06:03:15 +00002696utc_fake = FixedOffset(-12*60, "UTCfake", 0)
Tim Peters621818b2002-12-29 23:44:49 +00002697
2698class TestTimezoneConversions(unittest.TestCase):
Tim Peters327098a2003-01-20 22:54:38 +00002699 # The DST switch times for 2002, in std time.
Tim Peters0bf60bd2003-01-08 20:40:01 +00002700 dston = datetime(2002, 4, 7, 2)
Tim Peters327098a2003-01-20 22:54:38 +00002701 dstoff = datetime(2002, 10, 27, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002702
Tim Peters0bf60bd2003-01-08 20:40:01 +00002703 theclass = datetime
Tim Peters710fb152003-01-02 19:35:54 +00002704
Tim Peters521fc152002-12-31 17:36:56 +00002705 # Check a time that's inside DST.
2706 def checkinside(self, dt, tz, utc, dston, dstoff):
2707 self.assertEqual(dt.dst(), HOUR)
2708
2709 # Conversion to our own timezone is always an identity.
2710 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters521fc152002-12-31 17:36:56 +00002711
2712 asutc = dt.astimezone(utc)
2713 there_and_back = asutc.astimezone(tz)
2714
2715 # Conversion to UTC and back isn't always an identity here,
2716 # because there are redundant spellings (in local time) of
2717 # UTC time when DST begins: the clock jumps from 1:59:59
2718 # to 3:00:00, and a local time of 2:MM:SS doesn't really
2719 # make sense then. The classes above treat 2:MM:SS as
2720 # daylight time then (it's "after 2am"), really an alias
2721 # for 1:MM:SS standard time. The latter form is what
2722 # conversion back from UTC produces.
2723 if dt.date() == dston.date() and dt.hour == 2:
2724 # We're in the redundant hour, and coming back from
2725 # UTC gives the 1:MM:SS standard-time spelling.
2726 self.assertEqual(there_and_back + HOUR, dt)
2727 # Although during was considered to be in daylight
2728 # time, there_and_back is not.
2729 self.assertEqual(there_and_back.dst(), ZERO)
2730 # They're the same times in UTC.
2731 self.assertEqual(there_and_back.astimezone(utc),
2732 dt.astimezone(utc))
2733 else:
2734 # We're not in the redundant hour.
2735 self.assertEqual(dt, there_and_back)
2736
Tim Peters327098a2003-01-20 22:54:38 +00002737 # Because we have a redundant spelling when DST begins, there is
2738 # (unforunately) an hour when DST ends that can't be spelled at all in
2739 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
2740 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
2741 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
2742 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
2743 # expressed in local time. Nevertheless, we want conversion back
2744 # from UTC to mimic the local clock's "repeat an hour" behavior.
Tim Peters521fc152002-12-31 17:36:56 +00002745 nexthour_utc = asutc + HOUR
Tim Petersadf64202003-01-04 06:03:15 +00002746 nexthour_tz = nexthour_utc.astimezone(tz)
Tim Peters327098a2003-01-20 22:54:38 +00002747 if dt.date() == dstoff.date() and dt.hour == 0:
2748 # We're in the hour before the last DST hour. The last DST hour
Tim Petersadf64202003-01-04 06:03:15 +00002749 # is ineffable. We want the conversion back to repeat 1:MM.
Tim Peters327098a2003-01-20 22:54:38 +00002750 self.assertEqual(nexthour_tz, dt.replace(hour=1))
2751 nexthour_utc += HOUR
2752 nexthour_tz = nexthour_utc.astimezone(tz)
2753 self.assertEqual(nexthour_tz, dt.replace(hour=1))
Tim Peters521fc152002-12-31 17:36:56 +00002754 else:
Tim Peters327098a2003-01-20 22:54:38 +00002755 self.assertEqual(nexthour_tz - dt, HOUR)
Tim Peters521fc152002-12-31 17:36:56 +00002756
2757 # Check a time that's outside DST.
2758 def checkoutside(self, dt, tz, utc):
2759 self.assertEqual(dt.dst(), ZERO)
2760
2761 # Conversion to our own timezone is always an identity.
2762 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters52dcce22003-01-23 16:36:11 +00002763
2764 # Converting to UTC and back is an identity too.
2765 asutc = dt.astimezone(utc)
2766 there_and_back = asutc.astimezone(tz)
2767 self.assertEqual(dt, there_and_back)
Tim Peters521fc152002-12-31 17:36:56 +00002768
Tim Peters1024bf82002-12-30 17:09:40 +00002769 def convert_between_tz_and_utc(self, tz, utc):
2770 dston = self.dston.replace(tzinfo=tz)
Tim Peters327098a2003-01-20 22:54:38 +00002771 # Because 1:MM on the day DST ends is taken as being standard time,
2772 # there is no spelling in tz for the last hour of daylight time.
2773 # For purposes of the test, the last hour of DST is 0:MM, which is
2774 # taken as being daylight time (and 1:MM is taken as being standard
2775 # time).
Tim Peters1024bf82002-12-30 17:09:40 +00002776 dstoff = self.dstoff.replace(tzinfo=tz)
2777 for delta in (timedelta(weeks=13),
2778 DAY,
2779 HOUR,
2780 timedelta(minutes=1),
2781 timedelta(microseconds=1)):
2782
Tim Peters521fc152002-12-31 17:36:56 +00002783 self.checkinside(dston, tz, utc, dston, dstoff)
2784 for during in dston + delta, dstoff - delta:
2785 self.checkinside(during, tz, utc, dston, dstoff)
Tim Peters31cc3152002-12-30 17:37:30 +00002786
Tim Peters521fc152002-12-31 17:36:56 +00002787 self.checkoutside(dstoff, tz, utc)
2788 for outside in dston - delta, dstoff + delta:
2789 self.checkoutside(outside, tz, utc)
Tim Peters31cc3152002-12-30 17:37:30 +00002790
Tim Peters621818b2002-12-29 23:44:49 +00002791 def test_easy(self):
2792 # Despite the name of this test, the endcases are excruciating.
Tim Peters1024bf82002-12-30 17:09:40 +00002793 self.convert_between_tz_and_utc(Eastern, utc_real)
2794 self.convert_between_tz_and_utc(Pacific, utc_real)
2795 self.convert_between_tz_and_utc(Eastern, utc_fake)
2796 self.convert_between_tz_and_utc(Pacific, utc_fake)
2797 # The next is really dancing near the edge. It works because
2798 # Pacific and Eastern are far enough apart that their "problem
2799 # hours" don't overlap.
2800 self.convert_between_tz_and_utc(Eastern, Pacific)
2801 self.convert_between_tz_and_utc(Pacific, Eastern)
Tim Peters36087ed2003-01-01 04:18:51 +00002802 # OTOH, these fail! Don't enable them. The difficulty is that
2803 # the edge case tests assume that every hour is representable in
2804 # the "utc" class. This is always true for a fixed-offset tzinfo
2805 # class (lke utc_real and utc_fake), but not for Eastern or Central.
2806 # For these adjacent DST-aware time zones, the range of time offsets
2807 # tested ends up creating hours in the one that aren't representable
2808 # in the other. For the same reason, we would see failures in the
2809 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
2810 # offset deltas in convert_between_tz_and_utc().
2811 #
2812 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
2813 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
Tim Peters621818b2002-12-29 23:44:49 +00002814
Tim Petersf3615152003-01-01 21:51:37 +00002815 def test_tricky(self):
2816 # 22:00 on day before daylight starts.
2817 fourback = self.dston - timedelta(hours=4)
2818 ninewest = FixedOffset(-9*60, "-0900", 0)
Tim Peters52dcce22003-01-23 16:36:11 +00002819 fourback = fourback.replace(tzinfo=ninewest)
Tim Petersf3615152003-01-01 21:51:37 +00002820 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
2821 # 2", we should get the 3 spelling.
2822 # If we plug 22:00 the day before into Eastern, it "looks like std
2823 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
2824 # to 22:00 lands on 2:00, which makes no sense in local time (the
2825 # local clock jumps from 1 to 3). The point here is to make sure we
2826 # get the 3 spelling.
2827 expected = self.dston.replace(hour=3)
Tim Peters52dcce22003-01-23 16:36:11 +00002828 got = fourback.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00002829 self.assertEqual(expected, got)
2830
2831 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
2832 # case we want the 1:00 spelling.
Tim Peters52dcce22003-01-23 16:36:11 +00002833 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
Tim Petersf3615152003-01-01 21:51:37 +00002834 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
2835 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
2836 # spelling.
2837 expected = self.dston.replace(hour=1)
Tim Peters52dcce22003-01-23 16:36:11 +00002838 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00002839 self.assertEqual(expected, got)
Tim Peters621818b2002-12-29 23:44:49 +00002840
Tim Petersadf64202003-01-04 06:03:15 +00002841 # Now on the day DST ends, we want "repeat an hour" behavior.
2842 # UTC 4:MM 5:MM 6:MM 7:MM checking these
2843 # EST 23:MM 0:MM 1:MM 2:MM
2844 # EDT 0:MM 1:MM 2:MM 3:MM
2845 # wall 0:MM 1:MM 1:MM 2:MM against these
2846 for utc in utc_real, utc_fake:
2847 for tz in Eastern, Pacific:
Tim Peters327098a2003-01-20 22:54:38 +00002848 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
Tim Petersadf64202003-01-04 06:03:15 +00002849 # Convert that to UTC.
2850 first_std_hour -= tz.utcoffset(None)
2851 # Adjust for possibly fake UTC.
2852 asutc = first_std_hour + utc.utcoffset(None)
2853 # First UTC hour to convert; this is 4:00 when utc=utc_real &
2854 # tz=Eastern.
2855 asutcbase = asutc.replace(tzinfo=utc)
2856 for tzhour in (0, 1, 1, 2):
2857 expectedbase = self.dstoff.replace(hour=tzhour)
2858 for minute in 0, 30, 59:
2859 expected = expectedbase.replace(minute=minute)
2860 asutc = asutcbase.replace(minute=minute)
2861 astz = asutc.astimezone(tz)
2862 self.assertEqual(astz.replace(tzinfo=None), expected)
2863 asutcbase += HOUR
2864
2865
Tim Peters710fb152003-01-02 19:35:54 +00002866 def test_bogus_dst(self):
2867 class ok(tzinfo):
2868 def utcoffset(self, dt): return HOUR
2869 def dst(self, dt): return HOUR
2870
2871 now = self.theclass.now().replace(tzinfo=utc_real)
2872 # Doesn't blow up.
2873 now.astimezone(ok())
2874
2875 # Does blow up.
2876 class notok(ok):
2877 def dst(self, dt): return None
2878 self.assertRaises(ValueError, now.astimezone, notok())
2879
Tim Peters52dcce22003-01-23 16:36:11 +00002880 def test_fromutc(self):
2881 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
2882 now = datetime.utcnow().replace(tzinfo=utc_real)
2883 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
2884 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
2885 enow = Eastern.fromutc(now) # doesn't blow up
2886 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
2887 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
2888 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
2889
2890 # Always converts UTC to standard time.
2891 class FauxUSTimeZone(USTimeZone):
2892 def fromutc(self, dt):
2893 return dt + self.stdoffset
2894 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
2895
2896 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
2897 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
2898 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
2899
2900 # Check around DST start.
2901 start = self.dston.replace(hour=4, tzinfo=Eastern)
2902 fstart = start.replace(tzinfo=FEastern)
2903 for wall in 23, 0, 1, 3, 4, 5:
2904 expected = start.replace(hour=wall)
2905 if wall == 23:
2906 expected -= timedelta(days=1)
2907 got = Eastern.fromutc(start)
2908 self.assertEqual(expected, got)
2909
2910 expected = fstart + FEastern.stdoffset
2911 got = FEastern.fromutc(fstart)
2912 self.assertEqual(expected, got)
2913
2914 # Ensure astimezone() calls fromutc() too.
2915 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
2916 self.assertEqual(expected, got)
2917
2918 start += HOUR
2919 fstart += HOUR
2920
2921 # Check around DST end.
2922 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
2923 fstart = start.replace(tzinfo=FEastern)
2924 for wall in 0, 1, 1, 2, 3, 4:
2925 expected = start.replace(hour=wall)
2926 got = Eastern.fromutc(start)
2927 self.assertEqual(expected, got)
2928
2929 expected = fstart + FEastern.stdoffset
2930 got = FEastern.fromutc(fstart)
2931 self.assertEqual(expected, got)
2932
2933 # Ensure astimezone() calls fromutc() too.
2934 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
2935 self.assertEqual(expected, got)
2936
2937 start += HOUR
2938 fstart += HOUR
2939
Tim Peters710fb152003-01-02 19:35:54 +00002940
Tim Peterscfd4a8b2002-12-16 21:12:37 +00002941def test_suite():
Tim Peters2a799bf2002-12-16 20:18:38 +00002942 allsuites = [unittest.makeSuite(klass, 'test')
2943 for klass in (TestModule,
2944 TestTZInfo,
2945 TestTimeDelta,
2946 TestDateOnly,
2947 TestDate,
2948 TestDateTime,
2949 TestTime,
2950 TestTimeTZ,
2951 TestDateTimeTZ,
Tim Peters621818b2002-12-29 23:44:49 +00002952 TestTimezoneConversions,
Tim Peters2a799bf2002-12-16 20:18:38 +00002953 )
2954 ]
2955 return unittest.TestSuite(allsuites)
2956
2957def test_main():
2958 import gc
2959 import sys
2960
Tim Peterscfd4a8b2002-12-16 21:12:37 +00002961 thesuite = test_suite()
Tim Peters2a799bf2002-12-16 20:18:38 +00002962 lastrc = None
2963 while True:
2964 test_support.run_suite(thesuite)
2965 if 1: # change to 0, under a debug build, for some leak detection
2966 break
2967 gc.collect()
2968 if gc.garbage:
2969 raise SystemError("gc.garbage not empty after test run: %r" %
2970 gc.garbage)
2971 if hasattr(sys, 'gettotalrefcount'):
2972 thisrc = sys.gettotalrefcount()
2973 print >> sys.stderr, '*' * 10, 'total refs:', thisrc,
2974 if lastrc:
2975 print >> sys.stderr, 'delta:', thisrc - lastrc
2976 else:
2977 print >> sys.stderr
2978 lastrc = thisrc
2979
2980if __name__ == "__main__":
2981 test_main()