blob: c6dbb4895624f3451002b9dfb2bd7210a08122be [file] [log] [blame]
Tim Peters0bf60bd2003-01-08 20:40:01 +00001"""Test date/time type.
2
3See http://www.zope.org/Members/fdrake/DateTimeWiki/TestCases
4"""
Tim Peters2a799bf2002-12-16 20:18:38 +00005
6import sys
Guido van Rossum177e41a2003-01-30 22:06:23 +00007import pickle
8import cPickle
Tim Peters2a799bf2002-12-16 20:18:38 +00009import unittest
10
11from test import test_support
12
13from datetime import MINYEAR, MAXYEAR
14from datetime import timedelta
15from datetime import tzinfo
Tim Peters0bf60bd2003-01-08 20:40:01 +000016from datetime import time
17from datetime import date, datetime
18
Tim Peters35ad6412003-02-05 04:08:07 +000019pickle_choices = [(pickler, unpickler, proto)
20 for pickler in pickle, cPickle
21 for unpickler in pickle, cPickle
22 for proto in range(3)]
23assert len(pickle_choices) == 2*2*3
Guido van Rossum177e41a2003-01-30 22:06:23 +000024
Tim Peters68124bb2003-02-08 03:46:31 +000025# An arbitrary collection of objects of non-datetime types, for testing
26# mixed-type comparisons.
27OTHERSTUFF = (10, 10L, 34.5, "abc", {}, [], ())
Tim Peters0bf60bd2003-01-08 20:40:01 +000028
Tim Peters2a799bf2002-12-16 20:18:38 +000029
30#############################################################################
31# module tests
32
33class TestModule(unittest.TestCase):
34
35 def test_constants(self):
36 import datetime
37 self.assertEqual(datetime.MINYEAR, 1)
38 self.assertEqual(datetime.MAXYEAR, 9999)
39
40#############################################################################
41# tzinfo tests
42
43class FixedOffset(tzinfo):
44 def __init__(self, offset, name, dstoffset=42):
Tim Peters397301e2003-01-02 21:28:08 +000045 if isinstance(offset, int):
46 offset = timedelta(minutes=offset)
47 if isinstance(dstoffset, int):
48 dstoffset = timedelta(minutes=dstoffset)
Tim Peters2a799bf2002-12-16 20:18:38 +000049 self.__offset = offset
50 self.__name = name
51 self.__dstoffset = dstoffset
52 def __repr__(self):
53 return self.__name.lower()
54 def utcoffset(self, dt):
55 return self.__offset
56 def tzname(self, dt):
57 return self.__name
58 def dst(self, dt):
59 return self.__dstoffset
60
Tim Petersfb8472c2002-12-21 05:04:42 +000061class PicklableFixedOffset(FixedOffset):
62 def __init__(self, offset=None, name=None, dstoffset=None):
63 FixedOffset.__init__(self, offset, name, dstoffset)
64
Tim Peters2a799bf2002-12-16 20:18:38 +000065class TestTZInfo(unittest.TestCase):
66
67 def test_non_abstractness(self):
68 # In order to allow subclasses to get pickled, the C implementation
69 # wasn't able to get away with having __init__ raise
70 # NotImplementedError.
71 useless = tzinfo()
72 dt = datetime.max
73 self.assertRaises(NotImplementedError, useless.tzname, dt)
74 self.assertRaises(NotImplementedError, useless.utcoffset, dt)
75 self.assertRaises(NotImplementedError, useless.dst, dt)
76
77 def test_subclass_must_override(self):
78 class NotEnough(tzinfo):
79 def __init__(self, offset, name):
80 self.__offset = offset
81 self.__name = name
82 self.failUnless(issubclass(NotEnough, tzinfo))
83 ne = NotEnough(3, "NotByALongShot")
84 self.failUnless(isinstance(ne, tzinfo))
85
86 dt = datetime.now()
87 self.assertRaises(NotImplementedError, ne.tzname, dt)
88 self.assertRaises(NotImplementedError, ne.utcoffset, dt)
89 self.assertRaises(NotImplementedError, ne.dst, dt)
90
91 def test_normal(self):
92 fo = FixedOffset(3, "Three")
93 self.failUnless(isinstance(fo, tzinfo))
94 for dt in datetime.now(), None:
Tim Peters397301e2003-01-02 21:28:08 +000095 self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +000096 self.assertEqual(fo.tzname(dt), "Three")
Tim Peters397301e2003-01-02 21:28:08 +000097 self.assertEqual(fo.dst(dt), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +000098
99 def test_pickling_base(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000100 # There's no point to pickling tzinfo objects on their own (they
101 # carry no data), but they need to be picklable anyway else
102 # concrete subclasses can't be pickled.
103 orig = tzinfo.__new__(tzinfo)
104 self.failUnless(type(orig) is tzinfo)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000105 for pickler, unpickler, proto in pickle_choices:
Tim Petersf2715e02003-02-19 02:35:07 +0000106 green = pickler.dumps(orig, proto)
107 derived = unpickler.loads(green)
108 self.failUnless(type(derived) is tzinfo)
Tim Peters2a799bf2002-12-16 20:18:38 +0000109
110 def test_pickling_subclass(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000111 # Make sure we can pickle/unpickle an instance of a subclass.
Tim Peters397301e2003-01-02 21:28:08 +0000112 offset = timedelta(minutes=-300)
113 orig = PicklableFixedOffset(offset, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +0000114 self.failUnless(isinstance(orig, tzinfo))
Tim Petersfb8472c2002-12-21 05:04:42 +0000115 self.failUnless(type(orig) is PicklableFixedOffset)
Tim Peters397301e2003-01-02 21:28:08 +0000116 self.assertEqual(orig.utcoffset(None), offset)
Tim Peters2a799bf2002-12-16 20:18:38 +0000117 self.assertEqual(orig.tzname(None), 'cookie')
Guido van Rossum177e41a2003-01-30 22:06:23 +0000118 for pickler, unpickler, proto in pickle_choices:
Tim Petersf2715e02003-02-19 02:35:07 +0000119 green = pickler.dumps(orig, proto)
120 derived = unpickler.loads(green)
121 self.failUnless(isinstance(derived, tzinfo))
122 self.failUnless(type(derived) is PicklableFixedOffset)
123 self.assertEqual(derived.utcoffset(None), offset)
124 self.assertEqual(derived.tzname(None), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +0000125
126#############################################################################
Tim Peters07534a62003-02-07 22:50:28 +0000127# Base clase for testing a particular aspect of timedelta, time, date and
128# datetime comparisons.
129
130class HarmlessMixedComparison(unittest.TestCase):
131 # Test that __eq__ and __ne__ don't complain for mixed-type comparisons.
132
133 # Subclasses must define 'theclass', and theclass(1, 1, 1) must be a
134 # legit constructor.
135
136 def test_harmless_mixed_comparison(self):
137 me = self.theclass(1, 1, 1)
138
139 self.failIf(me == ())
140 self.failUnless(me != ())
141 self.failIf(() == me)
142 self.failUnless(() != me)
143
144 self.failUnless(me in [1, 20L, [], me])
145 self.failIf(me not in [1, 20L, [], me])
146
147 self.failUnless([] in [me, 1, 20L, []])
148 self.failIf([] not in [me, 1, 20L, []])
149
150 def test_harmful_mixed_comparison(self):
151 me = self.theclass(1, 1, 1)
152
153 self.assertRaises(TypeError, lambda: me < ())
154 self.assertRaises(TypeError, lambda: me <= ())
155 self.assertRaises(TypeError, lambda: me > ())
156 self.assertRaises(TypeError, lambda: me >= ())
157
158 self.assertRaises(TypeError, lambda: () < me)
159 self.assertRaises(TypeError, lambda: () <= me)
160 self.assertRaises(TypeError, lambda: () > me)
161 self.assertRaises(TypeError, lambda: () >= me)
162
163 self.assertRaises(TypeError, cmp, (), me)
164 self.assertRaises(TypeError, cmp, me, ())
165
166#############################################################################
Tim Peters2a799bf2002-12-16 20:18:38 +0000167# timedelta tests
168
Tim Peters07534a62003-02-07 22:50:28 +0000169class TestTimeDelta(HarmlessMixedComparison):
170
171 theclass = timedelta
Tim Peters2a799bf2002-12-16 20:18:38 +0000172
173 def test_constructor(self):
174 eq = self.assertEqual
175 td = timedelta
176
177 # Check keyword args to constructor
178 eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
179 milliseconds=0, microseconds=0))
180 eq(td(1), td(days=1))
181 eq(td(0, 1), td(seconds=1))
182 eq(td(0, 0, 1), td(microseconds=1))
183 eq(td(weeks=1), td(days=7))
184 eq(td(days=1), td(hours=24))
185 eq(td(hours=1), td(minutes=60))
186 eq(td(minutes=1), td(seconds=60))
187 eq(td(seconds=1), td(milliseconds=1000))
188 eq(td(milliseconds=1), td(microseconds=1000))
189
190 # Check float args to constructor
191 eq(td(weeks=1.0/7), td(days=1))
192 eq(td(days=1.0/24), td(hours=1))
193 eq(td(hours=1.0/60), td(minutes=1))
194 eq(td(minutes=1.0/60), td(seconds=1))
195 eq(td(seconds=0.001), td(milliseconds=1))
196 eq(td(milliseconds=0.001), td(microseconds=1))
197
198 def test_computations(self):
199 eq = self.assertEqual
200 td = timedelta
201
202 a = td(7) # One week
203 b = td(0, 60) # One minute
204 c = td(0, 0, 1000) # One millisecond
205 eq(a+b+c, td(7, 60, 1000))
206 eq(a-b, td(6, 24*3600 - 60))
207 eq(-a, td(-7))
208 eq(+a, td(7))
209 eq(-b, td(-1, 24*3600 - 60))
210 eq(-c, td(-1, 24*3600 - 1, 999000))
211 eq(abs(a), a)
212 eq(abs(-a), a)
213 eq(td(6, 24*3600), a)
214 eq(td(0, 0, 60*1000000), b)
215 eq(a*10, td(70))
216 eq(a*10, 10*a)
217 eq(a*10L, 10*a)
218 eq(b*10, td(0, 600))
219 eq(10*b, td(0, 600))
220 eq(b*10L, td(0, 600))
221 eq(c*10, td(0, 0, 10000))
222 eq(10*c, td(0, 0, 10000))
223 eq(c*10L, td(0, 0, 10000))
224 eq(a*-1, -a)
225 eq(b*-2, -b-b)
226 eq(c*-2, -c+-c)
227 eq(b*(60*24), (b*60)*24)
228 eq(b*(60*24), (60*b)*24)
229 eq(c*1000, td(0, 1))
230 eq(1000*c, td(0, 1))
231 eq(a//7, td(1))
232 eq(b//10, td(0, 6))
233 eq(c//1000, td(0, 0, 1))
234 eq(a//10, td(0, 7*24*360))
235 eq(a//3600000, td(0, 0, 7*24*1000))
236
237 def test_disallowed_computations(self):
238 a = timedelta(42)
239
240 # Add/sub ints, longs, floats should be illegal
241 for i in 1, 1L, 1.0:
242 self.assertRaises(TypeError, lambda: a+i)
243 self.assertRaises(TypeError, lambda: a-i)
244 self.assertRaises(TypeError, lambda: i+a)
245 self.assertRaises(TypeError, lambda: i-a)
246
247 # Mul/div by float isn't supported.
248 x = 2.3
249 self.assertRaises(TypeError, lambda: a*x)
250 self.assertRaises(TypeError, lambda: x*a)
251 self.assertRaises(TypeError, lambda: a/x)
252 self.assertRaises(TypeError, lambda: x/a)
253 self.assertRaises(TypeError, lambda: a // x)
254 self.assertRaises(TypeError, lambda: x // a)
255
256 # Divison of int by timedelta doesn't make sense.
257 # Division by zero doesn't make sense.
258 for zero in 0, 0L:
259 self.assertRaises(TypeError, lambda: zero // a)
260 self.assertRaises(ZeroDivisionError, lambda: a // zero)
261
262 def test_basic_attributes(self):
263 days, seconds, us = 1, 7, 31
264 td = timedelta(days, seconds, us)
265 self.assertEqual(td.days, days)
266 self.assertEqual(td.seconds, seconds)
267 self.assertEqual(td.microseconds, us)
268
269 def test_carries(self):
270 t1 = timedelta(days=100,
271 weeks=-7,
272 hours=-24*(100-49),
273 minutes=-3,
274 seconds=12,
275 microseconds=(3*60 - 12) * 1e6 + 1)
276 t2 = timedelta(microseconds=1)
277 self.assertEqual(t1, t2)
278
279 def test_hash_equality(self):
280 t1 = timedelta(days=100,
281 weeks=-7,
282 hours=-24*(100-49),
283 minutes=-3,
284 seconds=12,
285 microseconds=(3*60 - 12) * 1000000)
286 t2 = timedelta()
287 self.assertEqual(hash(t1), hash(t2))
288
289 t1 += timedelta(weeks=7)
290 t2 += timedelta(days=7*7)
291 self.assertEqual(t1, t2)
292 self.assertEqual(hash(t1), hash(t2))
293
294 d = {t1: 1}
295 d[t2] = 2
296 self.assertEqual(len(d), 1)
297 self.assertEqual(d[t1], 2)
298
299 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000300 args = 12, 34, 56
301 orig = timedelta(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000302 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +0000303 green = pickler.dumps(orig, proto)
304 derived = unpickler.loads(green)
305 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +0000306
307 def test_compare(self):
308 t1 = timedelta(2, 3, 4)
309 t2 = timedelta(2, 3, 4)
310 self.failUnless(t1 == t2)
311 self.failUnless(t1 <= t2)
312 self.failUnless(t1 >= t2)
313 self.failUnless(not t1 != t2)
314 self.failUnless(not t1 < t2)
315 self.failUnless(not t1 > t2)
316 self.assertEqual(cmp(t1, t2), 0)
317 self.assertEqual(cmp(t2, t1), 0)
318
319 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
320 t2 = timedelta(*args) # this is larger than t1
321 self.failUnless(t1 < t2)
322 self.failUnless(t2 > t1)
323 self.failUnless(t1 <= t2)
324 self.failUnless(t2 >= t1)
325 self.failUnless(t1 != t2)
326 self.failUnless(t2 != t1)
327 self.failUnless(not t1 == t2)
328 self.failUnless(not t2 == t1)
329 self.failUnless(not t1 > t2)
330 self.failUnless(not t2 < t1)
331 self.failUnless(not t1 >= t2)
332 self.failUnless(not t2 <= t1)
333 self.assertEqual(cmp(t1, t2), -1)
334 self.assertEqual(cmp(t2, t1), 1)
335
Tim Peters68124bb2003-02-08 03:46:31 +0000336 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +0000337 self.assertEqual(t1 == badarg, False)
338 self.assertEqual(t1 != badarg, True)
339 self.assertEqual(badarg == t1, False)
340 self.assertEqual(badarg != t1, True)
341
Tim Peters2a799bf2002-12-16 20:18:38 +0000342 self.assertRaises(TypeError, lambda: t1 <= badarg)
343 self.assertRaises(TypeError, lambda: t1 < badarg)
344 self.assertRaises(TypeError, lambda: t1 > badarg)
345 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +0000346 self.assertRaises(TypeError, lambda: badarg <= t1)
347 self.assertRaises(TypeError, lambda: badarg < t1)
348 self.assertRaises(TypeError, lambda: badarg > t1)
349 self.assertRaises(TypeError, lambda: badarg >= t1)
350
351 def test_str(self):
352 td = timedelta
353 eq = self.assertEqual
354
355 eq(str(td(1)), "1 day, 0:00:00")
356 eq(str(td(-1)), "-1 day, 0:00:00")
357 eq(str(td(2)), "2 days, 0:00:00")
358 eq(str(td(-2)), "-2 days, 0:00:00")
359
360 eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
361 eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
362 eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
363 "-210 days, 23:12:34")
364
365 eq(str(td(milliseconds=1)), "0:00:00.001000")
366 eq(str(td(microseconds=3)), "0:00:00.000003")
367
368 eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
369 microseconds=999999)),
370 "999999999 days, 23:59:59.999999")
371
372 def test_roundtrip(self):
373 for td in (timedelta(days=999999999, hours=23, minutes=59,
374 seconds=59, microseconds=999999),
375 timedelta(days=-999999999),
376 timedelta(days=1, seconds=2, microseconds=3)):
377
378 # Verify td -> string -> td identity.
379 s = repr(td)
380 self.failUnless(s.startswith('datetime.'))
381 s = s[9:]
382 td2 = eval(s)
383 self.assertEqual(td, td2)
384
385 # Verify identity via reconstructing from pieces.
386 td2 = timedelta(td.days, td.seconds, td.microseconds)
387 self.assertEqual(td, td2)
388
389 def test_resolution_info(self):
390 self.assert_(isinstance(timedelta.min, timedelta))
391 self.assert_(isinstance(timedelta.max, timedelta))
392 self.assert_(isinstance(timedelta.resolution, timedelta))
393 self.assert_(timedelta.max > timedelta.min)
394 self.assertEqual(timedelta.min, timedelta(-999999999))
395 self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
396 self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
397
398 def test_overflow(self):
399 tiny = timedelta.resolution
400
401 td = timedelta.min + tiny
402 td -= tiny # no problem
403 self.assertRaises(OverflowError, td.__sub__, tiny)
404 self.assertRaises(OverflowError, td.__add__, -tiny)
405
406 td = timedelta.max - tiny
407 td += tiny # no problem
408 self.assertRaises(OverflowError, td.__add__, tiny)
409 self.assertRaises(OverflowError, td.__sub__, -tiny)
410
411 self.assertRaises(OverflowError, lambda: -timedelta.max)
412
413 def test_microsecond_rounding(self):
414 td = timedelta
415 eq = self.assertEqual
416
417 # Single-field rounding.
418 eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
419 eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
420 eq(td(milliseconds=0.6/1000), td(microseconds=1))
421 eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
422
423 # Rounding due to contributions from more than one field.
424 us_per_hour = 3600e6
425 us_per_day = us_per_hour * 24
426 eq(td(days=.4/us_per_day), td(0))
427 eq(td(hours=.2/us_per_hour), td(0))
428 eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
429
430 eq(td(days=-.4/us_per_day), td(0))
431 eq(td(hours=-.2/us_per_hour), td(0))
432 eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
433
434 def test_massive_normalization(self):
435 td = timedelta(microseconds=-1)
436 self.assertEqual((td.days, td.seconds, td.microseconds),
437 (-1, 24*3600-1, 999999))
438
439 def test_bool(self):
440 self.failUnless(timedelta(1))
441 self.failUnless(timedelta(0, 1))
442 self.failUnless(timedelta(0, 0, 1))
443 self.failUnless(timedelta(microseconds=1))
444 self.failUnless(not timedelta(0))
445
Tim Petersb0c854d2003-05-17 15:57:00 +0000446 def test_subclass_timedelta(self):
447
448 class T(timedelta):
449 def from_td(td):
450 return T(td.days, td.seconds, td.microseconds)
451 from_td = staticmethod(from_td)
452
453 def as_hours(self):
454 sum = (self.days * 24 +
455 self.seconds / 3600.0 +
456 self.microseconds / 3600e6)
457 return round(sum)
458
459 t1 = T(days=1)
460 self.assert_(type(t1) is T)
461 self.assertEqual(t1.as_hours(), 24)
462
463 t2 = T(days=-1, seconds=-3600)
464 self.assert_(type(t2) is T)
465 self.assertEqual(t2.as_hours(), -25)
466
467 t3 = t1 + t2
468 self.assert_(type(t3) is timedelta)
469 t4 = T.from_td(t3)
470 self.assert_(type(t4) is T)
471 self.assertEqual(t3.days, t4.days)
472 self.assertEqual(t3.seconds, t4.seconds)
473 self.assertEqual(t3.microseconds, t4.microseconds)
474 self.assertEqual(str(t3), str(t4))
475 self.assertEqual(t4.as_hours(), -1)
476
Tim Peters2a799bf2002-12-16 20:18:38 +0000477#############################################################################
478# date tests
479
480class TestDateOnly(unittest.TestCase):
481 # Tests here won't pass if also run on datetime objects, so don't
482 # subclass this to test datetimes too.
483
484 def test_delta_non_days_ignored(self):
485 dt = date(2000, 1, 2)
486 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
487 microseconds=5)
488 days = timedelta(delta.days)
489 self.assertEqual(days, timedelta(1))
490
491 dt2 = dt + delta
492 self.assertEqual(dt2, dt + days)
493
494 dt2 = delta + dt
495 self.assertEqual(dt2, dt + days)
496
497 dt2 = dt - delta
498 self.assertEqual(dt2, dt - days)
499
500 delta = -delta
501 days = timedelta(delta.days)
502 self.assertEqual(days, timedelta(-2))
503
504 dt2 = dt + delta
505 self.assertEqual(dt2, dt + days)
506
507 dt2 = delta + dt
508 self.assertEqual(dt2, dt + days)
509
510 dt2 = dt - delta
511 self.assertEqual(dt2, dt - days)
512
Tim 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")
Raymond Hettingerf69d9f62003-06-27 08:14:17 +0000834 self.assertEqual(t.strftime(""), "") # SF bug #761337
Tim Peters2a799bf2002-12-16 20:18:38 +0000835
836 self.assertRaises(TypeError, t.strftime) # needs an arg
837 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
838 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
839
840 # A naive object replaces %z and %Z w/ empty strings.
841 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
842
843 def test_resolution_info(self):
844 self.assert_(isinstance(self.theclass.min, self.theclass))
845 self.assert_(isinstance(self.theclass.max, self.theclass))
846 self.assert_(isinstance(self.theclass.resolution, timedelta))
847 self.assert_(self.theclass.max > self.theclass.min)
848
849 def test_extreme_timedelta(self):
850 big = self.theclass.max - self.theclass.min
851 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
852 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
853 # n == 315537897599999999 ~= 2**58.13
854 justasbig = timedelta(0, 0, n)
855 self.assertEqual(big, justasbig)
856 self.assertEqual(self.theclass.min + big, self.theclass.max)
857 self.assertEqual(self.theclass.max - big, self.theclass.min)
858
859 def test_timetuple(self):
860 for i in range(7):
861 # January 2, 1956 is a Monday (0)
862 d = self.theclass(1956, 1, 2+i)
863 t = d.timetuple()
864 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
865 # February 1, 1956 is a Wednesday (2)
866 d = self.theclass(1956, 2, 1+i)
867 t = d.timetuple()
868 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
869 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
870 # of the year.
871 d = self.theclass(1956, 3, 1+i)
872 t = d.timetuple()
873 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
874 self.assertEqual(t.tm_year, 1956)
875 self.assertEqual(t.tm_mon, 3)
876 self.assertEqual(t.tm_mday, 1+i)
877 self.assertEqual(t.tm_hour, 0)
878 self.assertEqual(t.tm_min, 0)
879 self.assertEqual(t.tm_sec, 0)
880 self.assertEqual(t.tm_wday, (3+i)%7)
881 self.assertEqual(t.tm_yday, 61+i)
882 self.assertEqual(t.tm_isdst, -1)
883
884 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000885 args = 6, 7, 23
886 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000887 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +0000888 green = pickler.dumps(orig, proto)
889 derived = unpickler.loads(green)
890 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +0000891
892 def test_compare(self):
893 t1 = self.theclass(2, 3, 4)
894 t2 = self.theclass(2, 3, 4)
895 self.failUnless(t1 == t2)
896 self.failUnless(t1 <= t2)
897 self.failUnless(t1 >= t2)
898 self.failUnless(not t1 != t2)
899 self.failUnless(not t1 < t2)
900 self.failUnless(not t1 > t2)
901 self.assertEqual(cmp(t1, t2), 0)
902 self.assertEqual(cmp(t2, t1), 0)
903
904 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
905 t2 = self.theclass(*args) # this is larger than t1
906 self.failUnless(t1 < t2)
907 self.failUnless(t2 > t1)
908 self.failUnless(t1 <= t2)
909 self.failUnless(t2 >= t1)
910 self.failUnless(t1 != t2)
911 self.failUnless(t2 != t1)
912 self.failUnless(not t1 == t2)
913 self.failUnless(not t2 == t1)
914 self.failUnless(not t1 > t2)
915 self.failUnless(not t2 < t1)
916 self.failUnless(not t1 >= t2)
917 self.failUnless(not t2 <= t1)
918 self.assertEqual(cmp(t1, t2), -1)
919 self.assertEqual(cmp(t2, t1), 1)
920
Tim Peters68124bb2003-02-08 03:46:31 +0000921 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +0000922 self.assertEqual(t1 == badarg, False)
923 self.assertEqual(t1 != badarg, True)
924 self.assertEqual(badarg == t1, False)
925 self.assertEqual(badarg != t1, True)
926
Tim Peters2a799bf2002-12-16 20:18:38 +0000927 self.assertRaises(TypeError, lambda: t1 < badarg)
928 self.assertRaises(TypeError, lambda: t1 > badarg)
929 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +0000930 self.assertRaises(TypeError, lambda: badarg <= t1)
931 self.assertRaises(TypeError, lambda: badarg < t1)
932 self.assertRaises(TypeError, lambda: badarg > t1)
933 self.assertRaises(TypeError, lambda: badarg >= t1)
934
Tim Peters8d81a012003-01-24 22:36:34 +0000935 def test_mixed_compare(self):
936 our = self.theclass(2000, 4, 5)
937 self.assertRaises(TypeError, cmp, our, 1)
938 self.assertRaises(TypeError, cmp, 1, our)
939
940 class AnotherDateTimeClass(object):
941 def __cmp__(self, other):
942 # Return "equal" so calling this can't be confused with
943 # compare-by-address (which never says "equal" for distinct
944 # objects).
945 return 0
946
947 # This still errors, because date and datetime comparison raise
948 # TypeError instead of NotImplemented when they don't know what to
949 # do, in order to stop comparison from falling back to the default
950 # compare-by-address.
951 their = AnotherDateTimeClass()
952 self.assertRaises(TypeError, cmp, our, their)
953 # Oops: The next stab raises TypeError in the C implementation,
954 # but not in the Python implementation of datetime. The difference
955 # is due to that the Python implementation defines __cmp__ but
956 # the C implementation defines tp_richcompare. This is more pain
957 # to fix than it's worth, so commenting out the test.
958 # self.assertEqual(cmp(their, our), 0)
959
960 # But date and datetime comparison return NotImplemented instead if the
961 # other object has a timetuple attr. This gives the other object a
962 # chance to do the comparison.
963 class Comparable(AnotherDateTimeClass):
964 def timetuple(self):
965 return ()
966
967 their = Comparable()
968 self.assertEqual(cmp(our, their), 0)
969 self.assertEqual(cmp(their, our), 0)
970 self.failUnless(our == their)
971 self.failUnless(their == our)
972
Tim Peters2a799bf2002-12-16 20:18:38 +0000973 def test_bool(self):
974 # All dates are considered true.
975 self.failUnless(self.theclass.min)
976 self.failUnless(self.theclass.max)
977
Tim Petersd6844152002-12-22 20:58:42 +0000978 def test_srftime_out_of_range(self):
979 # For nasty technical reasons, we can't handle years before 1900.
980 cls = self.theclass
981 self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900")
982 for y in 1, 49, 51, 99, 100, 1000, 1899:
983 self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y")
Tim Peters12bf3392002-12-24 05:41:27 +0000984
985 def test_replace(self):
986 cls = self.theclass
987 args = [1, 2, 3]
988 base = cls(*args)
989 self.assertEqual(base, base.replace())
990
991 i = 0
992 for name, newval in (("year", 2),
993 ("month", 3),
994 ("day", 4)):
995 newargs = args[:]
996 newargs[i] = newval
997 expected = cls(*newargs)
998 got = base.replace(**{name: newval})
999 self.assertEqual(expected, got)
1000 i += 1
1001
1002 # Out of bounds.
1003 base = cls(2000, 2, 29)
1004 self.assertRaises(ValueError, base.replace, year=2001)
1005
Tim Petersa98924a2003-05-17 05:55:19 +00001006 def test_subclass_date(self):
1007
1008 class C(self.theclass):
1009 theAnswer = 42
1010
1011 def __new__(cls, *args, **kws):
1012 temp = kws.copy()
1013 extra = temp.pop('extra')
1014 result = self.theclass.__new__(cls, *args, **temp)
1015 result.extra = extra
1016 return result
1017
1018 def newmeth(self, start):
1019 return start + self.year + self.month
1020
1021 args = 2003, 4, 14
1022
1023 dt1 = self.theclass(*args)
1024 dt2 = C(*args, **{'extra': 7})
1025
1026 self.assertEqual(dt2.__class__, C)
1027 self.assertEqual(dt2.theAnswer, 42)
1028 self.assertEqual(dt2.extra, 7)
1029 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1030 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1031
Tim Peterseb1a4962003-05-17 02:25:20 +00001032
Tim Peters2a799bf2002-12-16 20:18:38 +00001033#############################################################################
1034# datetime tests
1035
1036class TestDateTime(TestDate):
1037
1038 theclass = datetime
1039
1040 def test_basic_attributes(self):
1041 dt = self.theclass(2002, 3, 1, 12, 0)
1042 self.assertEqual(dt.year, 2002)
1043 self.assertEqual(dt.month, 3)
1044 self.assertEqual(dt.day, 1)
1045 self.assertEqual(dt.hour, 12)
1046 self.assertEqual(dt.minute, 0)
1047 self.assertEqual(dt.second, 0)
1048 self.assertEqual(dt.microsecond, 0)
1049
1050 def test_basic_attributes_nonzero(self):
1051 # Make sure all attributes are non-zero so bugs in
1052 # bit-shifting access show up.
1053 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1054 self.assertEqual(dt.year, 2002)
1055 self.assertEqual(dt.month, 3)
1056 self.assertEqual(dt.day, 1)
1057 self.assertEqual(dt.hour, 12)
1058 self.assertEqual(dt.minute, 59)
1059 self.assertEqual(dt.second, 59)
1060 self.assertEqual(dt.microsecond, 8000)
1061
1062 def test_roundtrip(self):
1063 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1064 self.theclass.now()):
1065 # Verify dt -> string -> datetime identity.
1066 s = repr(dt)
1067 self.failUnless(s.startswith('datetime.'))
1068 s = s[9:]
1069 dt2 = eval(s)
1070 self.assertEqual(dt, dt2)
1071
1072 # Verify identity via reconstructing from pieces.
1073 dt2 = self.theclass(dt.year, dt.month, dt.day,
1074 dt.hour, dt.minute, dt.second,
1075 dt.microsecond)
1076 self.assertEqual(dt, dt2)
1077
1078 def test_isoformat(self):
1079 t = self.theclass(2, 3, 2, 4, 5, 1, 123)
1080 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123")
1081 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
1082 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
1083 # str is ISO format with the separator forced to a blank.
1084 self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
1085
1086 t = self.theclass(2, 3, 2)
1087 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1088 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1089 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1090 # str is ISO format with the separator forced to a blank.
1091 self.assertEqual(str(t), "0002-03-02 00:00:00")
1092
1093 def test_more_ctime(self):
1094 # Test fields that TestDate doesn't touch.
1095 import time
1096
1097 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1098 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1099 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1100 # out. The difference is that t.ctime() produces " 2" for the day,
1101 # but platform ctime() produces "02" for the day. According to
1102 # C99, t.ctime() is correct here.
1103 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1104
1105 # So test a case where that difference doesn't matter.
1106 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1107 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1108
1109 def test_tz_independent_comparing(self):
1110 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1111 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1112 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1113 self.assertEqual(dt1, dt3)
1114 self.assert_(dt2 > dt3)
1115
1116 # Make sure comparison doesn't forget microseconds, and isn't done
1117 # via comparing a float timestamp (an IEEE double doesn't have enough
1118 # precision to span microsecond resolution across years 1 thru 9999,
1119 # so comparing via timestamp necessarily calls some distinct values
1120 # equal).
1121 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1122 us = timedelta(microseconds=1)
1123 dt2 = dt1 + us
1124 self.assertEqual(dt2 - dt1, us)
1125 self.assert_(dt1 < dt2)
1126
1127 def test_bad_constructor_arguments(self):
1128 # bad years
1129 self.theclass(MINYEAR, 1, 1) # no exception
1130 self.theclass(MAXYEAR, 1, 1) # no exception
1131 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1132 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1133 # bad months
1134 self.theclass(2000, 1, 1) # no exception
1135 self.theclass(2000, 12, 1) # no exception
1136 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1137 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1138 # bad days
1139 self.theclass(2000, 2, 29) # no exception
1140 self.theclass(2004, 2, 29) # no exception
1141 self.theclass(2400, 2, 29) # no exception
1142 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1143 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1144 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1145 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1146 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1147 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1148 # bad hours
1149 self.theclass(2000, 1, 31, 0) # no exception
1150 self.theclass(2000, 1, 31, 23) # no exception
1151 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1152 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1153 # bad minutes
1154 self.theclass(2000, 1, 31, 23, 0) # no exception
1155 self.theclass(2000, 1, 31, 23, 59) # no exception
1156 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1157 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1158 # bad seconds
1159 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1160 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1161 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1162 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1163 # bad microseconds
1164 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1165 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1166 self.assertRaises(ValueError, self.theclass,
1167 2000, 1, 31, 23, 59, 59, -1)
1168 self.assertRaises(ValueError, self.theclass,
1169 2000, 1, 31, 23, 59, 59,
1170 1000000)
1171
1172 def test_hash_equality(self):
1173 d = self.theclass(2000, 12, 31, 23, 30, 17)
1174 e = self.theclass(2000, 12, 31, 23, 30, 17)
1175 self.assertEqual(d, e)
1176 self.assertEqual(hash(d), hash(e))
1177
1178 dic = {d: 1}
1179 dic[e] = 2
1180 self.assertEqual(len(dic), 1)
1181 self.assertEqual(dic[d], 2)
1182 self.assertEqual(dic[e], 2)
1183
1184 d = self.theclass(2001, 1, 1, 0, 5, 17)
1185 e = self.theclass(2001, 1, 1, 0, 5, 17)
1186 self.assertEqual(d, e)
1187 self.assertEqual(hash(d), hash(e))
1188
1189 dic = {d: 1}
1190 dic[e] = 2
1191 self.assertEqual(len(dic), 1)
1192 self.assertEqual(dic[d], 2)
1193 self.assertEqual(dic[e], 2)
1194
1195 def test_computations(self):
1196 a = self.theclass(2002, 1, 31)
1197 b = self.theclass(1956, 1, 31)
1198 diff = a-b
1199 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1200 self.assertEqual(diff.seconds, 0)
1201 self.assertEqual(diff.microseconds, 0)
1202 a = self.theclass(2002, 3, 2, 17, 6)
1203 millisec = timedelta(0, 0, 1000)
1204 hour = timedelta(0, 3600)
1205 day = timedelta(1)
1206 week = timedelta(7)
1207 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1208 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1209 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1210 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1211 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1212 self.assertEqual(a - hour, a + -hour)
1213 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1214 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1215 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1216 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1217 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1218 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1219 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1220 self.assertEqual((a + week) - a, week)
1221 self.assertEqual((a + day) - a, day)
1222 self.assertEqual((a + hour) - a, hour)
1223 self.assertEqual((a + millisec) - a, millisec)
1224 self.assertEqual((a - week) - a, -week)
1225 self.assertEqual((a - day) - a, -day)
1226 self.assertEqual((a - hour) - a, -hour)
1227 self.assertEqual((a - millisec) - a, -millisec)
1228 self.assertEqual(a - (a + week), -week)
1229 self.assertEqual(a - (a + day), -day)
1230 self.assertEqual(a - (a + hour), -hour)
1231 self.assertEqual(a - (a + millisec), -millisec)
1232 self.assertEqual(a - (a - week), week)
1233 self.assertEqual(a - (a - day), day)
1234 self.assertEqual(a - (a - hour), hour)
1235 self.assertEqual(a - (a - millisec), millisec)
1236 self.assertEqual(a + (week + day + hour + millisec),
1237 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1238 self.assertEqual(a + (week + day + hour + millisec),
1239 (((a + week) + day) + hour) + millisec)
1240 self.assertEqual(a - (week + day + hour + millisec),
1241 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1242 self.assertEqual(a - (week + day + hour + millisec),
1243 (((a - week) - day) - hour) - millisec)
1244 # Add/sub ints, longs, floats should be illegal
1245 for i in 1, 1L, 1.0:
1246 self.assertRaises(TypeError, lambda: a+i)
1247 self.assertRaises(TypeError, lambda: a-i)
1248 self.assertRaises(TypeError, lambda: i+a)
1249 self.assertRaises(TypeError, lambda: i-a)
1250
1251 # delta - datetime is senseless.
1252 self.assertRaises(TypeError, lambda: day - a)
1253 # mixing datetime and (delta or datetime) via * or // is senseless
1254 self.assertRaises(TypeError, lambda: day * a)
1255 self.assertRaises(TypeError, lambda: a * day)
1256 self.assertRaises(TypeError, lambda: day // a)
1257 self.assertRaises(TypeError, lambda: a // day)
1258 self.assertRaises(TypeError, lambda: a * a)
1259 self.assertRaises(TypeError, lambda: a // a)
1260 # datetime + datetime is senseless
1261 self.assertRaises(TypeError, lambda: a + a)
1262
1263 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001264 args = 6, 7, 23, 20, 59, 1, 64**2
1265 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001266 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001267 green = pickler.dumps(orig, proto)
1268 derived = unpickler.loads(green)
1269 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001270
Guido van Rossum275666f2003-02-07 21:49:01 +00001271 def test_more_pickling(self):
1272 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
1273 s = pickle.dumps(a)
1274 b = pickle.loads(s)
1275 self.assertEqual(b.year, 2003)
1276 self.assertEqual(b.month, 2)
1277 self.assertEqual(b.day, 7)
1278
Tim Peters2a799bf2002-12-16 20:18:38 +00001279 def test_more_compare(self):
1280 # The test_compare() inherited from TestDate covers the error cases.
1281 # We just want to test lexicographic ordering on the members datetime
1282 # has that date lacks.
1283 args = [2000, 11, 29, 20, 58, 16, 999998]
1284 t1 = self.theclass(*args)
1285 t2 = self.theclass(*args)
1286 self.failUnless(t1 == t2)
1287 self.failUnless(t1 <= t2)
1288 self.failUnless(t1 >= t2)
1289 self.failUnless(not t1 != t2)
1290 self.failUnless(not t1 < t2)
1291 self.failUnless(not t1 > t2)
1292 self.assertEqual(cmp(t1, t2), 0)
1293 self.assertEqual(cmp(t2, t1), 0)
1294
1295 for i in range(len(args)):
1296 newargs = args[:]
1297 newargs[i] = args[i] + 1
1298 t2 = self.theclass(*newargs) # this is larger than t1
1299 self.failUnless(t1 < t2)
1300 self.failUnless(t2 > t1)
1301 self.failUnless(t1 <= t2)
1302 self.failUnless(t2 >= t1)
1303 self.failUnless(t1 != t2)
1304 self.failUnless(t2 != t1)
1305 self.failUnless(not t1 == t2)
1306 self.failUnless(not t2 == t1)
1307 self.failUnless(not t1 > t2)
1308 self.failUnless(not t2 < t1)
1309 self.failUnless(not t1 >= t2)
1310 self.failUnless(not t2 <= t1)
1311 self.assertEqual(cmp(t1, t2), -1)
1312 self.assertEqual(cmp(t2, t1), 1)
1313
1314
1315 # A helper for timestamp constructor tests.
1316 def verify_field_equality(self, expected, got):
1317 self.assertEqual(expected.tm_year, got.year)
1318 self.assertEqual(expected.tm_mon, got.month)
1319 self.assertEqual(expected.tm_mday, got.day)
1320 self.assertEqual(expected.tm_hour, got.hour)
1321 self.assertEqual(expected.tm_min, got.minute)
1322 self.assertEqual(expected.tm_sec, got.second)
1323
1324 def test_fromtimestamp(self):
1325 import time
1326
1327 ts = time.time()
1328 expected = time.localtime(ts)
1329 got = self.theclass.fromtimestamp(ts)
1330 self.verify_field_equality(expected, got)
1331
1332 def test_utcfromtimestamp(self):
1333 import time
1334
1335 ts = time.time()
1336 expected = time.gmtime(ts)
1337 got = self.theclass.utcfromtimestamp(ts)
1338 self.verify_field_equality(expected, got)
1339
1340 def test_utcnow(self):
1341 import time
1342
1343 # Call it a success if utcnow() and utcfromtimestamp() are within
1344 # a second of each other.
1345 tolerance = timedelta(seconds=1)
1346 for dummy in range(3):
1347 from_now = self.theclass.utcnow()
1348 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1349 if abs(from_timestamp - from_now) <= tolerance:
1350 break
1351 # Else try again a few times.
1352 self.failUnless(abs(from_timestamp - from_now) <= tolerance)
1353
1354 def test_more_timetuple(self):
1355 # This tests fields beyond those tested by the TestDate.test_timetuple.
1356 t = self.theclass(2004, 12, 31, 6, 22, 33)
1357 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1358 self.assertEqual(t.timetuple(),
1359 (t.year, t.month, t.day,
1360 t.hour, t.minute, t.second,
1361 t.weekday(),
1362 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1363 -1))
1364 tt = t.timetuple()
1365 self.assertEqual(tt.tm_year, t.year)
1366 self.assertEqual(tt.tm_mon, t.month)
1367 self.assertEqual(tt.tm_mday, t.day)
1368 self.assertEqual(tt.tm_hour, t.hour)
1369 self.assertEqual(tt.tm_min, t.minute)
1370 self.assertEqual(tt.tm_sec, t.second)
1371 self.assertEqual(tt.tm_wday, t.weekday())
1372 self.assertEqual(tt.tm_yday, t.toordinal() -
1373 date(t.year, 1, 1).toordinal() + 1)
1374 self.assertEqual(tt.tm_isdst, -1)
1375
1376 def test_more_strftime(self):
1377 # This tests fields beyond those tested by the TestDate.test_strftime.
1378 t = self.theclass(2004, 12, 31, 6, 22, 33)
1379 self.assertEqual(t.strftime("%m %d %y %S %M %H %j"),
1380 "12 31 04 33 22 06 366")
1381
1382 def test_extract(self):
1383 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1384 self.assertEqual(dt.date(), date(2002, 3, 4))
1385 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1386
1387 def test_combine(self):
1388 d = date(2002, 3, 4)
1389 t = time(18, 45, 3, 1234)
1390 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1391 combine = self.theclass.combine
1392 dt = combine(d, t)
1393 self.assertEqual(dt, expected)
1394
1395 dt = combine(time=t, date=d)
1396 self.assertEqual(dt, expected)
1397
1398 self.assertEqual(d, dt.date())
1399 self.assertEqual(t, dt.time())
1400 self.assertEqual(dt, combine(dt.date(), dt.time()))
1401
1402 self.assertRaises(TypeError, combine) # need an arg
1403 self.assertRaises(TypeError, combine, d) # need two args
1404 self.assertRaises(TypeError, combine, t, d) # args reversed
1405 self.assertRaises(TypeError, combine, d, t, 1) # too many args
1406 self.assertRaises(TypeError, combine, "date", "time") # wrong types
1407
Tim Peters12bf3392002-12-24 05:41:27 +00001408 def test_replace(self):
1409 cls = self.theclass
1410 args = [1, 2, 3, 4, 5, 6, 7]
1411 base = cls(*args)
1412 self.assertEqual(base, base.replace())
1413
1414 i = 0
1415 for name, newval in (("year", 2),
1416 ("month", 3),
1417 ("day", 4),
1418 ("hour", 5),
1419 ("minute", 6),
1420 ("second", 7),
1421 ("microsecond", 8)):
1422 newargs = args[:]
1423 newargs[i] = newval
1424 expected = cls(*newargs)
1425 got = base.replace(**{name: newval})
1426 self.assertEqual(expected, got)
1427 i += 1
1428
1429 # Out of bounds.
1430 base = cls(2000, 2, 29)
1431 self.assertRaises(ValueError, base.replace, year=2001)
1432
Tim Peters80475bb2002-12-25 07:40:55 +00001433 def test_astimezone(self):
Tim Peters52dcce22003-01-23 16:36:11 +00001434 # Pretty boring! The TZ test is more interesting here. astimezone()
1435 # simply can't be applied to a naive object.
Tim Peters80475bb2002-12-25 07:40:55 +00001436 dt = self.theclass.now()
1437 f = FixedOffset(44, "")
Tim Peters80475bb2002-12-25 07:40:55 +00001438 self.assertRaises(TypeError, dt.astimezone) # not enough args
1439 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
1440 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Tim Peters52dcce22003-01-23 16:36:11 +00001441 self.assertRaises(ValueError, dt.astimezone, f) # naive
1442 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
Tim Peters80475bb2002-12-25 07:40:55 +00001443
Tim Peters52dcce22003-01-23 16:36:11 +00001444 class Bogus(tzinfo):
1445 def utcoffset(self, dt): return None
1446 def dst(self, dt): return timedelta(0)
1447 bog = Bogus()
1448 self.assertRaises(ValueError, dt.astimezone, bog) # naive
1449
1450 class AlsoBogus(tzinfo):
1451 def utcoffset(self, dt): return timedelta(0)
1452 def dst(self, dt): return None
1453 alsobog = AlsoBogus()
1454 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
Tim Peters12bf3392002-12-24 05:41:27 +00001455
Tim Petersa98924a2003-05-17 05:55:19 +00001456 def test_subclass_datetime(self):
1457
1458 class C(self.theclass):
1459 theAnswer = 42
1460
1461 def __new__(cls, *args, **kws):
1462 temp = kws.copy()
1463 extra = temp.pop('extra')
1464 result = self.theclass.__new__(cls, *args, **temp)
1465 result.extra = extra
1466 return result
1467
1468 def newmeth(self, start):
1469 return start + self.year + self.month + self.second
1470
1471 args = 2003, 4, 14, 12, 13, 41
1472
1473 dt1 = self.theclass(*args)
1474 dt2 = C(*args, **{'extra': 7})
1475
1476 self.assertEqual(dt2.__class__, C)
1477 self.assertEqual(dt2.theAnswer, 42)
1478 self.assertEqual(dt2.extra, 7)
1479 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1480 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
1481 dt1.second - 7)
1482
Tim Peters07534a62003-02-07 22:50:28 +00001483class TestTime(HarmlessMixedComparison):
Tim Peters2a799bf2002-12-16 20:18:38 +00001484
1485 theclass = time
1486
1487 def test_basic_attributes(self):
1488 t = self.theclass(12, 0)
1489 self.assertEqual(t.hour, 12)
1490 self.assertEqual(t.minute, 0)
1491 self.assertEqual(t.second, 0)
1492 self.assertEqual(t.microsecond, 0)
1493
1494 def test_basic_attributes_nonzero(self):
1495 # Make sure all attributes are non-zero so bugs in
1496 # bit-shifting access show up.
1497 t = self.theclass(12, 59, 59, 8000)
1498 self.assertEqual(t.hour, 12)
1499 self.assertEqual(t.minute, 59)
1500 self.assertEqual(t.second, 59)
1501 self.assertEqual(t.microsecond, 8000)
1502
1503 def test_roundtrip(self):
1504 t = self.theclass(1, 2, 3, 4)
1505
1506 # Verify t -> string -> time identity.
1507 s = repr(t)
1508 self.failUnless(s.startswith('datetime.'))
1509 s = s[9:]
1510 t2 = eval(s)
1511 self.assertEqual(t, t2)
1512
1513 # Verify identity via reconstructing from pieces.
1514 t2 = self.theclass(t.hour, t.minute, t.second,
1515 t.microsecond)
1516 self.assertEqual(t, t2)
1517
1518 def test_comparing(self):
1519 args = [1, 2, 3, 4]
1520 t1 = self.theclass(*args)
1521 t2 = self.theclass(*args)
1522 self.failUnless(t1 == t2)
1523 self.failUnless(t1 <= t2)
1524 self.failUnless(t1 >= t2)
1525 self.failUnless(not t1 != t2)
1526 self.failUnless(not t1 < t2)
1527 self.failUnless(not t1 > t2)
1528 self.assertEqual(cmp(t1, t2), 0)
1529 self.assertEqual(cmp(t2, t1), 0)
1530
1531 for i in range(len(args)):
1532 newargs = args[:]
1533 newargs[i] = args[i] + 1
1534 t2 = self.theclass(*newargs) # this is larger than t1
1535 self.failUnless(t1 < t2)
1536 self.failUnless(t2 > t1)
1537 self.failUnless(t1 <= t2)
1538 self.failUnless(t2 >= t1)
1539 self.failUnless(t1 != t2)
1540 self.failUnless(t2 != t1)
1541 self.failUnless(not t1 == t2)
1542 self.failUnless(not t2 == t1)
1543 self.failUnless(not t1 > t2)
1544 self.failUnless(not t2 < t1)
1545 self.failUnless(not t1 >= t2)
1546 self.failUnless(not t2 <= t1)
1547 self.assertEqual(cmp(t1, t2), -1)
1548 self.assertEqual(cmp(t2, t1), 1)
1549
Tim Peters68124bb2003-02-08 03:46:31 +00001550 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +00001551 self.assertEqual(t1 == badarg, False)
1552 self.assertEqual(t1 != badarg, True)
1553 self.assertEqual(badarg == t1, False)
1554 self.assertEqual(badarg != t1, True)
1555
Tim Peters2a799bf2002-12-16 20:18:38 +00001556 self.assertRaises(TypeError, lambda: t1 <= badarg)
1557 self.assertRaises(TypeError, lambda: t1 < badarg)
1558 self.assertRaises(TypeError, lambda: t1 > badarg)
1559 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +00001560 self.assertRaises(TypeError, lambda: badarg <= t1)
1561 self.assertRaises(TypeError, lambda: badarg < t1)
1562 self.assertRaises(TypeError, lambda: badarg > t1)
1563 self.assertRaises(TypeError, lambda: badarg >= t1)
1564
1565 def test_bad_constructor_arguments(self):
1566 # bad hours
1567 self.theclass(0, 0) # no exception
1568 self.theclass(23, 0) # no exception
1569 self.assertRaises(ValueError, self.theclass, -1, 0)
1570 self.assertRaises(ValueError, self.theclass, 24, 0)
1571 # bad minutes
1572 self.theclass(23, 0) # no exception
1573 self.theclass(23, 59) # no exception
1574 self.assertRaises(ValueError, self.theclass, 23, -1)
1575 self.assertRaises(ValueError, self.theclass, 23, 60)
1576 # bad seconds
1577 self.theclass(23, 59, 0) # no exception
1578 self.theclass(23, 59, 59) # no exception
1579 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
1580 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
1581 # bad microseconds
1582 self.theclass(23, 59, 59, 0) # no exception
1583 self.theclass(23, 59, 59, 999999) # no exception
1584 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
1585 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
1586
1587 def test_hash_equality(self):
1588 d = self.theclass(23, 30, 17)
1589 e = self.theclass(23, 30, 17)
1590 self.assertEqual(d, e)
1591 self.assertEqual(hash(d), hash(e))
1592
1593 dic = {d: 1}
1594 dic[e] = 2
1595 self.assertEqual(len(dic), 1)
1596 self.assertEqual(dic[d], 2)
1597 self.assertEqual(dic[e], 2)
1598
1599 d = self.theclass(0, 5, 17)
1600 e = self.theclass(0, 5, 17)
1601 self.assertEqual(d, e)
1602 self.assertEqual(hash(d), hash(e))
1603
1604 dic = {d: 1}
1605 dic[e] = 2
1606 self.assertEqual(len(dic), 1)
1607 self.assertEqual(dic[d], 2)
1608 self.assertEqual(dic[e], 2)
1609
1610 def test_isoformat(self):
1611 t = self.theclass(4, 5, 1, 123)
1612 self.assertEqual(t.isoformat(), "04:05:01.000123")
1613 self.assertEqual(t.isoformat(), str(t))
1614
1615 t = self.theclass()
1616 self.assertEqual(t.isoformat(), "00:00:00")
1617 self.assertEqual(t.isoformat(), str(t))
1618
1619 t = self.theclass(microsecond=1)
1620 self.assertEqual(t.isoformat(), "00:00:00.000001")
1621 self.assertEqual(t.isoformat(), str(t))
1622
1623 t = self.theclass(microsecond=10)
1624 self.assertEqual(t.isoformat(), "00:00:00.000010")
1625 self.assertEqual(t.isoformat(), str(t))
1626
1627 t = self.theclass(microsecond=100)
1628 self.assertEqual(t.isoformat(), "00:00:00.000100")
1629 self.assertEqual(t.isoformat(), str(t))
1630
1631 t = self.theclass(microsecond=1000)
1632 self.assertEqual(t.isoformat(), "00:00:00.001000")
1633 self.assertEqual(t.isoformat(), str(t))
1634
1635 t = self.theclass(microsecond=10000)
1636 self.assertEqual(t.isoformat(), "00:00:00.010000")
1637 self.assertEqual(t.isoformat(), str(t))
1638
1639 t = self.theclass(microsecond=100000)
1640 self.assertEqual(t.isoformat(), "00:00:00.100000")
1641 self.assertEqual(t.isoformat(), str(t))
1642
1643 def test_strftime(self):
1644 t = self.theclass(1, 2, 3, 4)
1645 self.assertEqual(t.strftime('%H %M %S'), "01 02 03")
1646 # A naive object replaces %z and %Z with empty strings.
1647 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1648
1649 def test_str(self):
1650 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
1651 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
1652 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
1653 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
1654 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
1655
1656 def test_repr(self):
1657 name = 'datetime.' + self.theclass.__name__
1658 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
1659 "%s(1, 2, 3, 4)" % name)
1660 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
1661 "%s(10, 2, 3, 4000)" % name)
1662 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
1663 "%s(0, 2, 3, 400000)" % name)
1664 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
1665 "%s(12, 2, 3)" % name)
1666 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
1667 "%s(23, 15)" % name)
1668
1669 def test_resolution_info(self):
1670 self.assert_(isinstance(self.theclass.min, self.theclass))
1671 self.assert_(isinstance(self.theclass.max, self.theclass))
1672 self.assert_(isinstance(self.theclass.resolution, timedelta))
1673 self.assert_(self.theclass.max > self.theclass.min)
1674
1675 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001676 args = 20, 59, 16, 64**2
1677 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001678 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001679 green = pickler.dumps(orig, proto)
1680 derived = unpickler.loads(green)
1681 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001682
1683 def test_bool(self):
1684 cls = self.theclass
1685 self.failUnless(cls(1))
1686 self.failUnless(cls(0, 1))
1687 self.failUnless(cls(0, 0, 1))
1688 self.failUnless(cls(0, 0, 0, 1))
1689 self.failUnless(not cls(0))
1690 self.failUnless(not cls())
1691
Tim Peters12bf3392002-12-24 05:41:27 +00001692 def test_replace(self):
1693 cls = self.theclass
1694 args = [1, 2, 3, 4]
1695 base = cls(*args)
1696 self.assertEqual(base, base.replace())
1697
1698 i = 0
1699 for name, newval in (("hour", 5),
1700 ("minute", 6),
1701 ("second", 7),
1702 ("microsecond", 8)):
1703 newargs = args[:]
1704 newargs[i] = newval
1705 expected = cls(*newargs)
1706 got = base.replace(**{name: newval})
1707 self.assertEqual(expected, got)
1708 i += 1
1709
1710 # Out of bounds.
1711 base = cls(1)
1712 self.assertRaises(ValueError, base.replace, hour=24)
1713 self.assertRaises(ValueError, base.replace, minute=-1)
1714 self.assertRaises(ValueError, base.replace, second=100)
1715 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1716
Tim Petersa98924a2003-05-17 05:55:19 +00001717 def test_subclass_time(self):
1718
1719 class C(self.theclass):
1720 theAnswer = 42
1721
1722 def __new__(cls, *args, **kws):
1723 temp = kws.copy()
1724 extra = temp.pop('extra')
1725 result = self.theclass.__new__(cls, *args, **temp)
1726 result.extra = extra
1727 return result
1728
1729 def newmeth(self, start):
1730 return start + self.hour + self.second
1731
1732 args = 4, 5, 6
1733
1734 dt1 = self.theclass(*args)
1735 dt2 = C(*args, **{'extra': 7})
1736
1737 self.assertEqual(dt2.__class__, C)
1738 self.assertEqual(dt2.theAnswer, 42)
1739 self.assertEqual(dt2.extra, 7)
1740 self.assertEqual(dt1.isoformat(), dt2.isoformat())
1741 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
1742
Tim Peters855fe882002-12-22 03:43:39 +00001743# A mixin for classes with a tzinfo= argument. Subclasses must define
1744# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001745# must be legit (which is true for time and datetime).
Tim Peters855fe882002-12-22 03:43:39 +00001746class TZInfoBase(unittest.TestCase):
Tim Peters2a799bf2002-12-16 20:18:38 +00001747
Tim Petersbad8ff02002-12-30 20:52:32 +00001748 def test_argument_passing(self):
1749 cls = self.theclass
Tim Peters0bf60bd2003-01-08 20:40:01 +00001750 # A datetime passes itself on, a time passes None.
Tim Petersbad8ff02002-12-30 20:52:32 +00001751 class introspective(tzinfo):
1752 def tzname(self, dt): return dt and "real" or "none"
Tim Peters397301e2003-01-02 21:28:08 +00001753 def utcoffset(self, dt):
1754 return timedelta(minutes = dt and 42 or -42)
Tim Petersbad8ff02002-12-30 20:52:32 +00001755 dst = utcoffset
1756
1757 obj = cls(1, 2, 3, tzinfo=introspective())
1758
Tim Peters0bf60bd2003-01-08 20:40:01 +00001759 expected = cls is time and "none" or "real"
Tim Petersbad8ff02002-12-30 20:52:32 +00001760 self.assertEqual(obj.tzname(), expected)
1761
Tim Peters0bf60bd2003-01-08 20:40:01 +00001762 expected = timedelta(minutes=(cls is time and -42 or 42))
Tim Petersbad8ff02002-12-30 20:52:32 +00001763 self.assertEqual(obj.utcoffset(), expected)
1764 self.assertEqual(obj.dst(), expected)
1765
Tim Peters855fe882002-12-22 03:43:39 +00001766 def test_bad_tzinfo_classes(self):
1767 cls = self.theclass
1768 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
Tim Peters2a799bf2002-12-16 20:18:38 +00001769
Tim Peters855fe882002-12-22 03:43:39 +00001770 class NiceTry(object):
1771 def __init__(self): pass
1772 def utcoffset(self, dt): pass
1773 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
1774
1775 class BetterTry(tzinfo):
1776 def __init__(self): pass
1777 def utcoffset(self, dt): pass
1778 b = BetterTry()
1779 t = cls(1, 1, 1, tzinfo=b)
1780 self.failUnless(t.tzinfo is b)
1781
1782 def test_utc_offset_out_of_bounds(self):
1783 class Edgy(tzinfo):
1784 def __init__(self, offset):
Tim Peters397301e2003-01-02 21:28:08 +00001785 self.offset = timedelta(minutes=offset)
Tim Peters855fe882002-12-22 03:43:39 +00001786 def utcoffset(self, dt):
1787 return self.offset
1788
1789 cls = self.theclass
1790 for offset, legit in ((-1440, False),
1791 (-1439, True),
1792 (1439, True),
1793 (1440, False)):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001794 if cls is time:
Tim Peters855fe882002-12-22 03:43:39 +00001795 t = cls(1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001796 elif cls is datetime:
Tim Peters855fe882002-12-22 03:43:39 +00001797 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001798 else:
1799 assert 0, "impossible"
Tim Peters855fe882002-12-22 03:43:39 +00001800 if legit:
1801 aofs = abs(offset)
1802 h, m = divmod(aofs, 60)
1803 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001804 if isinstance(t, datetime):
Tim Peters855fe882002-12-22 03:43:39 +00001805 t = t.timetz()
1806 self.assertEqual(str(t), "01:02:03" + tag)
1807 else:
1808 self.assertRaises(ValueError, str, t)
1809
1810 def test_tzinfo_classes(self):
1811 cls = self.theclass
1812 class C1(tzinfo):
1813 def utcoffset(self, dt): return None
1814 def dst(self, dt): return None
1815 def tzname(self, dt): return None
1816 for t in (cls(1, 1, 1),
1817 cls(1, 1, 1, tzinfo=None),
1818 cls(1, 1, 1, tzinfo=C1())):
1819 self.failUnless(t.utcoffset() is None)
1820 self.failUnless(t.dst() is None)
1821 self.failUnless(t.tzname() is None)
1822
Tim Peters855fe882002-12-22 03:43:39 +00001823 class C3(tzinfo):
1824 def utcoffset(self, dt): return timedelta(minutes=-1439)
1825 def dst(self, dt): return timedelta(minutes=1439)
1826 def tzname(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001827 t = cls(1, 1, 1, tzinfo=C3())
1828 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
1829 self.assertEqual(t.dst(), timedelta(minutes=1439))
1830 self.assertEqual(t.tzname(), "aname")
Tim Peters855fe882002-12-22 03:43:39 +00001831
1832 # Wrong types.
1833 class C4(tzinfo):
1834 def utcoffset(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001835 def dst(self, dt): return 7
Tim Peters855fe882002-12-22 03:43:39 +00001836 def tzname(self, dt): return 0
1837 t = cls(1, 1, 1, tzinfo=C4())
1838 self.assertRaises(TypeError, t.utcoffset)
1839 self.assertRaises(TypeError, t.dst)
1840 self.assertRaises(TypeError, t.tzname)
1841
1842 # Offset out of range.
Tim Peters855fe882002-12-22 03:43:39 +00001843 class C6(tzinfo):
1844 def utcoffset(self, dt): return timedelta(hours=-24)
1845 def dst(self, dt): return timedelta(hours=24)
Tim Peters397301e2003-01-02 21:28:08 +00001846 t = cls(1, 1, 1, tzinfo=C6())
1847 self.assertRaises(ValueError, t.utcoffset)
1848 self.assertRaises(ValueError, t.dst)
Tim Peters855fe882002-12-22 03:43:39 +00001849
1850 # Not a whole number of minutes.
1851 class C7(tzinfo):
1852 def utcoffset(self, dt): return timedelta(seconds=61)
1853 def dst(self, dt): return timedelta(microseconds=-81)
1854 t = cls(1, 1, 1, tzinfo=C7())
1855 self.assertRaises(ValueError, t.utcoffset)
1856 self.assertRaises(ValueError, t.dst)
1857
Tim Peters4c0db782002-12-26 05:01:19 +00001858 def test_aware_compare(self):
1859 cls = self.theclass
1860
Tim Peters60c76e42002-12-27 00:41:11 +00001861 # Ensure that utcoffset() gets ignored if the comparands have
1862 # the same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00001863 class OperandDependentOffset(tzinfo):
1864 def utcoffset(self, t):
1865 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00001866 # d0 and d1 equal after adjustment
1867 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00001868 else:
Tim Peters397301e2003-01-02 21:28:08 +00001869 # d2 off in the weeds
1870 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00001871
1872 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
1873 d0 = base.replace(minute=3)
1874 d1 = base.replace(minute=9)
1875 d2 = base.replace(minute=11)
1876 for x in d0, d1, d2:
1877 for y in d0, d1, d2:
1878 got = cmp(x, y)
Tim Peters60c76e42002-12-27 00:41:11 +00001879 expected = cmp(x.minute, y.minute)
1880 self.assertEqual(got, expected)
1881
1882 # However, if they're different members, uctoffset is not ignored.
Tim Peters0bf60bd2003-01-08 20:40:01 +00001883 # Note that a time can't actually have an operand-depedent offset,
1884 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
1885 # so skip this test for time.
1886 if cls is not time:
Tim Petersbad8ff02002-12-30 20:52:32 +00001887 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
1888 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
1889 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
1890 for x in d0, d1, d2:
1891 for y in d0, d1, d2:
1892 got = cmp(x, y)
1893 if (x is d0 or x is d1) and (y is d0 or y is d1):
1894 expected = 0
1895 elif x is y is d2:
1896 expected = 0
1897 elif x is d2:
1898 expected = -1
1899 else:
1900 assert y is d2
1901 expected = 1
1902 self.assertEqual(got, expected)
Tim Peters4c0db782002-12-26 05:01:19 +00001903
Tim Peters855fe882002-12-22 03:43:39 +00001904
Tim Peters0bf60bd2003-01-08 20:40:01 +00001905# Testing time objects with a non-None tzinfo.
Tim Peters855fe882002-12-22 03:43:39 +00001906class TestTimeTZ(TestTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001907 theclass = time
Tim Peters2a799bf2002-12-16 20:18:38 +00001908
1909 def test_empty(self):
1910 t = self.theclass()
1911 self.assertEqual(t.hour, 0)
1912 self.assertEqual(t.minute, 0)
1913 self.assertEqual(t.second, 0)
1914 self.assertEqual(t.microsecond, 0)
1915 self.failUnless(t.tzinfo is None)
1916
Tim Peters2a799bf2002-12-16 20:18:38 +00001917 def test_zones(self):
1918 est = FixedOffset(-300, "EST", 1)
1919 utc = FixedOffset(0, "UTC", -2)
1920 met = FixedOffset(60, "MET", 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001921 t1 = time( 7, 47, tzinfo=est)
1922 t2 = time(12, 47, tzinfo=utc)
1923 t3 = time(13, 47, tzinfo=met)
1924 t4 = time(microsecond=40)
1925 t5 = time(microsecond=40, tzinfo=utc)
Tim Peters2a799bf2002-12-16 20:18:38 +00001926
1927 self.assertEqual(t1.tzinfo, est)
1928 self.assertEqual(t2.tzinfo, utc)
1929 self.assertEqual(t3.tzinfo, met)
1930 self.failUnless(t4.tzinfo is None)
1931 self.assertEqual(t5.tzinfo, utc)
1932
Tim Peters855fe882002-12-22 03:43:39 +00001933 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
1934 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
1935 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00001936 self.failUnless(t4.utcoffset() is None)
1937 self.assertRaises(TypeError, t1.utcoffset, "no args")
1938
1939 self.assertEqual(t1.tzname(), "EST")
1940 self.assertEqual(t2.tzname(), "UTC")
1941 self.assertEqual(t3.tzname(), "MET")
1942 self.failUnless(t4.tzname() is None)
1943 self.assertRaises(TypeError, t1.tzname, "no args")
1944
Tim Peters855fe882002-12-22 03:43:39 +00001945 self.assertEqual(t1.dst(), timedelta(minutes=1))
1946 self.assertEqual(t2.dst(), timedelta(minutes=-2))
1947 self.assertEqual(t3.dst(), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +00001948 self.failUnless(t4.dst() is None)
1949 self.assertRaises(TypeError, t1.dst, "no args")
1950
1951 self.assertEqual(hash(t1), hash(t2))
1952 self.assertEqual(hash(t1), hash(t3))
1953 self.assertEqual(hash(t2), hash(t3))
1954
1955 self.assertEqual(t1, t2)
1956 self.assertEqual(t1, t3)
1957 self.assertEqual(t2, t3)
1958 self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
1959 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
1960 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
1961
1962 self.assertEqual(str(t1), "07:47:00-05:00")
1963 self.assertEqual(str(t2), "12:47:00+00:00")
1964 self.assertEqual(str(t3), "13:47:00+01:00")
1965 self.assertEqual(str(t4), "00:00:00.000040")
1966 self.assertEqual(str(t5), "00:00:00.000040+00:00")
1967
1968 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
1969 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
1970 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
1971 self.assertEqual(t4.isoformat(), "00:00:00.000040")
1972 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
1973
Tim Peters0bf60bd2003-01-08 20:40:01 +00001974 d = 'datetime.time'
Tim Peters2a799bf2002-12-16 20:18:38 +00001975 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
1976 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
1977 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
1978 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
1979 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
1980
1981 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
1982 "07:47:00 %Z=EST %z=-0500")
1983 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
1984 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
1985
1986 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
Tim Peters0bf60bd2003-01-08 20:40:01 +00001987 t1 = time(23, 59, tzinfo=yuck)
Tim Peters2a799bf2002-12-16 20:18:38 +00001988 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
1989 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
1990
Tim Petersb92bb712002-12-21 17:44:07 +00001991 # Check that an invalid tzname result raises an exception.
1992 class Badtzname(tzinfo):
1993 def tzname(self, dt): return 42
Tim Peters0bf60bd2003-01-08 20:40:01 +00001994 t = time(2, 3, 4, tzinfo=Badtzname())
Tim Petersb92bb712002-12-21 17:44:07 +00001995 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
1996 self.assertRaises(TypeError, t.strftime, "%Z")
Tim Peters2a799bf2002-12-16 20:18:38 +00001997
1998 def test_hash_edge_cases(self):
1999 # Offsets that overflow a basic time.
2000 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2001 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2002 self.assertEqual(hash(t1), hash(t2))
2003
2004 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2005 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2006 self.assertEqual(hash(t1), hash(t2))
2007
Tim Peters2a799bf2002-12-16 20:18:38 +00002008 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002009 # Try one without a tzinfo.
2010 args = 20, 59, 16, 64**2
2011 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002012 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002013 green = pickler.dumps(orig, proto)
2014 derived = unpickler.loads(green)
2015 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002016
2017 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002018 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002019 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002020 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002021 green = pickler.dumps(orig, proto)
2022 derived = unpickler.loads(green)
2023 self.assertEqual(orig, derived)
2024 self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
2025 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2026 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002027
2028 def test_more_bool(self):
2029 # Test cases with non-None tzinfo.
2030 cls = self.theclass
2031
2032 t = cls(0, tzinfo=FixedOffset(-300, ""))
2033 self.failUnless(t)
2034
2035 t = cls(5, tzinfo=FixedOffset(-300, ""))
2036 self.failUnless(t)
2037
2038 t = cls(5, tzinfo=FixedOffset(300, ""))
2039 self.failUnless(not t)
2040
2041 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2042 self.failUnless(not t)
2043
2044 # Mostly ensuring this doesn't overflow internally.
2045 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
2046 self.failUnless(t)
2047
2048 # But this should yield a value error -- the utcoffset is bogus.
2049 t = cls(0, tzinfo=FixedOffset(24*60, ""))
2050 self.assertRaises(ValueError, lambda: bool(t))
2051
2052 # Likewise.
2053 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
2054 self.assertRaises(ValueError, lambda: bool(t))
2055
Tim Peters12bf3392002-12-24 05:41:27 +00002056 def test_replace(self):
2057 cls = self.theclass
2058 z100 = FixedOffset(100, "+100")
2059 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2060 args = [1, 2, 3, 4, z100]
2061 base = cls(*args)
2062 self.assertEqual(base, base.replace())
2063
2064 i = 0
2065 for name, newval in (("hour", 5),
2066 ("minute", 6),
2067 ("second", 7),
2068 ("microsecond", 8),
2069 ("tzinfo", zm200)):
2070 newargs = args[:]
2071 newargs[i] = newval
2072 expected = cls(*newargs)
2073 got = base.replace(**{name: newval})
2074 self.assertEqual(expected, got)
2075 i += 1
2076
2077 # Ensure we can get rid of a tzinfo.
2078 self.assertEqual(base.tzname(), "+100")
2079 base2 = base.replace(tzinfo=None)
2080 self.failUnless(base2.tzinfo is None)
2081 self.failUnless(base2.tzname() is None)
2082
2083 # Ensure we can add one.
2084 base3 = base2.replace(tzinfo=z100)
2085 self.assertEqual(base, base3)
2086 self.failUnless(base.tzinfo is base3.tzinfo)
2087
2088 # Out of bounds.
2089 base = cls(1)
2090 self.assertRaises(ValueError, base.replace, hour=24)
2091 self.assertRaises(ValueError, base.replace, minute=-1)
2092 self.assertRaises(ValueError, base.replace, second=100)
2093 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2094
Tim Peters60c76e42002-12-27 00:41:11 +00002095 def test_mixed_compare(self):
2096 t1 = time(1, 2, 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002097 t2 = time(1, 2, 3)
Tim Peters60c76e42002-12-27 00:41:11 +00002098 self.assertEqual(t1, t2)
2099 t2 = t2.replace(tzinfo=None)
2100 self.assertEqual(t1, t2)
2101 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2102 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002103 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2104 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002105
Tim Peters0bf60bd2003-01-08 20:40:01 +00002106 # In time w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002107 class Varies(tzinfo):
2108 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002109 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002110 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002111 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002112 return self.offset
2113
2114 v = Varies()
2115 t1 = t2.replace(tzinfo=v)
2116 t2 = t2.replace(tzinfo=v)
2117 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2118 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2119 self.assertEqual(t1, t2)
2120
2121 # But if they're not identical, it isn't ignored.
2122 t2 = t2.replace(tzinfo=Varies())
2123 self.failUnless(t1 < t2) # t1's offset counter still going up
2124
Tim Petersa98924a2003-05-17 05:55:19 +00002125 def test_subclass_timetz(self):
2126
2127 class C(self.theclass):
2128 theAnswer = 42
2129
2130 def __new__(cls, *args, **kws):
2131 temp = kws.copy()
2132 extra = temp.pop('extra')
2133 result = self.theclass.__new__(cls, *args, **temp)
2134 result.extra = extra
2135 return result
2136
2137 def newmeth(self, start):
2138 return start + self.hour + self.second
2139
2140 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2141
2142 dt1 = self.theclass(*args)
2143 dt2 = C(*args, **{'extra': 7})
2144
2145 self.assertEqual(dt2.__class__, C)
2146 self.assertEqual(dt2.theAnswer, 42)
2147 self.assertEqual(dt2.extra, 7)
2148 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2149 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2150
Tim Peters4c0db782002-12-26 05:01:19 +00002151
Tim Peters0bf60bd2003-01-08 20:40:01 +00002152# Testing datetime objects with a non-None tzinfo.
2153
Tim Peters855fe882002-12-22 03:43:39 +00002154class TestDateTimeTZ(TestDateTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002155 theclass = datetime
Tim Peters2a799bf2002-12-16 20:18:38 +00002156
2157 def test_trivial(self):
2158 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2159 self.assertEqual(dt.year, 1)
2160 self.assertEqual(dt.month, 2)
2161 self.assertEqual(dt.day, 3)
2162 self.assertEqual(dt.hour, 4)
2163 self.assertEqual(dt.minute, 5)
2164 self.assertEqual(dt.second, 6)
2165 self.assertEqual(dt.microsecond, 7)
2166 self.assertEqual(dt.tzinfo, None)
2167
2168 def test_even_more_compare(self):
2169 # The test_compare() and test_more_compare() inherited from TestDate
2170 # and TestDateTime covered non-tzinfo cases.
2171
2172 # Smallest possible after UTC adjustment.
2173 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2174 # Largest possible after UTC adjustment.
2175 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2176 tzinfo=FixedOffset(-1439, ""))
2177
2178 # Make sure those compare correctly, and w/o overflow.
2179 self.failUnless(t1 < t2)
2180 self.failUnless(t1 != t2)
2181 self.failUnless(t2 > t1)
2182
2183 self.failUnless(t1 == t1)
2184 self.failUnless(t2 == t2)
2185
2186 # Equal afer adjustment.
2187 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2188 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2189 self.assertEqual(t1, t2)
2190
2191 # Change t1 not to subtract a minute, and t1 should be larger.
2192 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2193 self.failUnless(t1 > t2)
2194
2195 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2196 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2197 self.failUnless(t1 < t2)
2198
2199 # Back to the original t1, but make seconds resolve it.
2200 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2201 second=1)
2202 self.failUnless(t1 > t2)
2203
2204 # Likewise, but make microseconds resolve it.
2205 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2206 microsecond=1)
2207 self.failUnless(t1 > t2)
2208
2209 # Make t2 naive and it should fail.
2210 t2 = self.theclass.min
2211 self.assertRaises(TypeError, lambda: t1 == t2)
2212 self.assertEqual(t2, t2)
2213
2214 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2215 class Naive(tzinfo):
2216 def utcoffset(self, dt): return None
2217 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
2218 self.assertRaises(TypeError, lambda: t1 == t2)
2219 self.assertEqual(t2, t2)
2220
2221 # OTOH, it's OK to compare two of these mixing the two ways of being
2222 # naive.
2223 t1 = self.theclass(5, 6, 7)
2224 self.assertEqual(t1, t2)
2225
2226 # Try a bogus uctoffset.
2227 class Bogus(tzinfo):
Tim Peters397301e2003-01-02 21:28:08 +00002228 def utcoffset(self, dt):
2229 return timedelta(minutes=1440) # out of bounds
Tim Peters2a799bf2002-12-16 20:18:38 +00002230 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2231 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
Tim Peters60c76e42002-12-27 00:41:11 +00002232 self.assertRaises(ValueError, lambda: t1 == t2)
Tim Peters2a799bf2002-12-16 20:18:38 +00002233
Tim Peters2a799bf2002-12-16 20:18:38 +00002234 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002235 # Try one without a tzinfo.
2236 args = 6, 7, 23, 20, 59, 1, 64**2
2237 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002238 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002239 green = pickler.dumps(orig, proto)
2240 derived = unpickler.loads(green)
2241 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002242
2243 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002244 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002245 orig = self.theclass(*args, **{'tzinfo': tinfo})
Tim Petersa9bc1682003-01-11 03:39:11 +00002246 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
Guido van Rossum177e41a2003-01-30 22:06:23 +00002247 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002248 green = pickler.dumps(orig, proto)
2249 derived = unpickler.loads(green)
2250 self.assertEqual(orig, derived)
2251 self.failUnless(isinstance(derived.tzinfo,
2252 PicklableFixedOffset))
2253 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2254 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002255
2256 def test_extreme_hashes(self):
2257 # If an attempt is made to hash these via subtracting the offset
2258 # then hashing a datetime object, OverflowError results. The
2259 # Python implementation used to blow up here.
2260 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2261 hash(t)
2262 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2263 tzinfo=FixedOffset(-1439, ""))
2264 hash(t)
2265
2266 # OTOH, an OOB offset should blow up.
2267 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2268 self.assertRaises(ValueError, hash, t)
2269
2270 def test_zones(self):
2271 est = FixedOffset(-300, "EST")
2272 utc = FixedOffset(0, "UTC")
2273 met = FixedOffset(60, "MET")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002274 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2275 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2276 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
Tim Peters2a799bf2002-12-16 20:18:38 +00002277 self.assertEqual(t1.tzinfo, est)
2278 self.assertEqual(t2.tzinfo, utc)
2279 self.assertEqual(t3.tzinfo, met)
Tim Peters855fe882002-12-22 03:43:39 +00002280 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2281 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2282 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002283 self.assertEqual(t1.tzname(), "EST")
2284 self.assertEqual(t2.tzname(), "UTC")
2285 self.assertEqual(t3.tzname(), "MET")
2286 self.assertEqual(hash(t1), hash(t2))
2287 self.assertEqual(hash(t1), hash(t3))
2288 self.assertEqual(hash(t2), hash(t3))
2289 self.assertEqual(t1, t2)
2290 self.assertEqual(t1, t3)
2291 self.assertEqual(t2, t3)
2292 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2293 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2294 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002295 d = 'datetime.datetime(2002, 3, 19, '
Tim Peters2a799bf2002-12-16 20:18:38 +00002296 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2297 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2298 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2299
2300 def test_combine(self):
2301 met = FixedOffset(60, "MET")
2302 d = date(2002, 3, 4)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002303 tz = time(18, 45, 3, 1234, tzinfo=met)
2304 dt = datetime.combine(d, tz)
2305 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
Tim Peters2a799bf2002-12-16 20:18:38 +00002306 tzinfo=met))
2307
2308 def test_extract(self):
2309 met = FixedOffset(60, "MET")
2310 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2311 self.assertEqual(dt.date(), date(2002, 3, 4))
2312 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
Tim Peters0bf60bd2003-01-08 20:40:01 +00002313 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
Tim Peters2a799bf2002-12-16 20:18:38 +00002314
2315 def test_tz_aware_arithmetic(self):
2316 import random
2317
2318 now = self.theclass.now()
2319 tz55 = FixedOffset(-330, "west 5:30")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002320 timeaware = now.time().replace(tzinfo=tz55)
Tim Peters2a799bf2002-12-16 20:18:38 +00002321 nowaware = self.theclass.combine(now.date(), timeaware)
2322 self.failUnless(nowaware.tzinfo is tz55)
2323 self.assertEqual(nowaware.timetz(), timeaware)
2324
2325 # Can't mix aware and non-aware.
2326 self.assertRaises(TypeError, lambda: now - nowaware)
2327 self.assertRaises(TypeError, lambda: nowaware - now)
2328
Tim Peters0bf60bd2003-01-08 20:40:01 +00002329 # And adding datetime's doesn't make sense, aware or not.
Tim Peters2a799bf2002-12-16 20:18:38 +00002330 self.assertRaises(TypeError, lambda: now + nowaware)
2331 self.assertRaises(TypeError, lambda: nowaware + now)
2332 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2333
2334 # Subtracting should yield 0.
2335 self.assertEqual(now - now, timedelta(0))
2336 self.assertEqual(nowaware - nowaware, timedelta(0))
2337
2338 # Adding a delta should preserve tzinfo.
2339 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2340 nowawareplus = nowaware + delta
2341 self.failUnless(nowaware.tzinfo is tz55)
2342 nowawareplus2 = delta + nowaware
2343 self.failUnless(nowawareplus2.tzinfo is tz55)
2344 self.assertEqual(nowawareplus, nowawareplus2)
2345
2346 # that - delta should be what we started with, and that - what we
2347 # started with should be delta.
2348 diff = nowawareplus - delta
2349 self.failUnless(diff.tzinfo is tz55)
2350 self.assertEqual(nowaware, diff)
2351 self.assertRaises(TypeError, lambda: delta - nowawareplus)
2352 self.assertEqual(nowawareplus - nowaware, delta)
2353
2354 # Make up a random timezone.
2355 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
Tim Peters4c0db782002-12-26 05:01:19 +00002356 # Attach it to nowawareplus.
2357 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Tim Peters2a799bf2002-12-16 20:18:38 +00002358 self.failUnless(nowawareplus.tzinfo is tzr)
2359 # Make sure the difference takes the timezone adjustments into account.
2360 got = nowaware - nowawareplus
2361 # Expected: (nowaware base - nowaware offset) -
2362 # (nowawareplus base - nowawareplus offset) =
2363 # (nowaware base - nowawareplus base) +
2364 # (nowawareplus offset - nowaware offset) =
2365 # -delta + nowawareplus offset - nowaware offset
Tim Peters855fe882002-12-22 03:43:39 +00002366 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
Tim Peters2a799bf2002-12-16 20:18:38 +00002367 self.assertEqual(got, expected)
2368
2369 # Try max possible difference.
2370 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2371 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2372 tzinfo=FixedOffset(-1439, "max"))
2373 maxdiff = max - min
2374 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2375 timedelta(minutes=2*1439))
2376
2377 def test_tzinfo_now(self):
2378 meth = self.theclass.now
2379 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2380 base = meth()
2381 # Try with and without naming the keyword.
2382 off42 = FixedOffset(42, "42")
2383 another = meth(off42)
Tim Peters10cadce2003-01-23 19:58:02 +00002384 again = meth(tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002385 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002386 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002387 # Bad argument with and w/o naming the keyword.
2388 self.assertRaises(TypeError, meth, 16)
2389 self.assertRaises(TypeError, meth, tzinfo=16)
2390 # Bad keyword name.
2391 self.assertRaises(TypeError, meth, tinfo=off42)
2392 # Too many args.
2393 self.assertRaises(TypeError, meth, off42, off42)
2394
Tim Peters10cadce2003-01-23 19:58:02 +00002395 # We don't know which time zone we're in, and don't have a tzinfo
2396 # class to represent it, so seeing whether a tz argument actually
2397 # does a conversion is tricky.
2398 weirdtz = FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0)
2399 utc = FixedOffset(0, "utc", 0)
2400 for dummy in range(3):
2401 now = datetime.now(weirdtz)
2402 self.failUnless(now.tzinfo is weirdtz)
2403 utcnow = datetime.utcnow().replace(tzinfo=utc)
2404 now2 = utcnow.astimezone(weirdtz)
2405 if abs(now - now2) < timedelta(seconds=30):
2406 break
2407 # Else the code is broken, or more than 30 seconds passed between
2408 # calls; assuming the latter, just try again.
2409 else:
2410 # Three strikes and we're out.
2411 self.fail("utcnow(), now(tz), or astimezone() may be broken")
2412
Tim Peters2a799bf2002-12-16 20:18:38 +00002413 def test_tzinfo_fromtimestamp(self):
2414 import time
2415 meth = self.theclass.fromtimestamp
2416 ts = time.time()
2417 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2418 base = meth(ts)
2419 # Try with and without naming the keyword.
2420 off42 = FixedOffset(42, "42")
2421 another = meth(ts, off42)
Tim Peters2a44a8d2003-01-23 20:53:10 +00002422 again = meth(ts, tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002423 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002424 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002425 # Bad argument with and w/o naming the keyword.
2426 self.assertRaises(TypeError, meth, ts, 16)
2427 self.assertRaises(TypeError, meth, ts, tzinfo=16)
2428 # Bad keyword name.
2429 self.assertRaises(TypeError, meth, ts, tinfo=off42)
2430 # Too many args.
2431 self.assertRaises(TypeError, meth, ts, off42, off42)
2432 # Too few args.
2433 self.assertRaises(TypeError, meth)
2434
Tim Peters2a44a8d2003-01-23 20:53:10 +00002435 # Try to make sure tz= actually does some conversion.
Tim Peters84407612003-02-06 16:42:14 +00002436 timestamp = 1000000000
2437 utcdatetime = datetime.utcfromtimestamp(timestamp)
2438 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
2439 # But on some flavor of Mac, it's nowhere near that. So we can't have
2440 # any idea here what time that actually is, we can only test that
2441 # relative changes match.
2442 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
2443 tz = FixedOffset(utcoffset, "tz", 0)
2444 expected = utcdatetime + utcoffset
2445 got = datetime.fromtimestamp(timestamp, tz)
2446 self.assertEqual(expected, got.replace(tzinfo=None))
Tim Peters2a44a8d2003-01-23 20:53:10 +00002447
Tim Peters2a799bf2002-12-16 20:18:38 +00002448 def test_tzinfo_utcnow(self):
2449 meth = self.theclass.utcnow
2450 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2451 base = meth()
2452 # Try with and without naming the keyword; for whatever reason,
2453 # utcnow() doesn't accept a tzinfo argument.
2454 off42 = FixedOffset(42, "42")
2455 self.assertRaises(TypeError, meth, off42)
2456 self.assertRaises(TypeError, meth, tzinfo=off42)
2457
2458 def test_tzinfo_utcfromtimestamp(self):
2459 import time
2460 meth = self.theclass.utcfromtimestamp
2461 ts = time.time()
2462 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2463 base = meth(ts)
2464 # Try with and without naming the keyword; for whatever reason,
2465 # utcfromtimestamp() doesn't accept a tzinfo argument.
2466 off42 = FixedOffset(42, "42")
2467 self.assertRaises(TypeError, meth, ts, off42)
2468 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
2469
2470 def test_tzinfo_timetuple(self):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002471 # TestDateTime tested most of this. datetime adds a twist to the
Tim Peters2a799bf2002-12-16 20:18:38 +00002472 # DST flag.
2473 class DST(tzinfo):
2474 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002475 if isinstance(dstvalue, int):
2476 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002477 self.dstvalue = dstvalue
2478 def dst(self, dt):
2479 return self.dstvalue
2480
2481 cls = self.theclass
2482 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
2483 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
2484 t = d.timetuple()
2485 self.assertEqual(1, t.tm_year)
2486 self.assertEqual(1, t.tm_mon)
2487 self.assertEqual(1, t.tm_mday)
2488 self.assertEqual(10, t.tm_hour)
2489 self.assertEqual(20, t.tm_min)
2490 self.assertEqual(30, t.tm_sec)
2491 self.assertEqual(0, t.tm_wday)
2492 self.assertEqual(1, t.tm_yday)
2493 self.assertEqual(flag, t.tm_isdst)
2494
2495 # dst() returns wrong type.
2496 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
2497
2498 # dst() at the edge.
2499 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
2500 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
2501
2502 # dst() out of range.
2503 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
2504 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
2505
2506 def test_utctimetuple(self):
2507 class DST(tzinfo):
2508 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002509 if isinstance(dstvalue, int):
2510 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002511 self.dstvalue = dstvalue
2512 def dst(self, dt):
2513 return self.dstvalue
2514
2515 cls = self.theclass
2516 # This can't work: DST didn't implement utcoffset.
2517 self.assertRaises(NotImplementedError,
2518 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
2519
2520 class UOFS(DST):
2521 def __init__(self, uofs, dofs=None):
2522 DST.__init__(self, dofs)
Tim Peters397301e2003-01-02 21:28:08 +00002523 self.uofs = timedelta(minutes=uofs)
Tim Peters2a799bf2002-12-16 20:18:38 +00002524 def utcoffset(self, dt):
2525 return self.uofs
2526
2527 # Ensure tm_isdst is 0 regardless of what dst() says: DST is never
2528 # in effect for a UTC time.
2529 for dstvalue in -33, 33, 0, None:
2530 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
2531 t = d.utctimetuple()
2532 self.assertEqual(d.year, t.tm_year)
2533 self.assertEqual(d.month, t.tm_mon)
2534 self.assertEqual(d.day, t.tm_mday)
2535 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
2536 self.assertEqual(13, t.tm_min)
2537 self.assertEqual(d.second, t.tm_sec)
2538 self.assertEqual(d.weekday(), t.tm_wday)
2539 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
2540 t.tm_yday)
2541 self.assertEqual(0, t.tm_isdst)
2542
2543 # At the edges, UTC adjustment can normalize into years out-of-range
2544 # for a datetime object. Ensure that a correct timetuple is
2545 # created anyway.
2546 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
2547 # That goes back 1 minute less than a full day.
2548 t = tiny.utctimetuple()
2549 self.assertEqual(t.tm_year, MINYEAR-1)
2550 self.assertEqual(t.tm_mon, 12)
2551 self.assertEqual(t.tm_mday, 31)
2552 self.assertEqual(t.tm_hour, 0)
2553 self.assertEqual(t.tm_min, 1)
2554 self.assertEqual(t.tm_sec, 37)
2555 self.assertEqual(t.tm_yday, 366) # "year 0" is a leap year
2556 self.assertEqual(t.tm_isdst, 0)
2557
2558 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
2559 # That goes forward 1 minute less than a full day.
2560 t = huge.utctimetuple()
2561 self.assertEqual(t.tm_year, MAXYEAR+1)
2562 self.assertEqual(t.tm_mon, 1)
2563 self.assertEqual(t.tm_mday, 1)
2564 self.assertEqual(t.tm_hour, 23)
2565 self.assertEqual(t.tm_min, 58)
2566 self.assertEqual(t.tm_sec, 37)
2567 self.assertEqual(t.tm_yday, 1)
2568 self.assertEqual(t.tm_isdst, 0)
2569
2570 def test_tzinfo_isoformat(self):
2571 zero = FixedOffset(0, "+00:00")
2572 plus = FixedOffset(220, "+03:40")
2573 minus = FixedOffset(-231, "-03:51")
2574 unknown = FixedOffset(None, "")
2575
2576 cls = self.theclass
2577 datestr = '0001-02-03'
2578 for ofs in None, zero, plus, minus, unknown:
Tim Peters6578dc92002-12-24 18:31:27 +00002579 for us in 0, 987001:
Tim Peters2a799bf2002-12-16 20:18:38 +00002580 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
2581 timestr = '04:05:59' + (us and '.987001' or '')
2582 ofsstr = ofs is not None and d.tzname() or ''
2583 tailstr = timestr + ofsstr
2584 iso = d.isoformat()
2585 self.assertEqual(iso, datestr + 'T' + tailstr)
2586 self.assertEqual(iso, d.isoformat('T'))
2587 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
2588 self.assertEqual(str(d), datestr + ' ' + tailstr)
2589
Tim Peters12bf3392002-12-24 05:41:27 +00002590 def test_replace(self):
2591 cls = self.theclass
2592 z100 = FixedOffset(100, "+100")
2593 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2594 args = [1, 2, 3, 4, 5, 6, 7, z100]
2595 base = cls(*args)
2596 self.assertEqual(base, base.replace())
2597
2598 i = 0
2599 for name, newval in (("year", 2),
2600 ("month", 3),
2601 ("day", 4),
2602 ("hour", 5),
2603 ("minute", 6),
2604 ("second", 7),
2605 ("microsecond", 8),
2606 ("tzinfo", zm200)):
2607 newargs = args[:]
2608 newargs[i] = newval
2609 expected = cls(*newargs)
2610 got = base.replace(**{name: newval})
2611 self.assertEqual(expected, got)
2612 i += 1
2613
2614 # Ensure we can get rid of a tzinfo.
2615 self.assertEqual(base.tzname(), "+100")
2616 base2 = base.replace(tzinfo=None)
2617 self.failUnless(base2.tzinfo is None)
2618 self.failUnless(base2.tzname() is None)
2619
2620 # Ensure we can add one.
2621 base3 = base2.replace(tzinfo=z100)
2622 self.assertEqual(base, base3)
2623 self.failUnless(base.tzinfo is base3.tzinfo)
2624
2625 # Out of bounds.
2626 base = cls(2000, 2, 29)
2627 self.assertRaises(ValueError, base.replace, year=2001)
Tim Peters2a799bf2002-12-16 20:18:38 +00002628
Tim Peters80475bb2002-12-25 07:40:55 +00002629 def test_more_astimezone(self):
2630 # The inherited test_astimezone covered some trivial and error cases.
2631 fnone = FixedOffset(None, "None")
2632 f44m = FixedOffset(44, "44")
2633 fm5h = FixedOffset(-timedelta(hours=5), "m300")
2634
Tim Peters10cadce2003-01-23 19:58:02 +00002635 dt = self.theclass.now(tz=f44m)
Tim Peters80475bb2002-12-25 07:40:55 +00002636 self.failUnless(dt.tzinfo is f44m)
Tim Peters52dcce22003-01-23 16:36:11 +00002637 # Replacing with degenerate tzinfo raises an exception.
2638 self.assertRaises(ValueError, dt.astimezone, fnone)
2639 # Ditto with None tz.
2640 self.assertRaises(TypeError, dt.astimezone, None)
2641 # Replacing with same tzinfo makes no change.
Tim Peters80475bb2002-12-25 07:40:55 +00002642 x = dt.astimezone(dt.tzinfo)
2643 self.failUnless(x.tzinfo is f44m)
2644 self.assertEqual(x.date(), dt.date())
2645 self.assertEqual(x.time(), dt.time())
2646
2647 # Replacing with different tzinfo does adjust.
2648 got = dt.astimezone(fm5h)
2649 self.failUnless(got.tzinfo is fm5h)
2650 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
2651 expected = dt - dt.utcoffset() # in effect, convert to UTC
2652 expected += fm5h.utcoffset(dt) # and from there to local time
2653 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
2654 self.assertEqual(got.date(), expected.date())
2655 self.assertEqual(got.time(), expected.time())
2656 self.assertEqual(got.timetz(), expected.timetz())
2657 self.failUnless(got.tzinfo is expected.tzinfo)
2658 self.assertEqual(got, expected)
2659
Tim Peters4c0db782002-12-26 05:01:19 +00002660 def test_aware_subtract(self):
2661 cls = self.theclass
2662
Tim Peters60c76e42002-12-27 00:41:11 +00002663 # Ensure that utcoffset() is ignored when the operands have the
2664 # same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00002665 class OperandDependentOffset(tzinfo):
2666 def utcoffset(self, t):
2667 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00002668 # d0 and d1 equal after adjustment
2669 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00002670 else:
Tim Peters397301e2003-01-02 21:28:08 +00002671 # d2 off in the weeds
2672 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00002673
2674 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
2675 d0 = base.replace(minute=3)
2676 d1 = base.replace(minute=9)
2677 d2 = base.replace(minute=11)
2678 for x in d0, d1, d2:
2679 for y in d0, d1, d2:
2680 got = x - y
Tim Peters60c76e42002-12-27 00:41:11 +00002681 expected = timedelta(minutes=x.minute - y.minute)
2682 self.assertEqual(got, expected)
2683
2684 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
2685 # ignored.
2686 base = cls(8, 9, 10, 11, 12, 13, 14)
2687 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2688 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2689 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2690 for x in d0, d1, d2:
2691 for y in d0, d1, d2:
2692 got = x - y
Tim Peters4c0db782002-12-26 05:01:19 +00002693 if (x is d0 or x is d1) and (y is d0 or y is d1):
2694 expected = timedelta(0)
2695 elif x is y is d2:
2696 expected = timedelta(0)
2697 elif x is d2:
2698 expected = timedelta(minutes=(11-59)-0)
2699 else:
2700 assert y is d2
2701 expected = timedelta(minutes=0-(11-59))
2702 self.assertEqual(got, expected)
2703
Tim Peters60c76e42002-12-27 00:41:11 +00002704 def test_mixed_compare(self):
2705 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002706 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters60c76e42002-12-27 00:41:11 +00002707 self.assertEqual(t1, t2)
2708 t2 = t2.replace(tzinfo=None)
2709 self.assertEqual(t1, t2)
2710 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2711 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002712 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2713 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002714
Tim Peters0bf60bd2003-01-08 20:40:01 +00002715 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002716 class Varies(tzinfo):
2717 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002718 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002719 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002720 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002721 return self.offset
2722
2723 v = Varies()
2724 t1 = t2.replace(tzinfo=v)
2725 t2 = t2.replace(tzinfo=v)
2726 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2727 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2728 self.assertEqual(t1, t2)
2729
2730 # But if they're not identical, it isn't ignored.
2731 t2 = t2.replace(tzinfo=Varies())
2732 self.failUnless(t1 < t2) # t1's offset counter still going up
Tim Peters80475bb2002-12-25 07:40:55 +00002733
Tim Petersa98924a2003-05-17 05:55:19 +00002734 def test_subclass_datetimetz(self):
2735
2736 class C(self.theclass):
2737 theAnswer = 42
2738
2739 def __new__(cls, *args, **kws):
2740 temp = kws.copy()
2741 extra = temp.pop('extra')
2742 result = self.theclass.__new__(cls, *args, **temp)
2743 result.extra = extra
2744 return result
2745
2746 def newmeth(self, start):
2747 return start + self.hour + self.year
2748
2749 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2750
2751 dt1 = self.theclass(*args)
2752 dt2 = C(*args, **{'extra': 7})
2753
2754 self.assertEqual(dt2.__class__, C)
2755 self.assertEqual(dt2.theAnswer, 42)
2756 self.assertEqual(dt2.extra, 7)
2757 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2758 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
2759
Tim Peters621818b2002-12-29 23:44:49 +00002760# Pain to set up DST-aware tzinfo classes.
2761
2762def first_sunday_on_or_after(dt):
2763 days_to_go = 6 - dt.weekday()
2764 if days_to_go:
2765 dt += timedelta(days_to_go)
2766 return dt
2767
2768ZERO = timedelta(0)
2769HOUR = timedelta(hours=1)
2770DAY = timedelta(days=1)
2771# In the US, DST starts at 2am (standard time) on the first Sunday in April.
2772DSTSTART = datetime(1, 4, 1, 2)
2773# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
Tim Peters327098a2003-01-20 22:54:38 +00002774# which is the first Sunday on or after Oct 25. Because we view 1:MM as
2775# being standard time on that day, there is no spelling in local time of
2776# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
2777DSTEND = datetime(1, 10, 25, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002778
2779class USTimeZone(tzinfo):
2780
2781 def __init__(self, hours, reprname, stdname, dstname):
2782 self.stdoffset = timedelta(hours=hours)
2783 self.reprname = reprname
2784 self.stdname = stdname
2785 self.dstname = dstname
2786
2787 def __repr__(self):
2788 return self.reprname
2789
2790 def tzname(self, dt):
2791 if self.dst(dt):
2792 return self.dstname
2793 else:
2794 return self.stdname
2795
2796 def utcoffset(self, dt):
2797 return self.stdoffset + self.dst(dt)
2798
2799 def dst(self, dt):
Tim Petersbad8ff02002-12-30 20:52:32 +00002800 if dt is None or dt.tzinfo is None:
Tim Peters621818b2002-12-29 23:44:49 +00002801 # An exception instead may be sensible here, in one or more of
2802 # the cases.
2803 return ZERO
Tim Peters521fc152002-12-31 17:36:56 +00002804 assert dt.tzinfo is self
Tim Peters621818b2002-12-29 23:44:49 +00002805
2806 # Find first Sunday in April.
2807 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
2808 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
2809
2810 # Find last Sunday in October.
2811 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
2812 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
2813
Tim Peters621818b2002-12-29 23:44:49 +00002814 # Can't compare naive to aware objects, so strip the timezone from
2815 # dt first.
Tim Peters52dcce22003-01-23 16:36:11 +00002816 if start <= dt.replace(tzinfo=None) < end:
Tim Peters621818b2002-12-29 23:44:49 +00002817 return HOUR
2818 else:
2819 return ZERO
2820
Tim Peters521fc152002-12-31 17:36:56 +00002821Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
2822Central = USTimeZone(-6, "Central", "CST", "CDT")
2823Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
2824Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
Tim Peters1024bf82002-12-30 17:09:40 +00002825utc_real = FixedOffset(0, "UTC", 0)
2826# For better test coverage, we want another flavor of UTC that's west of
2827# the Eastern and Pacific timezones.
Tim Petersadf64202003-01-04 06:03:15 +00002828utc_fake = FixedOffset(-12*60, "UTCfake", 0)
Tim Peters621818b2002-12-29 23:44:49 +00002829
2830class TestTimezoneConversions(unittest.TestCase):
Tim Peters327098a2003-01-20 22:54:38 +00002831 # The DST switch times for 2002, in std time.
Tim Peters0bf60bd2003-01-08 20:40:01 +00002832 dston = datetime(2002, 4, 7, 2)
Tim Peters327098a2003-01-20 22:54:38 +00002833 dstoff = datetime(2002, 10, 27, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002834
Tim Peters0bf60bd2003-01-08 20:40:01 +00002835 theclass = datetime
Tim Peters710fb152003-01-02 19:35:54 +00002836
Tim Peters521fc152002-12-31 17:36:56 +00002837 # Check a time that's inside DST.
2838 def checkinside(self, dt, tz, utc, dston, dstoff):
2839 self.assertEqual(dt.dst(), HOUR)
2840
2841 # Conversion to our own timezone is always an identity.
2842 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters521fc152002-12-31 17:36:56 +00002843
2844 asutc = dt.astimezone(utc)
2845 there_and_back = asutc.astimezone(tz)
2846
2847 # Conversion to UTC and back isn't always an identity here,
2848 # because there are redundant spellings (in local time) of
2849 # UTC time when DST begins: the clock jumps from 1:59:59
2850 # to 3:00:00, and a local time of 2:MM:SS doesn't really
2851 # make sense then. The classes above treat 2:MM:SS as
2852 # daylight time then (it's "after 2am"), really an alias
2853 # for 1:MM:SS standard time. The latter form is what
2854 # conversion back from UTC produces.
2855 if dt.date() == dston.date() and dt.hour == 2:
2856 # We're in the redundant hour, and coming back from
2857 # UTC gives the 1:MM:SS standard-time spelling.
2858 self.assertEqual(there_and_back + HOUR, dt)
2859 # Although during was considered to be in daylight
2860 # time, there_and_back is not.
2861 self.assertEqual(there_and_back.dst(), ZERO)
2862 # They're the same times in UTC.
2863 self.assertEqual(there_and_back.astimezone(utc),
2864 dt.astimezone(utc))
2865 else:
2866 # We're not in the redundant hour.
2867 self.assertEqual(dt, there_and_back)
2868
Tim Peters327098a2003-01-20 22:54:38 +00002869 # Because we have a redundant spelling when DST begins, there is
2870 # (unforunately) an hour when DST ends that can't be spelled at all in
2871 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
2872 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
2873 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
2874 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
2875 # expressed in local time. Nevertheless, we want conversion back
2876 # from UTC to mimic the local clock's "repeat an hour" behavior.
Tim Peters521fc152002-12-31 17:36:56 +00002877 nexthour_utc = asutc + HOUR
Tim Petersadf64202003-01-04 06:03:15 +00002878 nexthour_tz = nexthour_utc.astimezone(tz)
Tim Peters327098a2003-01-20 22:54:38 +00002879 if dt.date() == dstoff.date() and dt.hour == 0:
2880 # We're in the hour before the last DST hour. The last DST hour
Tim Petersadf64202003-01-04 06:03:15 +00002881 # is ineffable. We want the conversion back to repeat 1:MM.
Tim Peters327098a2003-01-20 22:54:38 +00002882 self.assertEqual(nexthour_tz, dt.replace(hour=1))
2883 nexthour_utc += HOUR
2884 nexthour_tz = nexthour_utc.astimezone(tz)
2885 self.assertEqual(nexthour_tz, dt.replace(hour=1))
Tim Peters521fc152002-12-31 17:36:56 +00002886 else:
Tim Peters327098a2003-01-20 22:54:38 +00002887 self.assertEqual(nexthour_tz - dt, HOUR)
Tim Peters521fc152002-12-31 17:36:56 +00002888
2889 # Check a time that's outside DST.
2890 def checkoutside(self, dt, tz, utc):
2891 self.assertEqual(dt.dst(), ZERO)
2892
2893 # Conversion to our own timezone is always an identity.
2894 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters52dcce22003-01-23 16:36:11 +00002895
2896 # Converting to UTC and back is an identity too.
2897 asutc = dt.astimezone(utc)
2898 there_and_back = asutc.astimezone(tz)
2899 self.assertEqual(dt, there_and_back)
Tim Peters521fc152002-12-31 17:36:56 +00002900
Tim Peters1024bf82002-12-30 17:09:40 +00002901 def convert_between_tz_and_utc(self, tz, utc):
2902 dston = self.dston.replace(tzinfo=tz)
Tim Peters327098a2003-01-20 22:54:38 +00002903 # Because 1:MM on the day DST ends is taken as being standard time,
2904 # there is no spelling in tz for the last hour of daylight time.
2905 # For purposes of the test, the last hour of DST is 0:MM, which is
2906 # taken as being daylight time (and 1:MM is taken as being standard
2907 # time).
Tim Peters1024bf82002-12-30 17:09:40 +00002908 dstoff = self.dstoff.replace(tzinfo=tz)
2909 for delta in (timedelta(weeks=13),
2910 DAY,
2911 HOUR,
2912 timedelta(minutes=1),
2913 timedelta(microseconds=1)):
2914
Tim Peters521fc152002-12-31 17:36:56 +00002915 self.checkinside(dston, tz, utc, dston, dstoff)
2916 for during in dston + delta, dstoff - delta:
2917 self.checkinside(during, tz, utc, dston, dstoff)
Tim Peters31cc3152002-12-30 17:37:30 +00002918
Tim Peters521fc152002-12-31 17:36:56 +00002919 self.checkoutside(dstoff, tz, utc)
2920 for outside in dston - delta, dstoff + delta:
2921 self.checkoutside(outside, tz, utc)
Tim Peters31cc3152002-12-30 17:37:30 +00002922
Tim Peters621818b2002-12-29 23:44:49 +00002923 def test_easy(self):
2924 # Despite the name of this test, the endcases are excruciating.
Tim Peters1024bf82002-12-30 17:09:40 +00002925 self.convert_between_tz_and_utc(Eastern, utc_real)
2926 self.convert_between_tz_and_utc(Pacific, utc_real)
2927 self.convert_between_tz_and_utc(Eastern, utc_fake)
2928 self.convert_between_tz_and_utc(Pacific, utc_fake)
2929 # The next is really dancing near the edge. It works because
2930 # Pacific and Eastern are far enough apart that their "problem
2931 # hours" don't overlap.
2932 self.convert_between_tz_and_utc(Eastern, Pacific)
2933 self.convert_between_tz_and_utc(Pacific, Eastern)
Tim Peters36087ed2003-01-01 04:18:51 +00002934 # OTOH, these fail! Don't enable them. The difficulty is that
2935 # the edge case tests assume that every hour is representable in
2936 # the "utc" class. This is always true for a fixed-offset tzinfo
2937 # class (lke utc_real and utc_fake), but not for Eastern or Central.
2938 # For these adjacent DST-aware time zones, the range of time offsets
2939 # tested ends up creating hours in the one that aren't representable
2940 # in the other. For the same reason, we would see failures in the
2941 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
2942 # offset deltas in convert_between_tz_and_utc().
2943 #
2944 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
2945 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
Tim Peters621818b2002-12-29 23:44:49 +00002946
Tim Petersf3615152003-01-01 21:51:37 +00002947 def test_tricky(self):
2948 # 22:00 on day before daylight starts.
2949 fourback = self.dston - timedelta(hours=4)
2950 ninewest = FixedOffset(-9*60, "-0900", 0)
Tim Peters52dcce22003-01-23 16:36:11 +00002951 fourback = fourback.replace(tzinfo=ninewest)
Tim Petersf3615152003-01-01 21:51:37 +00002952 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
2953 # 2", we should get the 3 spelling.
2954 # If we plug 22:00 the day before into Eastern, it "looks like std
2955 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
2956 # to 22:00 lands on 2:00, which makes no sense in local time (the
2957 # local clock jumps from 1 to 3). The point here is to make sure we
2958 # get the 3 spelling.
2959 expected = self.dston.replace(hour=3)
Tim Peters52dcce22003-01-23 16:36:11 +00002960 got = fourback.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00002961 self.assertEqual(expected, got)
2962
2963 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
2964 # case we want the 1:00 spelling.
Tim Peters52dcce22003-01-23 16:36:11 +00002965 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
Tim Petersf3615152003-01-01 21:51:37 +00002966 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
2967 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
2968 # spelling.
2969 expected = self.dston.replace(hour=1)
Tim Peters52dcce22003-01-23 16:36:11 +00002970 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00002971 self.assertEqual(expected, got)
Tim Peters621818b2002-12-29 23:44:49 +00002972
Tim Petersadf64202003-01-04 06:03:15 +00002973 # Now on the day DST ends, we want "repeat an hour" behavior.
2974 # UTC 4:MM 5:MM 6:MM 7:MM checking these
2975 # EST 23:MM 0:MM 1:MM 2:MM
2976 # EDT 0:MM 1:MM 2:MM 3:MM
2977 # wall 0:MM 1:MM 1:MM 2:MM against these
2978 for utc in utc_real, utc_fake:
2979 for tz in Eastern, Pacific:
Tim Peters327098a2003-01-20 22:54:38 +00002980 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
Tim Petersadf64202003-01-04 06:03:15 +00002981 # Convert that to UTC.
2982 first_std_hour -= tz.utcoffset(None)
2983 # Adjust for possibly fake UTC.
2984 asutc = first_std_hour + utc.utcoffset(None)
2985 # First UTC hour to convert; this is 4:00 when utc=utc_real &
2986 # tz=Eastern.
2987 asutcbase = asutc.replace(tzinfo=utc)
2988 for tzhour in (0, 1, 1, 2):
2989 expectedbase = self.dstoff.replace(hour=tzhour)
2990 for minute in 0, 30, 59:
2991 expected = expectedbase.replace(minute=minute)
2992 asutc = asutcbase.replace(minute=minute)
2993 astz = asutc.astimezone(tz)
2994 self.assertEqual(astz.replace(tzinfo=None), expected)
2995 asutcbase += HOUR
2996
2997
Tim Peters710fb152003-01-02 19:35:54 +00002998 def test_bogus_dst(self):
2999 class ok(tzinfo):
3000 def utcoffset(self, dt): return HOUR
3001 def dst(self, dt): return HOUR
3002
3003 now = self.theclass.now().replace(tzinfo=utc_real)
3004 # Doesn't blow up.
3005 now.astimezone(ok())
3006
3007 # Does blow up.
3008 class notok(ok):
3009 def dst(self, dt): return None
3010 self.assertRaises(ValueError, now.astimezone, notok())
3011
Tim Peters52dcce22003-01-23 16:36:11 +00003012 def test_fromutc(self):
3013 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
3014 now = datetime.utcnow().replace(tzinfo=utc_real)
3015 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3016 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
3017 enow = Eastern.fromutc(now) # doesn't blow up
3018 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3019 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3020 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3021
3022 # Always converts UTC to standard time.
3023 class FauxUSTimeZone(USTimeZone):
3024 def fromutc(self, dt):
3025 return dt + self.stdoffset
3026 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
3027
3028 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
3029 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
3030 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
3031
3032 # Check around DST start.
3033 start = self.dston.replace(hour=4, tzinfo=Eastern)
3034 fstart = start.replace(tzinfo=FEastern)
3035 for wall in 23, 0, 1, 3, 4, 5:
3036 expected = start.replace(hour=wall)
3037 if wall == 23:
3038 expected -= timedelta(days=1)
3039 got = Eastern.fromutc(start)
3040 self.assertEqual(expected, got)
3041
3042 expected = fstart + FEastern.stdoffset
3043 got = FEastern.fromutc(fstart)
3044 self.assertEqual(expected, got)
3045
3046 # Ensure astimezone() calls fromutc() too.
3047 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3048 self.assertEqual(expected, got)
3049
3050 start += HOUR
3051 fstart += HOUR
3052
3053 # Check around DST end.
3054 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
3055 fstart = start.replace(tzinfo=FEastern)
3056 for wall in 0, 1, 1, 2, 3, 4:
3057 expected = start.replace(hour=wall)
3058 got = Eastern.fromutc(start)
3059 self.assertEqual(expected, got)
3060
3061 expected = fstart + FEastern.stdoffset
3062 got = FEastern.fromutc(fstart)
3063 self.assertEqual(expected, got)
3064
3065 # Ensure astimezone() calls fromutc() too.
3066 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3067 self.assertEqual(expected, got)
3068
3069 start += HOUR
3070 fstart += HOUR
3071
Tim Peters710fb152003-01-02 19:35:54 +00003072
Tim Peterscfd4a8b2002-12-16 21:12:37 +00003073def test_suite():
Tim Peters2a799bf2002-12-16 20:18:38 +00003074 allsuites = [unittest.makeSuite(klass, 'test')
3075 for klass in (TestModule,
3076 TestTZInfo,
3077 TestTimeDelta,
3078 TestDateOnly,
3079 TestDate,
3080 TestDateTime,
3081 TestTime,
3082 TestTimeTZ,
3083 TestDateTimeTZ,
Tim Peters621818b2002-12-29 23:44:49 +00003084 TestTimezoneConversions,
Tim Peters2a799bf2002-12-16 20:18:38 +00003085 )
3086 ]
3087 return unittest.TestSuite(allsuites)
3088
3089def test_main():
3090 import gc
3091 import sys
3092
Tim Peterscfd4a8b2002-12-16 21:12:37 +00003093 thesuite = test_suite()
Tim Peters2a799bf2002-12-16 20:18:38 +00003094 lastrc = None
3095 while True:
3096 test_support.run_suite(thesuite)
3097 if 1: # change to 0, under a debug build, for some leak detection
3098 break
3099 gc.collect()
3100 if gc.garbage:
3101 raise SystemError("gc.garbage not empty after test run: %r" %
3102 gc.garbage)
3103 if hasattr(sys, 'gettotalrefcount'):
3104 thisrc = sys.gettotalrefcount()
3105 print >> sys.stderr, '*' * 10, 'total refs:', thisrc,
3106 if lastrc:
3107 print >> sys.stderr, 'delta:', thisrc - lastrc
3108 else:
3109 print >> sys.stderr
3110 lastrc = thisrc
3111
3112if __name__ == "__main__":
3113 test_main()