blob: 11deffccd7a7faf9516aa89274bf7630020ec45f [file] [log] [blame]
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001"""Test date/time type.
2
3See http://www.zope.org/Members/fdrake/DateTimeWiki/TestCases
4"""
5
Antoine Pitrou392f4132014-10-03 11:25:30 +02006import decimal
Alexander Belopolskycf86e362010-07-23 19:25:47 +00007import sys
8import pickle
Raymond Hettinger5a2146a2014-07-25 14:59:48 -07009import random
Alexander Belopolskycf86e362010-07-23 19:25:47 +000010import unittest
11
12from operator import lt, le, gt, ge, eq, ne, truediv, floordiv, mod
13
14from test import support
15
16import datetime as datetime_module
17from datetime import MINYEAR, MAXYEAR
18from datetime import timedelta
19from datetime import tzinfo
20from datetime import time
21from datetime import timezone
22from datetime import date, datetime
23import time as _time
24
25# Needed by test_datetime
26import _strptime
27#
28
29
30pickle_choices = [(pickle, pickle, proto)
31 for proto in range(pickle.HIGHEST_PROTOCOL + 1)]
32assert len(pickle_choices) == pickle.HIGHEST_PROTOCOL + 1
33
34# An arbitrary collection of objects of non-datetime types, for testing
35# mixed-type comparisons.
36OTHERSTUFF = (10, 34.5, "abc", {}, [], ())
37
38
39# XXX Copied from test_float.
40INF = float("inf")
41NAN = float("nan")
42
Alexander Belopolskycf86e362010-07-23 19:25:47 +000043
44#############################################################################
45# module tests
46
47class TestModule(unittest.TestCase):
48
49 def test_constants(self):
50 datetime = datetime_module
51 self.assertEqual(datetime.MINYEAR, 1)
52 self.assertEqual(datetime.MAXYEAR, 9999)
53
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -040054 def test_name_cleanup(self):
55 if '_Fast' not in str(self):
56 return
57 datetime = datetime_module
58 names = set(name for name in dir(datetime)
59 if not name.startswith('__') and not name.endswith('__'))
60 allowed = set(['MAXYEAR', 'MINYEAR', 'date', 'datetime',
61 'datetime_CAPI', 'time', 'timedelta', 'timezone',
62 'tzinfo'])
63 self.assertEqual(names - allowed, set([]))
64
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -050065 def test_divide_and_round(self):
66 if '_Fast' in str(self):
67 return
68 dar = datetime_module._divide_and_round
69
70 self.assertEqual(dar(-10, -3), 3)
71 self.assertEqual(dar(5, -2), -2)
72
73 # four cases: (2 signs of a) x (2 signs of b)
74 self.assertEqual(dar(7, 3), 2)
75 self.assertEqual(dar(-7, 3), -2)
76 self.assertEqual(dar(7, -3), -2)
77 self.assertEqual(dar(-7, -3), 2)
78
79 # ties to even - eight cases:
80 # (2 signs of a) x (2 signs of b) x (even / odd quotient)
81 self.assertEqual(dar(10, 4), 2)
82 self.assertEqual(dar(-10, 4), -2)
83 self.assertEqual(dar(10, -4), -2)
84 self.assertEqual(dar(-10, -4), 2)
85
86 self.assertEqual(dar(6, 4), 2)
87 self.assertEqual(dar(-6, 4), -2)
88 self.assertEqual(dar(6, -4), -2)
89 self.assertEqual(dar(-6, -4), 2)
90
91
Alexander Belopolskycf86e362010-07-23 19:25:47 +000092#############################################################################
93# tzinfo tests
94
95class FixedOffset(tzinfo):
96
97 def __init__(self, offset, name, dstoffset=42):
98 if isinstance(offset, int):
99 offset = timedelta(minutes=offset)
100 if isinstance(dstoffset, int):
101 dstoffset = timedelta(minutes=dstoffset)
102 self.__offset = offset
103 self.__name = name
104 self.__dstoffset = dstoffset
105 def __repr__(self):
106 return self.__name.lower()
107 def utcoffset(self, dt):
108 return self.__offset
109 def tzname(self, dt):
110 return self.__name
111 def dst(self, dt):
112 return self.__dstoffset
113
114class PicklableFixedOffset(FixedOffset):
115
116 def __init__(self, offset=None, name=None, dstoffset=None):
117 FixedOffset.__init__(self, offset, name, dstoffset)
118
Raymond Hettinger5a2146a2014-07-25 14:59:48 -0700119class _TZInfo(tzinfo):
120 def utcoffset(self, datetime_module):
121 return random.random()
122
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000123class TestTZInfo(unittest.TestCase):
124
Raymond Hettinger5a2146a2014-07-25 14:59:48 -0700125 def test_refcnt_crash_bug_22044(self):
126 tz1 = _TZInfo()
127 dt1 = datetime(2014, 7, 21, 11, 32, 3, 0, tz1)
128 with self.assertRaises(TypeError):
129 dt1.utcoffset()
130
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000131 def test_non_abstractness(self):
132 # In order to allow subclasses to get pickled, the C implementation
133 # wasn't able to get away with having __init__ raise
134 # NotImplementedError.
135 useless = tzinfo()
136 dt = datetime.max
137 self.assertRaises(NotImplementedError, useless.tzname, dt)
138 self.assertRaises(NotImplementedError, useless.utcoffset, dt)
139 self.assertRaises(NotImplementedError, useless.dst, dt)
140
141 def test_subclass_must_override(self):
142 class NotEnough(tzinfo):
143 def __init__(self, offset, name):
144 self.__offset = offset
145 self.__name = name
146 self.assertTrue(issubclass(NotEnough, tzinfo))
147 ne = NotEnough(3, "NotByALongShot")
148 self.assertIsInstance(ne, tzinfo)
149
150 dt = datetime.now()
151 self.assertRaises(NotImplementedError, ne.tzname, dt)
152 self.assertRaises(NotImplementedError, ne.utcoffset, dt)
153 self.assertRaises(NotImplementedError, ne.dst, dt)
154
155 def test_normal(self):
156 fo = FixedOffset(3, "Three")
157 self.assertIsInstance(fo, tzinfo)
158 for dt in datetime.now(), None:
159 self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3))
160 self.assertEqual(fo.tzname(dt), "Three")
161 self.assertEqual(fo.dst(dt), timedelta(minutes=42))
162
163 def test_pickling_base(self):
164 # There's no point to pickling tzinfo objects on their own (they
165 # carry no data), but they need to be picklable anyway else
166 # concrete subclasses can't be pickled.
167 orig = tzinfo.__new__(tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200168 self.assertIs(type(orig), tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000169 for pickler, unpickler, proto in pickle_choices:
170 green = pickler.dumps(orig, proto)
171 derived = unpickler.loads(green)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200172 self.assertIs(type(derived), tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000173
174 def test_pickling_subclass(self):
175 # Make sure we can pickle/unpickle an instance of a subclass.
176 offset = timedelta(minutes=-300)
177 for otype, args in [
178 (PicklableFixedOffset, (offset, 'cookie')),
179 (timezone, (offset,)),
180 (timezone, (offset, "EST"))]:
181 orig = otype(*args)
182 oname = orig.tzname(None)
183 self.assertIsInstance(orig, tzinfo)
184 self.assertIs(type(orig), otype)
185 self.assertEqual(orig.utcoffset(None), offset)
186 self.assertEqual(orig.tzname(None), oname)
187 for pickler, unpickler, proto in pickle_choices:
188 green = pickler.dumps(orig, proto)
189 derived = unpickler.loads(green)
190 self.assertIsInstance(derived, tzinfo)
191 self.assertIs(type(derived), otype)
192 self.assertEqual(derived.utcoffset(None), offset)
193 self.assertEqual(derived.tzname(None), oname)
194
Alexander Belopolskyc79447b2015-09-27 21:41:55 -0400195 def test_issue23600(self):
196 DSTDIFF = DSTOFFSET = timedelta(hours=1)
197
198 class UKSummerTime(tzinfo):
199 """Simple time zone which pretends to always be in summer time, since
200 that's what shows the failure.
201 """
202
203 def utcoffset(self, dt):
204 return DSTOFFSET
205
206 def dst(self, dt):
207 return DSTDIFF
208
209 def tzname(self, dt):
210 return 'UKSummerTime'
211
212 tz = UKSummerTime()
213 u = datetime(2014, 4, 26, 12, 1, tzinfo=tz)
214 t = tz.fromutc(u)
215 self.assertEqual(t - t.utcoffset(), u)
216
217
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000218class TestTimeZone(unittest.TestCase):
219
220 def setUp(self):
221 self.ACDT = timezone(timedelta(hours=9.5), 'ACDT')
222 self.EST = timezone(-timedelta(hours=5), 'EST')
223 self.DT = datetime(2010, 1, 1)
224
225 def test_str(self):
226 for tz in [self.ACDT, self.EST, timezone.utc,
227 timezone.min, timezone.max]:
228 self.assertEqual(str(tz), tz.tzname(None))
229
230 def test_repr(self):
231 datetime = datetime_module
232 for tz in [self.ACDT, self.EST, timezone.utc,
233 timezone.min, timezone.max]:
234 # test round-trip
235 tzrep = repr(tz)
236 self.assertEqual(tz, eval(tzrep))
237
238
239 def test_class_members(self):
240 limit = timedelta(hours=23, minutes=59)
241 self.assertEqual(timezone.utc.utcoffset(None), ZERO)
242 self.assertEqual(timezone.min.utcoffset(None), -limit)
243 self.assertEqual(timezone.max.utcoffset(None), limit)
244
245
246 def test_constructor(self):
Alexander Belopolsky1bcbaab2010-10-14 17:03:51 +0000247 self.assertIs(timezone.utc, timezone(timedelta(0)))
248 self.assertIsNot(timezone.utc, timezone(timedelta(0), 'UTC'))
249 self.assertEqual(timezone.utc, timezone(timedelta(0), 'UTC'))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000250 # invalid offsets
251 for invalid in [timedelta(microseconds=1), timedelta(1, 1),
252 timedelta(seconds=1), timedelta(1), -timedelta(1)]:
253 self.assertRaises(ValueError, timezone, invalid)
254 self.assertRaises(ValueError, timezone, -invalid)
255
256 with self.assertRaises(TypeError): timezone(None)
257 with self.assertRaises(TypeError): timezone(42)
258 with self.assertRaises(TypeError): timezone(ZERO, None)
259 with self.assertRaises(TypeError): timezone(ZERO, 42)
260 with self.assertRaises(TypeError): timezone(ZERO, 'ABC', 'extra')
261
262 def test_inheritance(self):
263 self.assertIsInstance(timezone.utc, tzinfo)
264 self.assertIsInstance(self.EST, tzinfo)
265
266 def test_utcoffset(self):
267 dummy = self.DT
268 for h in [0, 1.5, 12]:
269 offset = h * HOUR
270 self.assertEqual(offset, timezone(offset).utcoffset(dummy))
271 self.assertEqual(-offset, timezone(-offset).utcoffset(dummy))
272
273 with self.assertRaises(TypeError): self.EST.utcoffset('')
274 with self.assertRaises(TypeError): self.EST.utcoffset(5)
275
276
277 def test_dst(self):
Raymond Hettinger7beae8a2011-01-06 05:34:17 +0000278 self.assertIsNone(timezone.utc.dst(self.DT))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000279
280 with self.assertRaises(TypeError): self.EST.dst('')
281 with self.assertRaises(TypeError): self.EST.dst(5)
282
283 def test_tzname(self):
Alexander Belopolsky7827a5b2015-09-06 13:07:21 -0400284 self.assertEqual('UTC', timezone.utc.tzname(None))
285 self.assertEqual('UTC', timezone(ZERO).tzname(None))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000286 self.assertEqual('UTC-05:00', timezone(-5 * HOUR).tzname(None))
287 self.assertEqual('UTC+09:30', timezone(9.5 * HOUR).tzname(None))
288 self.assertEqual('UTC-00:01', timezone(timedelta(minutes=-1)).tzname(None))
289 self.assertEqual('XYZ', timezone(-5 * HOUR, 'XYZ').tzname(None))
290
291 with self.assertRaises(TypeError): self.EST.tzname('')
292 with self.assertRaises(TypeError): self.EST.tzname(5)
293
294 def test_fromutc(self):
295 with self.assertRaises(ValueError):
296 timezone.utc.fromutc(self.DT)
297 with self.assertRaises(TypeError):
298 timezone.utc.fromutc('not datetime')
299 for tz in [self.EST, self.ACDT, Eastern]:
300 utctime = self.DT.replace(tzinfo=tz)
301 local = tz.fromutc(utctime)
302 self.assertEqual(local - utctime, tz.utcoffset(local))
303 self.assertEqual(local,
304 self.DT.replace(tzinfo=timezone.utc))
305
306 def test_comparison(self):
307 self.assertNotEqual(timezone(ZERO), timezone(HOUR))
308 self.assertEqual(timezone(HOUR), timezone(HOUR))
309 self.assertEqual(timezone(-5 * HOUR), timezone(-5 * HOUR, 'EST'))
310 with self.assertRaises(TypeError): timezone(ZERO) < timezone(ZERO)
311 self.assertIn(timezone(ZERO), {timezone(ZERO)})
Georg Brandl0085a242012-09-22 09:23:12 +0200312 self.assertTrue(timezone(ZERO) != None)
313 self.assertFalse(timezone(ZERO) == None)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000314
315 def test_aware_datetime(self):
316 # test that timezone instances can be used by datetime
317 t = datetime(1, 1, 1)
318 for tz in [timezone.min, timezone.max, timezone.utc]:
319 self.assertEqual(tz.tzname(t),
320 t.replace(tzinfo=tz).tzname())
321 self.assertEqual(tz.utcoffset(t),
322 t.replace(tzinfo=tz).utcoffset())
323 self.assertEqual(tz.dst(t),
324 t.replace(tzinfo=tz).dst())
325
326#############################################################################
Ezio Melotti85a86292013-08-17 16:57:41 +0300327# Base class for testing a particular aspect of timedelta, time, date and
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000328# datetime comparisons.
329
330class HarmlessMixedComparison:
331 # Test that __eq__ and __ne__ don't complain for mixed-type comparisons.
332
333 # Subclasses must define 'theclass', and theclass(1, 1, 1) must be a
334 # legit constructor.
335
336 def test_harmless_mixed_comparison(self):
337 me = self.theclass(1, 1, 1)
338
339 self.assertFalse(me == ())
340 self.assertTrue(me != ())
341 self.assertFalse(() == me)
342 self.assertTrue(() != me)
343
344 self.assertIn(me, [1, 20, [], me])
345 self.assertIn([], [me, 1, 20, []])
346
347 def test_harmful_mixed_comparison(self):
348 me = self.theclass(1, 1, 1)
349
350 self.assertRaises(TypeError, lambda: me < ())
351 self.assertRaises(TypeError, lambda: me <= ())
352 self.assertRaises(TypeError, lambda: me > ())
353 self.assertRaises(TypeError, lambda: me >= ())
354
355 self.assertRaises(TypeError, lambda: () < me)
356 self.assertRaises(TypeError, lambda: () <= me)
357 self.assertRaises(TypeError, lambda: () > me)
358 self.assertRaises(TypeError, lambda: () >= me)
359
360#############################################################################
361# timedelta tests
362
363class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
364
365 theclass = timedelta
366
367 def test_constructor(self):
368 eq = self.assertEqual
369 td = timedelta
370
371 # Check keyword args to constructor
372 eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
373 milliseconds=0, microseconds=0))
374 eq(td(1), td(days=1))
375 eq(td(0, 1), td(seconds=1))
376 eq(td(0, 0, 1), td(microseconds=1))
377 eq(td(weeks=1), td(days=7))
378 eq(td(days=1), td(hours=24))
379 eq(td(hours=1), td(minutes=60))
380 eq(td(minutes=1), td(seconds=60))
381 eq(td(seconds=1), td(milliseconds=1000))
382 eq(td(milliseconds=1), td(microseconds=1000))
383
384 # Check float args to constructor
385 eq(td(weeks=1.0/7), td(days=1))
386 eq(td(days=1.0/24), td(hours=1))
387 eq(td(hours=1.0/60), td(minutes=1))
388 eq(td(minutes=1.0/60), td(seconds=1))
389 eq(td(seconds=0.001), td(milliseconds=1))
390 eq(td(milliseconds=0.001), td(microseconds=1))
391
392 def test_computations(self):
393 eq = self.assertEqual
394 td = timedelta
395
396 a = td(7) # One week
397 b = td(0, 60) # One minute
398 c = td(0, 0, 1000) # One millisecond
399 eq(a+b+c, td(7, 60, 1000))
400 eq(a-b, td(6, 24*3600 - 60))
401 eq(b.__rsub__(a), td(6, 24*3600 - 60))
402 eq(-a, td(-7))
403 eq(+a, td(7))
404 eq(-b, td(-1, 24*3600 - 60))
405 eq(-c, td(-1, 24*3600 - 1, 999000))
406 eq(abs(a), a)
407 eq(abs(-a), a)
408 eq(td(6, 24*3600), a)
409 eq(td(0, 0, 60*1000000), b)
410 eq(a*10, td(70))
411 eq(a*10, 10*a)
412 eq(a*10, 10*a)
413 eq(b*10, td(0, 600))
414 eq(10*b, td(0, 600))
415 eq(b*10, td(0, 600))
416 eq(c*10, td(0, 0, 10000))
417 eq(10*c, td(0, 0, 10000))
418 eq(c*10, td(0, 0, 10000))
419 eq(a*-1, -a)
420 eq(b*-2, -b-b)
421 eq(c*-2, -c+-c)
422 eq(b*(60*24), (b*60)*24)
423 eq(b*(60*24), (60*b)*24)
424 eq(c*1000, td(0, 1))
425 eq(1000*c, td(0, 1))
426 eq(a//7, td(1))
427 eq(b//10, td(0, 6))
428 eq(c//1000, td(0, 0, 1))
429 eq(a//10, td(0, 7*24*360))
430 eq(a//3600000, td(0, 0, 7*24*1000))
431 eq(a/0.5, td(14))
432 eq(b/0.5, td(0, 120))
433 eq(a/7, td(1))
434 eq(b/10, td(0, 6))
435 eq(c/1000, td(0, 0, 1))
436 eq(a/10, td(0, 7*24*360))
437 eq(a/3600000, td(0, 0, 7*24*1000))
438
439 # Multiplication by float
440 us = td(microseconds=1)
441 eq((3*us) * 0.5, 2*us)
442 eq((5*us) * 0.5, 2*us)
443 eq(0.5 * (3*us), 2*us)
444 eq(0.5 * (5*us), 2*us)
445 eq((-3*us) * 0.5, -2*us)
446 eq((-5*us) * 0.5, -2*us)
447
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -0500448 # Issue #23521
449 eq(td(seconds=1) * 0.123456, td(microseconds=123456))
450 eq(td(seconds=1) * 0.6112295, td(microseconds=611229))
451
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000452 # Division by int and float
453 eq((3*us) / 2, 2*us)
454 eq((5*us) / 2, 2*us)
455 eq((-3*us) / 2.0, -2*us)
456 eq((-5*us) / 2.0, -2*us)
457 eq((3*us) / -2, -2*us)
458 eq((5*us) / -2, -2*us)
459 eq((3*us) / -2.0, -2*us)
460 eq((5*us) / -2.0, -2*us)
461 for i in range(-10, 10):
462 eq((i*us/3)//us, round(i/3))
463 for i in range(-10, 10):
464 eq((i*us/-3)//us, round(i/-3))
465
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -0500466 # Issue #23521
467 eq(td(seconds=1) / (1 / 0.6112295), td(microseconds=611229))
468
Alexander Belopolskyb6f5ec72011-04-05 20:07:38 -0400469 # Issue #11576
470 eq(td(999999999, 86399, 999999) - td(999999999, 86399, 999998),
471 td(0, 0, 1))
472 eq(td(999999999, 1, 1) - td(999999999, 1, 0),
473 td(0, 0, 1))
474
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000475 def test_disallowed_computations(self):
476 a = timedelta(42)
477
478 # Add/sub ints or floats should be illegal
479 for i in 1, 1.0:
480 self.assertRaises(TypeError, lambda: a+i)
481 self.assertRaises(TypeError, lambda: a-i)
482 self.assertRaises(TypeError, lambda: i+a)
483 self.assertRaises(TypeError, lambda: i-a)
484
485 # Division of int by timedelta doesn't make sense.
486 # Division by zero doesn't make sense.
487 zero = 0
488 self.assertRaises(TypeError, lambda: zero // a)
489 self.assertRaises(ZeroDivisionError, lambda: a // zero)
490 self.assertRaises(ZeroDivisionError, lambda: a / zero)
491 self.assertRaises(ZeroDivisionError, lambda: a / 0.0)
492 self.assertRaises(TypeError, lambda: a / '')
493
Eric Smith3ab08ca2010-12-04 15:17:38 +0000494 @support.requires_IEEE_754
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000495 def test_disallowed_special(self):
496 a = timedelta(42)
497 self.assertRaises(ValueError, a.__mul__, NAN)
498 self.assertRaises(ValueError, a.__truediv__, NAN)
499
500 def test_basic_attributes(self):
501 days, seconds, us = 1, 7, 31
502 td = timedelta(days, seconds, us)
503 self.assertEqual(td.days, days)
504 self.assertEqual(td.seconds, seconds)
505 self.assertEqual(td.microseconds, us)
506
507 def test_total_seconds(self):
508 td = timedelta(days=365)
509 self.assertEqual(td.total_seconds(), 31536000.0)
510 for total_seconds in [123456.789012, -123456.789012, 0.123456, 0, 1e6]:
511 td = timedelta(seconds=total_seconds)
512 self.assertEqual(td.total_seconds(), total_seconds)
513 # Issue8644: Test that td.total_seconds() has the same
514 # accuracy as td / timedelta(seconds=1).
515 for ms in [-1, -2, -123]:
516 td = timedelta(microseconds=ms)
517 self.assertEqual(td.total_seconds(), td / timedelta(seconds=1))
518
519 def test_carries(self):
520 t1 = timedelta(days=100,
521 weeks=-7,
522 hours=-24*(100-49),
523 minutes=-3,
524 seconds=12,
525 microseconds=(3*60 - 12) * 1e6 + 1)
526 t2 = timedelta(microseconds=1)
527 self.assertEqual(t1, t2)
528
529 def test_hash_equality(self):
530 t1 = timedelta(days=100,
531 weeks=-7,
532 hours=-24*(100-49),
533 minutes=-3,
534 seconds=12,
535 microseconds=(3*60 - 12) * 1000000)
536 t2 = timedelta()
537 self.assertEqual(hash(t1), hash(t2))
538
539 t1 += timedelta(weeks=7)
540 t2 += timedelta(days=7*7)
541 self.assertEqual(t1, t2)
542 self.assertEqual(hash(t1), hash(t2))
543
544 d = {t1: 1}
545 d[t2] = 2
546 self.assertEqual(len(d), 1)
547 self.assertEqual(d[t1], 2)
548
549 def test_pickling(self):
550 args = 12, 34, 56
551 orig = timedelta(*args)
552 for pickler, unpickler, proto in pickle_choices:
553 green = pickler.dumps(orig, proto)
554 derived = unpickler.loads(green)
555 self.assertEqual(orig, derived)
556
557 def test_compare(self):
558 t1 = timedelta(2, 3, 4)
559 t2 = timedelta(2, 3, 4)
560 self.assertEqual(t1, t2)
561 self.assertTrue(t1 <= t2)
562 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200563 self.assertFalse(t1 != t2)
564 self.assertFalse(t1 < t2)
565 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000566
567 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
568 t2 = timedelta(*args) # this is larger than t1
569 self.assertTrue(t1 < t2)
570 self.assertTrue(t2 > t1)
571 self.assertTrue(t1 <= t2)
572 self.assertTrue(t2 >= t1)
573 self.assertTrue(t1 != t2)
574 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200575 self.assertFalse(t1 == t2)
576 self.assertFalse(t2 == t1)
577 self.assertFalse(t1 > t2)
578 self.assertFalse(t2 < t1)
579 self.assertFalse(t1 >= t2)
580 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000581
582 for badarg in OTHERSTUFF:
583 self.assertEqual(t1 == badarg, False)
584 self.assertEqual(t1 != badarg, True)
585 self.assertEqual(badarg == t1, False)
586 self.assertEqual(badarg != t1, True)
587
588 self.assertRaises(TypeError, lambda: t1 <= badarg)
589 self.assertRaises(TypeError, lambda: t1 < badarg)
590 self.assertRaises(TypeError, lambda: t1 > badarg)
591 self.assertRaises(TypeError, lambda: t1 >= badarg)
592 self.assertRaises(TypeError, lambda: badarg <= t1)
593 self.assertRaises(TypeError, lambda: badarg < t1)
594 self.assertRaises(TypeError, lambda: badarg > t1)
595 self.assertRaises(TypeError, lambda: badarg >= t1)
596
597 def test_str(self):
598 td = timedelta
599 eq = self.assertEqual
600
601 eq(str(td(1)), "1 day, 0:00:00")
602 eq(str(td(-1)), "-1 day, 0:00:00")
603 eq(str(td(2)), "2 days, 0:00:00")
604 eq(str(td(-2)), "-2 days, 0:00:00")
605
606 eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
607 eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
608 eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
609 "-210 days, 23:12:34")
610
611 eq(str(td(milliseconds=1)), "0:00:00.001000")
612 eq(str(td(microseconds=3)), "0:00:00.000003")
613
614 eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
615 microseconds=999999)),
616 "999999999 days, 23:59:59.999999")
617
618 def test_repr(self):
619 name = 'datetime.' + self.theclass.__name__
620 self.assertEqual(repr(self.theclass(1)),
621 "%s(1)" % name)
622 self.assertEqual(repr(self.theclass(10, 2)),
623 "%s(10, 2)" % name)
624 self.assertEqual(repr(self.theclass(-10, 2, 400000)),
625 "%s(-10, 2, 400000)" % name)
626
627 def test_roundtrip(self):
628 for td in (timedelta(days=999999999, hours=23, minutes=59,
629 seconds=59, microseconds=999999),
630 timedelta(days=-999999999),
631 timedelta(days=-999999999, seconds=1),
632 timedelta(days=1, seconds=2, microseconds=3)):
633
634 # Verify td -> string -> td identity.
635 s = repr(td)
636 self.assertTrue(s.startswith('datetime.'))
637 s = s[9:]
638 td2 = eval(s)
639 self.assertEqual(td, td2)
640
641 # Verify identity via reconstructing from pieces.
642 td2 = timedelta(td.days, td.seconds, td.microseconds)
643 self.assertEqual(td, td2)
644
645 def test_resolution_info(self):
646 self.assertIsInstance(timedelta.min, timedelta)
647 self.assertIsInstance(timedelta.max, timedelta)
648 self.assertIsInstance(timedelta.resolution, timedelta)
649 self.assertTrue(timedelta.max > timedelta.min)
650 self.assertEqual(timedelta.min, timedelta(-999999999))
651 self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
652 self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
653
654 def test_overflow(self):
655 tiny = timedelta.resolution
656
657 td = timedelta.min + tiny
658 td -= tiny # no problem
659 self.assertRaises(OverflowError, td.__sub__, tiny)
660 self.assertRaises(OverflowError, td.__add__, -tiny)
661
662 td = timedelta.max - tiny
663 td += tiny # no problem
664 self.assertRaises(OverflowError, td.__add__, tiny)
665 self.assertRaises(OverflowError, td.__sub__, -tiny)
666
667 self.assertRaises(OverflowError, lambda: -timedelta.max)
668
669 day = timedelta(1)
670 self.assertRaises(OverflowError, day.__mul__, 10**9)
671 self.assertRaises(OverflowError, day.__mul__, 1e9)
672 self.assertRaises(OverflowError, day.__truediv__, 1e-20)
673 self.assertRaises(OverflowError, day.__truediv__, 1e-10)
674 self.assertRaises(OverflowError, day.__truediv__, 9e-10)
675
Eric Smith3ab08ca2010-12-04 15:17:38 +0000676 @support.requires_IEEE_754
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000677 def _test_overflow_special(self):
678 day = timedelta(1)
679 self.assertRaises(OverflowError, day.__mul__, INF)
680 self.assertRaises(OverflowError, day.__mul__, -INF)
681
682 def test_microsecond_rounding(self):
683 td = timedelta
684 eq = self.assertEqual
685
686 # Single-field rounding.
687 eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
688 eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
Victor Stinner69cc4872015-09-08 23:58:54 +0200689 eq(td(milliseconds=0.5/1000), td(microseconds=0))
690 eq(td(milliseconds=-0.5/1000), td(microseconds=-0))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000691 eq(td(milliseconds=0.6/1000), td(microseconds=1))
692 eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
Victor Stinner69cc4872015-09-08 23:58:54 +0200693 eq(td(milliseconds=1.5/1000), td(microseconds=2))
694 eq(td(milliseconds=-1.5/1000), td(microseconds=-2))
695 eq(td(seconds=0.5/10**6), td(microseconds=0))
696 eq(td(seconds=-0.5/10**6), td(microseconds=-0))
697 eq(td(seconds=1/2**7), td(microseconds=7812))
698 eq(td(seconds=-1/2**7), td(microseconds=-7812))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000699
700 # Rounding due to contributions from more than one field.
701 us_per_hour = 3600e6
702 us_per_day = us_per_hour * 24
703 eq(td(days=.4/us_per_day), td(0))
704 eq(td(hours=.2/us_per_hour), td(0))
Victor Stinnercd5d7652015-09-09 01:09:21 +0200705 eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000706
707 eq(td(days=-.4/us_per_day), td(0))
708 eq(td(hours=-.2/us_per_hour), td(0))
709 eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
710
Victor Stinner69cc4872015-09-08 23:58:54 +0200711 # Test for a patch in Issue 8860
712 eq(td(microseconds=0.5), 0.5*td(microseconds=1.0))
713 eq(td(microseconds=0.5)//td.resolution, 0.5*td.resolution//td.resolution)
714
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000715 def test_massive_normalization(self):
716 td = timedelta(microseconds=-1)
717 self.assertEqual((td.days, td.seconds, td.microseconds),
718 (-1, 24*3600-1, 999999))
719
720 def test_bool(self):
721 self.assertTrue(timedelta(1))
722 self.assertTrue(timedelta(0, 1))
723 self.assertTrue(timedelta(0, 0, 1))
724 self.assertTrue(timedelta(microseconds=1))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200725 self.assertFalse(timedelta(0))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000726
727 def test_subclass_timedelta(self):
728
729 class T(timedelta):
730 @staticmethod
731 def from_td(td):
732 return T(td.days, td.seconds, td.microseconds)
733
734 def as_hours(self):
735 sum = (self.days * 24 +
736 self.seconds / 3600.0 +
737 self.microseconds / 3600e6)
738 return round(sum)
739
740 t1 = T(days=1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200741 self.assertIs(type(t1), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000742 self.assertEqual(t1.as_hours(), 24)
743
744 t2 = T(days=-1, seconds=-3600)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200745 self.assertIs(type(t2), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000746 self.assertEqual(t2.as_hours(), -25)
747
748 t3 = t1 + t2
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200749 self.assertIs(type(t3), timedelta)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000750 t4 = T.from_td(t3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200751 self.assertIs(type(t4), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000752 self.assertEqual(t3.days, t4.days)
753 self.assertEqual(t3.seconds, t4.seconds)
754 self.assertEqual(t3.microseconds, t4.microseconds)
755 self.assertEqual(str(t3), str(t4))
756 self.assertEqual(t4.as_hours(), -1)
757
758 def test_division(self):
759 t = timedelta(hours=1, minutes=24, seconds=19)
760 second = timedelta(seconds=1)
761 self.assertEqual(t / second, 5059.0)
762 self.assertEqual(t // second, 5059)
763
764 t = timedelta(minutes=2, seconds=30)
765 minute = timedelta(minutes=1)
766 self.assertEqual(t / minute, 2.5)
767 self.assertEqual(t // minute, 2)
768
769 zerotd = timedelta(0)
770 self.assertRaises(ZeroDivisionError, truediv, t, zerotd)
771 self.assertRaises(ZeroDivisionError, floordiv, t, zerotd)
772
773 # self.assertRaises(TypeError, truediv, t, 2)
774 # note: floor division of a timedelta by an integer *is*
775 # currently permitted.
776
777 def test_remainder(self):
778 t = timedelta(minutes=2, seconds=30)
779 minute = timedelta(minutes=1)
780 r = t % minute
781 self.assertEqual(r, timedelta(seconds=30))
782
783 t = timedelta(minutes=-2, seconds=30)
784 r = t % minute
785 self.assertEqual(r, timedelta(seconds=30))
786
787 zerotd = timedelta(0)
788 self.assertRaises(ZeroDivisionError, mod, t, zerotd)
789
790 self.assertRaises(TypeError, mod, t, 10)
791
792 def test_divmod(self):
793 t = timedelta(minutes=2, seconds=30)
794 minute = timedelta(minutes=1)
795 q, r = divmod(t, minute)
796 self.assertEqual(q, 2)
797 self.assertEqual(r, timedelta(seconds=30))
798
799 t = timedelta(minutes=-2, seconds=30)
800 q, r = divmod(t, minute)
801 self.assertEqual(q, -2)
802 self.assertEqual(r, timedelta(seconds=30))
803
804 zerotd = timedelta(0)
805 self.assertRaises(ZeroDivisionError, divmod, t, zerotd)
806
807 self.assertRaises(TypeError, divmod, t, 10)
808
809
810#############################################################################
811# date tests
812
813class TestDateOnly(unittest.TestCase):
814 # Tests here won't pass if also run on datetime objects, so don't
815 # subclass this to test datetimes too.
816
817 def test_delta_non_days_ignored(self):
818 dt = date(2000, 1, 2)
819 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
820 microseconds=5)
821 days = timedelta(delta.days)
822 self.assertEqual(days, timedelta(1))
823
824 dt2 = dt + delta
825 self.assertEqual(dt2, dt + days)
826
827 dt2 = delta + dt
828 self.assertEqual(dt2, dt + days)
829
830 dt2 = dt - delta
831 self.assertEqual(dt2, dt - days)
832
833 delta = -delta
834 days = timedelta(delta.days)
835 self.assertEqual(days, timedelta(-2))
836
837 dt2 = dt + delta
838 self.assertEqual(dt2, dt + days)
839
840 dt2 = delta + dt
841 self.assertEqual(dt2, dt + days)
842
843 dt2 = dt - delta
844 self.assertEqual(dt2, dt - days)
845
846class SubclassDate(date):
847 sub_var = 1
848
849class TestDate(HarmlessMixedComparison, unittest.TestCase):
850 # Tests here should pass for both dates and datetimes, except for a
851 # few tests that TestDateTime overrides.
852
853 theclass = date
854
855 def test_basic_attributes(self):
856 dt = self.theclass(2002, 3, 1)
857 self.assertEqual(dt.year, 2002)
858 self.assertEqual(dt.month, 3)
859 self.assertEqual(dt.day, 1)
860
861 def test_roundtrip(self):
862 for dt in (self.theclass(1, 2, 3),
863 self.theclass.today()):
864 # Verify dt -> string -> date identity.
865 s = repr(dt)
866 self.assertTrue(s.startswith('datetime.'))
867 s = s[9:]
868 dt2 = eval(s)
869 self.assertEqual(dt, dt2)
870
871 # Verify identity via reconstructing from pieces.
872 dt2 = self.theclass(dt.year, dt.month, dt.day)
873 self.assertEqual(dt, dt2)
874
875 def test_ordinal_conversions(self):
876 # Check some fixed values.
877 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
878 (1, 12, 31, 365),
879 (2, 1, 1, 366),
880 # first example from "Calendrical Calculations"
881 (1945, 11, 12, 710347)]:
882 d = self.theclass(y, m, d)
883 self.assertEqual(n, d.toordinal())
884 fromord = self.theclass.fromordinal(n)
885 self.assertEqual(d, fromord)
886 if hasattr(fromord, "hour"):
887 # if we're checking something fancier than a date, verify
888 # the extra fields have been zeroed out
889 self.assertEqual(fromord.hour, 0)
890 self.assertEqual(fromord.minute, 0)
891 self.assertEqual(fromord.second, 0)
892 self.assertEqual(fromord.microsecond, 0)
893
894 # Check first and last days of year spottily across the whole
895 # range of years supported.
896 for year in range(MINYEAR, MAXYEAR+1, 7):
897 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
898 d = self.theclass(year, 1, 1)
899 n = d.toordinal()
900 d2 = self.theclass.fromordinal(n)
901 self.assertEqual(d, d2)
902 # Verify that moving back a day gets to the end of year-1.
903 if year > 1:
904 d = self.theclass.fromordinal(n-1)
905 d2 = self.theclass(year-1, 12, 31)
906 self.assertEqual(d, d2)
907 self.assertEqual(d2.toordinal(), n-1)
908
909 # Test every day in a leap-year and a non-leap year.
910 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
911 for year, isleap in (2000, True), (2002, False):
912 n = self.theclass(year, 1, 1).toordinal()
913 for month, maxday in zip(range(1, 13), dim):
914 if month == 2 and isleap:
915 maxday += 1
916 for day in range(1, maxday+1):
917 d = self.theclass(year, month, day)
918 self.assertEqual(d.toordinal(), n)
919 self.assertEqual(d, self.theclass.fromordinal(n))
920 n += 1
921
922 def test_extreme_ordinals(self):
923 a = self.theclass.min
924 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
925 aord = a.toordinal()
926 b = a.fromordinal(aord)
927 self.assertEqual(a, b)
928
929 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
930
931 b = a + timedelta(days=1)
932 self.assertEqual(b.toordinal(), aord + 1)
933 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
934
935 a = self.theclass.max
936 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
937 aord = a.toordinal()
938 b = a.fromordinal(aord)
939 self.assertEqual(a, b)
940
941 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
942
943 b = a - timedelta(days=1)
944 self.assertEqual(b.toordinal(), aord - 1)
945 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
946
947 def test_bad_constructor_arguments(self):
948 # bad years
949 self.theclass(MINYEAR, 1, 1) # no exception
950 self.theclass(MAXYEAR, 1, 1) # no exception
951 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
952 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
953 # bad months
954 self.theclass(2000, 1, 1) # no exception
955 self.theclass(2000, 12, 1) # no exception
956 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
957 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
958 # bad days
959 self.theclass(2000, 2, 29) # no exception
960 self.theclass(2004, 2, 29) # no exception
961 self.theclass(2400, 2, 29) # no exception
962 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
963 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
964 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
965 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
966 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
967 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
968
969 def test_hash_equality(self):
970 d = self.theclass(2000, 12, 31)
971 # same thing
972 e = self.theclass(2000, 12, 31)
973 self.assertEqual(d, e)
974 self.assertEqual(hash(d), hash(e))
975
976 dic = {d: 1}
977 dic[e] = 2
978 self.assertEqual(len(dic), 1)
979 self.assertEqual(dic[d], 2)
980 self.assertEqual(dic[e], 2)
981
982 d = self.theclass(2001, 1, 1)
983 # same thing
984 e = self.theclass(2001, 1, 1)
985 self.assertEqual(d, e)
986 self.assertEqual(hash(d), hash(e))
987
988 dic = {d: 1}
989 dic[e] = 2
990 self.assertEqual(len(dic), 1)
991 self.assertEqual(dic[d], 2)
992 self.assertEqual(dic[e], 2)
993
994 def test_computations(self):
995 a = self.theclass(2002, 1, 31)
996 b = self.theclass(1956, 1, 31)
997 c = self.theclass(2001,2,1)
998
999 diff = a-b
1000 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1001 self.assertEqual(diff.seconds, 0)
1002 self.assertEqual(diff.microseconds, 0)
1003
1004 day = timedelta(1)
1005 week = timedelta(7)
1006 a = self.theclass(2002, 3, 2)
1007 self.assertEqual(a + day, self.theclass(2002, 3, 3))
1008 self.assertEqual(day + a, self.theclass(2002, 3, 3))
1009 self.assertEqual(a - day, self.theclass(2002, 3, 1))
1010 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
1011 self.assertEqual(a + week, self.theclass(2002, 3, 9))
1012 self.assertEqual(a - week, self.theclass(2002, 2, 23))
1013 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
1014 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
1015 self.assertEqual((a + week) - a, week)
1016 self.assertEqual((a + day) - a, day)
1017 self.assertEqual((a - week) - a, -week)
1018 self.assertEqual((a - day) - a, -day)
1019 self.assertEqual(a - (a + week), -week)
1020 self.assertEqual(a - (a + day), -day)
1021 self.assertEqual(a - (a - week), week)
1022 self.assertEqual(a - (a - day), day)
1023 self.assertEqual(c - (c - day), day)
1024
1025 # Add/sub ints or floats should be illegal
1026 for i in 1, 1.0:
1027 self.assertRaises(TypeError, lambda: a+i)
1028 self.assertRaises(TypeError, lambda: a-i)
1029 self.assertRaises(TypeError, lambda: i+a)
1030 self.assertRaises(TypeError, lambda: i-a)
1031
1032 # delta - date is senseless.
1033 self.assertRaises(TypeError, lambda: day - a)
1034 # mixing date and (delta or date) via * or // is senseless
1035 self.assertRaises(TypeError, lambda: day * a)
1036 self.assertRaises(TypeError, lambda: a * day)
1037 self.assertRaises(TypeError, lambda: day // a)
1038 self.assertRaises(TypeError, lambda: a // day)
1039 self.assertRaises(TypeError, lambda: a * a)
1040 self.assertRaises(TypeError, lambda: a // a)
1041 # date + date is senseless
1042 self.assertRaises(TypeError, lambda: a + a)
1043
1044 def test_overflow(self):
1045 tiny = self.theclass.resolution
1046
1047 for delta in [tiny, timedelta(1), timedelta(2)]:
1048 dt = self.theclass.min + delta
1049 dt -= delta # no problem
1050 self.assertRaises(OverflowError, dt.__sub__, delta)
1051 self.assertRaises(OverflowError, dt.__add__, -delta)
1052
1053 dt = self.theclass.max - delta
1054 dt += delta # no problem
1055 self.assertRaises(OverflowError, dt.__add__, delta)
1056 self.assertRaises(OverflowError, dt.__sub__, -delta)
1057
1058 def test_fromtimestamp(self):
1059 import time
1060
1061 # Try an arbitrary fixed value.
1062 year, month, day = 1999, 9, 19
1063 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
1064 d = self.theclass.fromtimestamp(ts)
1065 self.assertEqual(d.year, year)
1066 self.assertEqual(d.month, month)
1067 self.assertEqual(d.day, day)
1068
1069 def test_insane_fromtimestamp(self):
1070 # It's possible that some platform maps time_t to double,
1071 # and that this test will fail there. This test should
1072 # exempt such platforms (provided they return reasonable
1073 # results!).
1074 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001075 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001076 insane)
1077
1078 def test_today(self):
1079 import time
1080
1081 # We claim that today() is like fromtimestamp(time.time()), so
1082 # prove it.
1083 for dummy in range(3):
1084 today = self.theclass.today()
1085 ts = time.time()
1086 todayagain = self.theclass.fromtimestamp(ts)
1087 if today == todayagain:
1088 break
1089 # There are several legit reasons that could fail:
1090 # 1. It recently became midnight, between the today() and the
1091 # time() calls.
1092 # 2. The platform time() has such fine resolution that we'll
1093 # never get the same value twice.
1094 # 3. The platform time() has poor resolution, and we just
1095 # happened to call today() right before a resolution quantum
1096 # boundary.
1097 # 4. The system clock got fiddled between calls.
1098 # In any case, wait a little while and try again.
1099 time.sleep(0.1)
1100
1101 # It worked or it didn't. If it didn't, assume it's reason #2, and
1102 # let the test pass if they're within half a second of each other.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001103 if today != todayagain:
1104 self.assertAlmostEqual(todayagain, today,
1105 delta=timedelta(seconds=0.5))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001106
1107 def test_weekday(self):
1108 for i in range(7):
1109 # March 4, 2002 is a Monday
1110 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
1111 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
1112 # January 2, 1956 is a Monday
1113 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
1114 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
1115
1116 def test_isocalendar(self):
1117 # Check examples from
1118 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1119 for i in range(7):
1120 d = self.theclass(2003, 12, 22+i)
1121 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
1122 d = self.theclass(2003, 12, 29) + timedelta(i)
1123 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
1124 d = self.theclass(2004, 1, 5+i)
1125 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
1126 d = self.theclass(2009, 12, 21+i)
1127 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
1128 d = self.theclass(2009, 12, 28) + timedelta(i)
1129 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
1130 d = self.theclass(2010, 1, 4+i)
1131 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
1132
1133 def test_iso_long_years(self):
1134 # Calculate long ISO years and compare to table from
1135 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1136 ISO_LONG_YEARS_TABLE = """
1137 4 32 60 88
1138 9 37 65 93
1139 15 43 71 99
1140 20 48 76
1141 26 54 82
1142
1143 105 133 161 189
1144 111 139 167 195
1145 116 144 172
1146 122 150 178
1147 128 156 184
1148
1149 201 229 257 285
1150 207 235 263 291
1151 212 240 268 296
1152 218 246 274
1153 224 252 280
1154
1155 303 331 359 387
1156 308 336 364 392
1157 314 342 370 398
1158 320 348 376
1159 325 353 381
1160 """
1161 iso_long_years = sorted(map(int, ISO_LONG_YEARS_TABLE.split()))
1162 L = []
1163 for i in range(400):
1164 d = self.theclass(2000+i, 12, 31)
1165 d1 = self.theclass(1600+i, 12, 31)
1166 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
1167 if d.isocalendar()[1] == 53:
1168 L.append(i)
1169 self.assertEqual(L, iso_long_years)
1170
1171 def test_isoformat(self):
1172 t = self.theclass(2, 3, 2)
1173 self.assertEqual(t.isoformat(), "0002-03-02")
1174
1175 def test_ctime(self):
1176 t = self.theclass(2002, 3, 2)
1177 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
1178
1179 def test_strftime(self):
1180 t = self.theclass(2005, 3, 2)
1181 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
1182 self.assertEqual(t.strftime(""), "") # SF bug #761337
1183 self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784
1184
1185 self.assertRaises(TypeError, t.strftime) # needs an arg
1186 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
1187 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
1188
1189 # test that unicode input is allowed (issue 2782)
1190 self.assertEqual(t.strftime("%m"), "03")
1191
1192 # A naive object replaces %z and %Z w/ empty strings.
1193 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1194
1195 #make sure that invalid format specifiers are handled correctly
1196 #self.assertRaises(ValueError, t.strftime, "%e")
1197 #self.assertRaises(ValueError, t.strftime, "%")
1198 #self.assertRaises(ValueError, t.strftime, "%#")
1199
1200 #oh well, some systems just ignore those invalid ones.
1201 #at least, excercise them to make sure that no crashes
1202 #are generated
1203 for f in ["%e", "%", "%#"]:
1204 try:
1205 t.strftime(f)
1206 except ValueError:
1207 pass
1208
1209 #check that this standard extension works
1210 t.strftime("%f")
1211
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001212 def test_format(self):
1213 dt = self.theclass(2007, 9, 10)
1214 self.assertEqual(dt.__format__(''), str(dt))
1215
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001216 with self.assertRaisesRegex(TypeError, '^must be str, not int$'):
1217 dt.__format__(123)
1218
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001219 # check that a derived class's __str__() gets called
1220 class A(self.theclass):
1221 def __str__(self):
1222 return 'A'
1223 a = A(2007, 9, 10)
1224 self.assertEqual(a.__format__(''), 'A')
1225
1226 # check that a derived class's strftime gets called
1227 class B(self.theclass):
1228 def strftime(self, format_spec):
1229 return 'B'
1230 b = B(2007, 9, 10)
1231 self.assertEqual(b.__format__(''), str(dt))
1232
1233 for fmt in ["m:%m d:%d y:%y",
1234 "m:%m d:%d y:%y H:%H M:%M S:%S",
1235 "%z %Z",
1236 ]:
1237 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1238 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1239 self.assertEqual(b.__format__(fmt), 'B')
1240
1241 def test_resolution_info(self):
1242 # XXX: Should min and max respect subclassing?
1243 if issubclass(self.theclass, datetime):
1244 expected_class = datetime
1245 else:
1246 expected_class = date
1247 self.assertIsInstance(self.theclass.min, expected_class)
1248 self.assertIsInstance(self.theclass.max, expected_class)
1249 self.assertIsInstance(self.theclass.resolution, timedelta)
1250 self.assertTrue(self.theclass.max > self.theclass.min)
1251
1252 def test_extreme_timedelta(self):
1253 big = self.theclass.max - self.theclass.min
1254 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
1255 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
1256 # n == 315537897599999999 ~= 2**58.13
1257 justasbig = timedelta(0, 0, n)
1258 self.assertEqual(big, justasbig)
1259 self.assertEqual(self.theclass.min + big, self.theclass.max)
1260 self.assertEqual(self.theclass.max - big, self.theclass.min)
1261
1262 def test_timetuple(self):
1263 for i in range(7):
1264 # January 2, 1956 is a Monday (0)
1265 d = self.theclass(1956, 1, 2+i)
1266 t = d.timetuple()
1267 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
1268 # February 1, 1956 is a Wednesday (2)
1269 d = self.theclass(1956, 2, 1+i)
1270 t = d.timetuple()
1271 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
1272 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
1273 # of the year.
1274 d = self.theclass(1956, 3, 1+i)
1275 t = d.timetuple()
1276 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
1277 self.assertEqual(t.tm_year, 1956)
1278 self.assertEqual(t.tm_mon, 3)
1279 self.assertEqual(t.tm_mday, 1+i)
1280 self.assertEqual(t.tm_hour, 0)
1281 self.assertEqual(t.tm_min, 0)
1282 self.assertEqual(t.tm_sec, 0)
1283 self.assertEqual(t.tm_wday, (3+i)%7)
1284 self.assertEqual(t.tm_yday, 61+i)
1285 self.assertEqual(t.tm_isdst, -1)
1286
1287 def test_pickling(self):
1288 args = 6, 7, 23
1289 orig = self.theclass(*args)
1290 for pickler, unpickler, proto in pickle_choices:
1291 green = pickler.dumps(orig, proto)
1292 derived = unpickler.loads(green)
1293 self.assertEqual(orig, derived)
1294
1295 def test_compare(self):
1296 t1 = self.theclass(2, 3, 4)
1297 t2 = self.theclass(2, 3, 4)
1298 self.assertEqual(t1, t2)
1299 self.assertTrue(t1 <= t2)
1300 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001301 self.assertFalse(t1 != t2)
1302 self.assertFalse(t1 < t2)
1303 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001304
1305 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
1306 t2 = self.theclass(*args) # this is larger than t1
1307 self.assertTrue(t1 < t2)
1308 self.assertTrue(t2 > t1)
1309 self.assertTrue(t1 <= t2)
1310 self.assertTrue(t2 >= t1)
1311 self.assertTrue(t1 != t2)
1312 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001313 self.assertFalse(t1 == t2)
1314 self.assertFalse(t2 == t1)
1315 self.assertFalse(t1 > t2)
1316 self.assertFalse(t2 < t1)
1317 self.assertFalse(t1 >= t2)
1318 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001319
1320 for badarg in OTHERSTUFF:
1321 self.assertEqual(t1 == badarg, False)
1322 self.assertEqual(t1 != badarg, True)
1323 self.assertEqual(badarg == t1, False)
1324 self.assertEqual(badarg != t1, True)
1325
1326 self.assertRaises(TypeError, lambda: t1 < badarg)
1327 self.assertRaises(TypeError, lambda: t1 > badarg)
1328 self.assertRaises(TypeError, lambda: t1 >= badarg)
1329 self.assertRaises(TypeError, lambda: badarg <= t1)
1330 self.assertRaises(TypeError, lambda: badarg < t1)
1331 self.assertRaises(TypeError, lambda: badarg > t1)
1332 self.assertRaises(TypeError, lambda: badarg >= t1)
1333
1334 def test_mixed_compare(self):
1335 our = self.theclass(2000, 4, 5)
1336
1337 # Our class can be compared for equality to other classes
1338 self.assertEqual(our == 1, False)
1339 self.assertEqual(1 == our, False)
1340 self.assertEqual(our != 1, True)
1341 self.assertEqual(1 != our, True)
1342
1343 # But the ordering is undefined
1344 self.assertRaises(TypeError, lambda: our < 1)
1345 self.assertRaises(TypeError, lambda: 1 < our)
1346
1347 # Repeat those tests with a different class
1348
1349 class SomeClass:
1350 pass
1351
1352 their = SomeClass()
1353 self.assertEqual(our == their, False)
1354 self.assertEqual(their == our, False)
1355 self.assertEqual(our != their, True)
1356 self.assertEqual(their != our, True)
1357 self.assertRaises(TypeError, lambda: our < their)
1358 self.assertRaises(TypeError, lambda: their < our)
1359
1360 # However, if the other class explicitly defines ordering
1361 # relative to our class, it is allowed to do so
1362
1363 class LargerThanAnything:
1364 def __lt__(self, other):
1365 return False
1366 def __le__(self, other):
1367 return isinstance(other, LargerThanAnything)
1368 def __eq__(self, other):
1369 return isinstance(other, LargerThanAnything)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001370 def __gt__(self, other):
1371 return not isinstance(other, LargerThanAnything)
1372 def __ge__(self, other):
1373 return True
1374
1375 their = LargerThanAnything()
1376 self.assertEqual(our == their, False)
1377 self.assertEqual(their == our, False)
1378 self.assertEqual(our != their, True)
1379 self.assertEqual(their != our, True)
1380 self.assertEqual(our < their, True)
1381 self.assertEqual(their < our, False)
1382
1383 def test_bool(self):
1384 # All dates are considered true.
1385 self.assertTrue(self.theclass.min)
1386 self.assertTrue(self.theclass.max)
1387
Alexander Belopolsky89da3492011-05-02 13:14:24 -04001388 def test_strftime_y2k(self):
1389 for y in (1, 49, 70, 99, 100, 999, 1000, 1970):
Florent Xicluna49ce0682011-11-01 12:56:14 +01001390 d = self.theclass(y, 1, 1)
1391 # Issue 13305: For years < 1000, the value is not always
1392 # padded to 4 digits across platforms. The C standard
1393 # assumes year >= 1900, so it does not specify the number
1394 # of digits.
1395 if d.strftime("%Y") != '%04d' % y:
1396 # Year 42 returns '42', not padded
1397 self.assertEqual(d.strftime("%Y"), '%d' % y)
1398 # '0042' is obtained anyway
1399 self.assertEqual(d.strftime("%4Y"), '%04d' % y)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001400
1401 def test_replace(self):
1402 cls = self.theclass
1403 args = [1, 2, 3]
1404 base = cls(*args)
1405 self.assertEqual(base, base.replace())
1406
1407 i = 0
1408 for name, newval in (("year", 2),
1409 ("month", 3),
1410 ("day", 4)):
1411 newargs = args[:]
1412 newargs[i] = newval
1413 expected = cls(*newargs)
1414 got = base.replace(**{name: newval})
1415 self.assertEqual(expected, got)
1416 i += 1
1417
1418 # Out of bounds.
1419 base = cls(2000, 2, 29)
1420 self.assertRaises(ValueError, base.replace, year=2001)
1421
1422 def test_subclass_date(self):
1423
1424 class C(self.theclass):
1425 theAnswer = 42
1426
1427 def __new__(cls, *args, **kws):
1428 temp = kws.copy()
1429 extra = temp.pop('extra')
1430 result = self.theclass.__new__(cls, *args, **temp)
1431 result.extra = extra
1432 return result
1433
1434 def newmeth(self, start):
1435 return start + self.year + self.month
1436
1437 args = 2003, 4, 14
1438
1439 dt1 = self.theclass(*args)
1440 dt2 = C(*args, **{'extra': 7})
1441
1442 self.assertEqual(dt2.__class__, C)
1443 self.assertEqual(dt2.theAnswer, 42)
1444 self.assertEqual(dt2.extra, 7)
1445 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1446 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1447
1448 def test_pickling_subclass_date(self):
1449
1450 args = 6, 7, 23
1451 orig = SubclassDate(*args)
1452 for pickler, unpickler, proto in pickle_choices:
1453 green = pickler.dumps(orig, proto)
1454 derived = unpickler.loads(green)
1455 self.assertEqual(orig, derived)
1456
1457 def test_backdoor_resistance(self):
1458 # For fast unpickling, the constructor accepts a pickle byte string.
1459 # This is a low-overhead backdoor. A user can (by intent or
1460 # mistake) pass a string directly, which (if it's the right length)
1461 # will get treated like a pickle, and bypass the normal sanity
1462 # checks in the constructor. This can create insane objects.
1463 # The constructor doesn't want to burn the time to validate all
1464 # fields, but does check the month field. This stops, e.g.,
1465 # datetime.datetime('1995-03-25') from yielding an insane object.
1466 base = b'1995-03-25'
1467 if not issubclass(self.theclass, datetime):
1468 base = base[:4]
1469 for month_byte in b'9', b'\0', b'\r', b'\xff':
1470 self.assertRaises(TypeError, self.theclass,
1471 base[:2] + month_byte + base[3:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001472 if issubclass(self.theclass, datetime):
1473 # Good bytes, but bad tzinfo:
1474 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
1475 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001476
1477 for ord_byte in range(1, 13):
1478 # This shouldn't blow up because of the month byte alone. If
1479 # the implementation changes to do more-careful checking, it may
1480 # blow up because other fields are insane.
1481 self.theclass(base[:2] + bytes([ord_byte]) + base[3:])
1482
1483#############################################################################
1484# datetime tests
1485
1486class SubclassDatetime(datetime):
1487 sub_var = 1
1488
1489class TestDateTime(TestDate):
1490
1491 theclass = datetime
1492
1493 def test_basic_attributes(self):
1494 dt = self.theclass(2002, 3, 1, 12, 0)
1495 self.assertEqual(dt.year, 2002)
1496 self.assertEqual(dt.month, 3)
1497 self.assertEqual(dt.day, 1)
1498 self.assertEqual(dt.hour, 12)
1499 self.assertEqual(dt.minute, 0)
1500 self.assertEqual(dt.second, 0)
1501 self.assertEqual(dt.microsecond, 0)
1502
1503 def test_basic_attributes_nonzero(self):
1504 # Make sure all attributes are non-zero so bugs in
1505 # bit-shifting access show up.
1506 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1507 self.assertEqual(dt.year, 2002)
1508 self.assertEqual(dt.month, 3)
1509 self.assertEqual(dt.day, 1)
1510 self.assertEqual(dt.hour, 12)
1511 self.assertEqual(dt.minute, 59)
1512 self.assertEqual(dt.second, 59)
1513 self.assertEqual(dt.microsecond, 8000)
1514
1515 def test_roundtrip(self):
1516 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1517 self.theclass.now()):
1518 # Verify dt -> string -> datetime identity.
1519 s = repr(dt)
1520 self.assertTrue(s.startswith('datetime.'))
1521 s = s[9:]
1522 dt2 = eval(s)
1523 self.assertEqual(dt, dt2)
1524
1525 # Verify identity via reconstructing from pieces.
1526 dt2 = self.theclass(dt.year, dt.month, dt.day,
1527 dt.hour, dt.minute, dt.second,
1528 dt.microsecond)
1529 self.assertEqual(dt, dt2)
1530
1531 def test_isoformat(self):
1532 t = self.theclass(2, 3, 2, 4, 5, 1, 123)
1533 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123")
1534 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
1535 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
1536 self.assertEqual(t.isoformat('\x00'), "0002-03-02\x0004:05:01.000123")
1537 # str is ISO format with the separator forced to a blank.
1538 self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
1539
1540 t = self.theclass(2, 3, 2)
1541 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1542 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1543 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1544 # str is ISO format with the separator forced to a blank.
1545 self.assertEqual(str(t), "0002-03-02 00:00:00")
1546
1547 def test_format(self):
1548 dt = self.theclass(2007, 9, 10, 4, 5, 1, 123)
1549 self.assertEqual(dt.__format__(''), str(dt))
1550
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001551 with self.assertRaisesRegex(TypeError, '^must be str, not int$'):
1552 dt.__format__(123)
1553
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001554 # check that a derived class's __str__() gets called
1555 class A(self.theclass):
1556 def __str__(self):
1557 return 'A'
1558 a = A(2007, 9, 10, 4, 5, 1, 123)
1559 self.assertEqual(a.__format__(''), 'A')
1560
1561 # check that a derived class's strftime gets called
1562 class B(self.theclass):
1563 def strftime(self, format_spec):
1564 return 'B'
1565 b = B(2007, 9, 10, 4, 5, 1, 123)
1566 self.assertEqual(b.__format__(''), str(dt))
1567
1568 for fmt in ["m:%m d:%d y:%y",
1569 "m:%m d:%d y:%y H:%H M:%M S:%S",
1570 "%z %Z",
1571 ]:
1572 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1573 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1574 self.assertEqual(b.__format__(fmt), 'B')
1575
1576 def test_more_ctime(self):
1577 # Test fields that TestDate doesn't touch.
1578 import time
1579
1580 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1581 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1582 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1583 # out. The difference is that t.ctime() produces " 2" for the day,
1584 # but platform ctime() produces "02" for the day. According to
1585 # C99, t.ctime() is correct here.
1586 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1587
1588 # So test a case where that difference doesn't matter.
1589 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1590 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1591
1592 def test_tz_independent_comparing(self):
1593 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1594 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1595 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1596 self.assertEqual(dt1, dt3)
1597 self.assertTrue(dt2 > dt3)
1598
1599 # Make sure comparison doesn't forget microseconds, and isn't done
1600 # via comparing a float timestamp (an IEEE double doesn't have enough
1601 # precision to span microsecond resolution across years 1 thru 9999,
1602 # so comparing via timestamp necessarily calls some distinct values
1603 # equal).
1604 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1605 us = timedelta(microseconds=1)
1606 dt2 = dt1 + us
1607 self.assertEqual(dt2 - dt1, us)
1608 self.assertTrue(dt1 < dt2)
1609
1610 def test_strftime_with_bad_tzname_replace(self):
1611 # verify ok if tzinfo.tzname().replace() returns a non-string
1612 class MyTzInfo(FixedOffset):
1613 def tzname(self, dt):
1614 class MyStr(str):
1615 def replace(self, *args):
1616 return None
1617 return MyStr('name')
1618 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
1619 self.assertRaises(TypeError, t.strftime, '%Z')
1620
1621 def test_bad_constructor_arguments(self):
1622 # bad years
1623 self.theclass(MINYEAR, 1, 1) # no exception
1624 self.theclass(MAXYEAR, 1, 1) # no exception
1625 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1626 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1627 # bad months
1628 self.theclass(2000, 1, 1) # no exception
1629 self.theclass(2000, 12, 1) # no exception
1630 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1631 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1632 # bad days
1633 self.theclass(2000, 2, 29) # no exception
1634 self.theclass(2004, 2, 29) # no exception
1635 self.theclass(2400, 2, 29) # no exception
1636 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1637 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1638 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1639 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1640 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1641 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1642 # bad hours
1643 self.theclass(2000, 1, 31, 0) # no exception
1644 self.theclass(2000, 1, 31, 23) # no exception
1645 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1646 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1647 # bad minutes
1648 self.theclass(2000, 1, 31, 23, 0) # no exception
1649 self.theclass(2000, 1, 31, 23, 59) # no exception
1650 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1651 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1652 # bad seconds
1653 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1654 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1655 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1656 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1657 # bad microseconds
1658 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1659 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1660 self.assertRaises(ValueError, self.theclass,
1661 2000, 1, 31, 23, 59, 59, -1)
1662 self.assertRaises(ValueError, self.theclass,
1663 2000, 1, 31, 23, 59, 59,
1664 1000000)
1665
1666 def test_hash_equality(self):
1667 d = self.theclass(2000, 12, 31, 23, 30, 17)
1668 e = self.theclass(2000, 12, 31, 23, 30, 17)
1669 self.assertEqual(d, e)
1670 self.assertEqual(hash(d), hash(e))
1671
1672 dic = {d: 1}
1673 dic[e] = 2
1674 self.assertEqual(len(dic), 1)
1675 self.assertEqual(dic[d], 2)
1676 self.assertEqual(dic[e], 2)
1677
1678 d = self.theclass(2001, 1, 1, 0, 5, 17)
1679 e = self.theclass(2001, 1, 1, 0, 5, 17)
1680 self.assertEqual(d, e)
1681 self.assertEqual(hash(d), hash(e))
1682
1683 dic = {d: 1}
1684 dic[e] = 2
1685 self.assertEqual(len(dic), 1)
1686 self.assertEqual(dic[d], 2)
1687 self.assertEqual(dic[e], 2)
1688
1689 def test_computations(self):
1690 a = self.theclass(2002, 1, 31)
1691 b = self.theclass(1956, 1, 31)
1692 diff = a-b
1693 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1694 self.assertEqual(diff.seconds, 0)
1695 self.assertEqual(diff.microseconds, 0)
1696 a = self.theclass(2002, 3, 2, 17, 6)
1697 millisec = timedelta(0, 0, 1000)
1698 hour = timedelta(0, 3600)
1699 day = timedelta(1)
1700 week = timedelta(7)
1701 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1702 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1703 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1704 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1705 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1706 self.assertEqual(a - hour, a + -hour)
1707 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1708 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1709 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1710 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1711 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1712 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1713 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1714 self.assertEqual((a + week) - a, week)
1715 self.assertEqual((a + day) - a, day)
1716 self.assertEqual((a + hour) - a, hour)
1717 self.assertEqual((a + millisec) - a, millisec)
1718 self.assertEqual((a - week) - a, -week)
1719 self.assertEqual((a - day) - a, -day)
1720 self.assertEqual((a - hour) - a, -hour)
1721 self.assertEqual((a - millisec) - a, -millisec)
1722 self.assertEqual(a - (a + week), -week)
1723 self.assertEqual(a - (a + day), -day)
1724 self.assertEqual(a - (a + hour), -hour)
1725 self.assertEqual(a - (a + millisec), -millisec)
1726 self.assertEqual(a - (a - week), week)
1727 self.assertEqual(a - (a - day), day)
1728 self.assertEqual(a - (a - hour), hour)
1729 self.assertEqual(a - (a - millisec), millisec)
1730 self.assertEqual(a + (week + day + hour + millisec),
1731 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1732 self.assertEqual(a + (week + day + hour + millisec),
1733 (((a + week) + day) + hour) + millisec)
1734 self.assertEqual(a - (week + day + hour + millisec),
1735 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1736 self.assertEqual(a - (week + day + hour + millisec),
1737 (((a - week) - day) - hour) - millisec)
1738 # Add/sub ints or floats should be illegal
1739 for i in 1, 1.0:
1740 self.assertRaises(TypeError, lambda: a+i)
1741 self.assertRaises(TypeError, lambda: a-i)
1742 self.assertRaises(TypeError, lambda: i+a)
1743 self.assertRaises(TypeError, lambda: i-a)
1744
1745 # delta - datetime is senseless.
1746 self.assertRaises(TypeError, lambda: day - a)
1747 # mixing datetime and (delta or datetime) via * or // is senseless
1748 self.assertRaises(TypeError, lambda: day * a)
1749 self.assertRaises(TypeError, lambda: a * day)
1750 self.assertRaises(TypeError, lambda: day // a)
1751 self.assertRaises(TypeError, lambda: a // day)
1752 self.assertRaises(TypeError, lambda: a * a)
1753 self.assertRaises(TypeError, lambda: a // a)
1754 # datetime + datetime is senseless
1755 self.assertRaises(TypeError, lambda: a + a)
1756
1757 def test_pickling(self):
1758 args = 6, 7, 23, 20, 59, 1, 64**2
1759 orig = self.theclass(*args)
1760 for pickler, unpickler, proto in pickle_choices:
1761 green = pickler.dumps(orig, proto)
1762 derived = unpickler.loads(green)
1763 self.assertEqual(orig, derived)
1764
1765 def test_more_pickling(self):
1766 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
Serhiy Storchakabad12572014-12-15 14:03:42 +02001767 for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1768 s = pickle.dumps(a, proto)
1769 b = pickle.loads(s)
1770 self.assertEqual(b.year, 2003)
1771 self.assertEqual(b.month, 2)
1772 self.assertEqual(b.day, 7)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001773
1774 def test_pickling_subclass_datetime(self):
1775 args = 6, 7, 23, 20, 59, 1, 64**2
1776 orig = SubclassDatetime(*args)
1777 for pickler, unpickler, proto in pickle_choices:
1778 green = pickler.dumps(orig, proto)
1779 derived = unpickler.loads(green)
1780 self.assertEqual(orig, derived)
1781
1782 def test_more_compare(self):
1783 # The test_compare() inherited from TestDate covers the error cases.
1784 # We just want to test lexicographic ordering on the members datetime
1785 # has that date lacks.
1786 args = [2000, 11, 29, 20, 58, 16, 999998]
1787 t1 = self.theclass(*args)
1788 t2 = self.theclass(*args)
1789 self.assertEqual(t1, t2)
1790 self.assertTrue(t1 <= t2)
1791 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001792 self.assertFalse(t1 != t2)
1793 self.assertFalse(t1 < t2)
1794 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001795
1796 for i in range(len(args)):
1797 newargs = args[:]
1798 newargs[i] = args[i] + 1
1799 t2 = self.theclass(*newargs) # this is larger than t1
1800 self.assertTrue(t1 < t2)
1801 self.assertTrue(t2 > t1)
1802 self.assertTrue(t1 <= t2)
1803 self.assertTrue(t2 >= t1)
1804 self.assertTrue(t1 != t2)
1805 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001806 self.assertFalse(t1 == t2)
1807 self.assertFalse(t2 == t1)
1808 self.assertFalse(t1 > t2)
1809 self.assertFalse(t2 < t1)
1810 self.assertFalse(t1 >= t2)
1811 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001812
1813
1814 # A helper for timestamp constructor tests.
1815 def verify_field_equality(self, expected, got):
1816 self.assertEqual(expected.tm_year, got.year)
1817 self.assertEqual(expected.tm_mon, got.month)
1818 self.assertEqual(expected.tm_mday, got.day)
1819 self.assertEqual(expected.tm_hour, got.hour)
1820 self.assertEqual(expected.tm_min, got.minute)
1821 self.assertEqual(expected.tm_sec, got.second)
1822
1823 def test_fromtimestamp(self):
1824 import time
1825
1826 ts = time.time()
1827 expected = time.localtime(ts)
1828 got = self.theclass.fromtimestamp(ts)
1829 self.verify_field_equality(expected, got)
1830
1831 def test_utcfromtimestamp(self):
1832 import time
1833
1834 ts = time.time()
1835 expected = time.gmtime(ts)
1836 got = self.theclass.utcfromtimestamp(ts)
1837 self.verify_field_equality(expected, got)
1838
Alexander Belopolskya4415142012-06-08 12:33:09 -04001839 # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in
1840 # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0).
1841 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
1842 def test_timestamp_naive(self):
1843 t = self.theclass(1970, 1, 1)
1844 self.assertEqual(t.timestamp(), 18000.0)
1845 t = self.theclass(1970, 1, 1, 1, 2, 3, 4)
1846 self.assertEqual(t.timestamp(),
1847 18000.0 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky0c687e52012-06-08 12:58:31 -04001848 # Missing hour may produce platform-dependent result
Alexander Belopolskya4415142012-06-08 12:33:09 -04001849 t = self.theclass(2012, 3, 11, 2, 30)
Alexander Belopolsky0c687e52012-06-08 12:58:31 -04001850 self.assertIn(self.theclass.fromtimestamp(t.timestamp()),
Alexander Belopolskyf6f56182012-06-08 13:00:27 -04001851 [t - timedelta(hours=1), t + timedelta(hours=1)])
Alexander Belopolskya4415142012-06-08 12:33:09 -04001852 # Ambiguous hour defaults to DST
1853 t = self.theclass(2012, 11, 4, 1, 30)
1854 self.assertEqual(self.theclass.fromtimestamp(t.timestamp()), t)
1855
1856 # Timestamp may raise an overflow error on some platforms
1857 for t in [self.theclass(1,1,1), self.theclass(9999,12,12)]:
1858 try:
1859 s = t.timestamp()
1860 except OverflowError:
1861 pass
1862 else:
1863 self.assertEqual(self.theclass.fromtimestamp(s), t)
1864
1865 def test_timestamp_aware(self):
1866 t = self.theclass(1970, 1, 1, tzinfo=timezone.utc)
1867 self.assertEqual(t.timestamp(), 0.0)
1868 t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone.utc)
1869 self.assertEqual(t.timestamp(),
1870 3600 + 2*60 + 3 + 4*1e-6)
1871 t = self.theclass(1970, 1, 1, 1, 2, 3, 4,
1872 tzinfo=timezone(timedelta(hours=-5), 'EST'))
1873 self.assertEqual(t.timestamp(),
1874 18000 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001875
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001876 def test_microsecond_rounding(self):
Victor Stinner5ebfe422015-09-18 14:52:15 +02001877 for fts in [self.theclass.fromtimestamp,
1878 self.theclass.utcfromtimestamp]:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001879 zero = fts(0)
1880 self.assertEqual(zero.second, 0)
1881 self.assertEqual(zero.microsecond, 0)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02001882 one = fts(1e-6)
Victor Stinner8050ca92012-03-14 00:17:05 +01001883 try:
1884 minus_one = fts(-1e-6)
1885 except OSError:
1886 # localtime(-1) and gmtime(-1) is not supported on Windows
1887 pass
1888 else:
1889 self.assertEqual(minus_one.second, 59)
1890 self.assertEqual(minus_one.microsecond, 999999)
Victor Stinner5d272cc2012-03-13 13:35:55 +01001891
Victor Stinner8050ca92012-03-14 00:17:05 +01001892 t = fts(-1e-8)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02001893 self.assertEqual(t, zero)
Victor Stinner8050ca92012-03-14 00:17:05 +01001894 t = fts(-9e-7)
1895 self.assertEqual(t, minus_one)
1896 t = fts(-1e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02001897 self.assertEqual(t, zero)
Victor Stinner8820a352015-09-05 10:50:20 +02001898 t = fts(-1/2**7)
1899 self.assertEqual(t.second, 59)
Victor Stinner7667f582015-09-09 01:02:23 +02001900 self.assertEqual(t.microsecond, 992188)
Victor Stinner5d272cc2012-03-13 13:35:55 +01001901
1902 t = fts(1e-7)
1903 self.assertEqual(t, zero)
1904 t = fts(9e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02001905 self.assertEqual(t, one)
Victor Stinner5d272cc2012-03-13 13:35:55 +01001906 t = fts(0.99999949)
1907 self.assertEqual(t.second, 0)
1908 self.assertEqual(t.microsecond, 999999)
1909 t = fts(0.9999999)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02001910 self.assertEqual(t.second, 1)
1911 self.assertEqual(t.microsecond, 0)
Victor Stinneradfefa52015-09-04 23:57:25 +02001912 t = fts(1/2**7)
1913 self.assertEqual(t.second, 0)
Victor Stinner7667f582015-09-09 01:02:23 +02001914 self.assertEqual(t.microsecond, 7812)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001915
1916 def test_insane_fromtimestamp(self):
1917 # It's possible that some platform maps time_t to double,
1918 # and that this test will fail there. This test should
1919 # exempt such platforms (provided they return reasonable
1920 # results!).
1921 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001922 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001923 insane)
1924
1925 def test_insane_utcfromtimestamp(self):
1926 # It's possible that some platform maps time_t to double,
1927 # and that this test will fail there. This test should
1928 # exempt such platforms (provided they return reasonable
1929 # results!).
1930 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001931 self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001932 insane)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001933
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001934 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
1935 def test_negative_float_fromtimestamp(self):
1936 # The result is tz-dependent; at least test that this doesn't
1937 # fail (like it did before bug 1646728 was fixed).
1938 self.theclass.fromtimestamp(-1.05)
1939
1940 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
1941 def test_negative_float_utcfromtimestamp(self):
1942 d = self.theclass.utcfromtimestamp(-1.05)
1943 self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
1944
1945 def test_utcnow(self):
1946 import time
1947
1948 # Call it a success if utcnow() and utcfromtimestamp() are within
1949 # a second of each other.
1950 tolerance = timedelta(seconds=1)
1951 for dummy in range(3):
1952 from_now = self.theclass.utcnow()
1953 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1954 if abs(from_timestamp - from_now) <= tolerance:
1955 break
1956 # Else try again a few times.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001957 self.assertLessEqual(abs(from_timestamp - from_now), tolerance)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001958
1959 def test_strptime(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001960 string = '2004-12-01 13:02:47.197'
1961 format = '%Y-%m-%d %H:%M:%S.%f'
1962 expected = _strptime._strptime_datetime(self.theclass, string, format)
1963 got = self.theclass.strptime(string, format)
1964 self.assertEqual(expected, got)
1965 self.assertIs(type(expected), self.theclass)
1966 self.assertIs(type(got), self.theclass)
1967
1968 strptime = self.theclass.strptime
1969 self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
1970 self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
1971 # Only local timezone and UTC are supported
1972 for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
1973 (-_time.timezone, _time.tzname[0])):
1974 if tzseconds < 0:
1975 sign = '-'
1976 seconds = -tzseconds
1977 else:
1978 sign ='+'
1979 seconds = tzseconds
1980 hours, minutes = divmod(seconds//60, 60)
1981 dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
1982 dt = strptime(dtstr, "%z %Z")
1983 self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
1984 self.assertEqual(dt.tzname(), tzname)
1985 # Can produce inconsistent datetime
1986 dtstr, fmt = "+1234 UTC", "%z %Z"
1987 dt = strptime(dtstr, fmt)
1988 self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
1989 self.assertEqual(dt.tzname(), 'UTC')
1990 # yet will roundtrip
1991 self.assertEqual(dt.strftime(fmt), dtstr)
1992
1993 # Produce naive datetime if no %z is provided
1994 self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
1995
1996 with self.assertRaises(ValueError): strptime("-2400", "%z")
1997 with self.assertRaises(ValueError): strptime("-000", "%z")
1998
1999 def test_more_timetuple(self):
2000 # This tests fields beyond those tested by the TestDate.test_timetuple.
2001 t = self.theclass(2004, 12, 31, 6, 22, 33)
2002 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
2003 self.assertEqual(t.timetuple(),
2004 (t.year, t.month, t.day,
2005 t.hour, t.minute, t.second,
2006 t.weekday(),
2007 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
2008 -1))
2009 tt = t.timetuple()
2010 self.assertEqual(tt.tm_year, t.year)
2011 self.assertEqual(tt.tm_mon, t.month)
2012 self.assertEqual(tt.tm_mday, t.day)
2013 self.assertEqual(tt.tm_hour, t.hour)
2014 self.assertEqual(tt.tm_min, t.minute)
2015 self.assertEqual(tt.tm_sec, t.second)
2016 self.assertEqual(tt.tm_wday, t.weekday())
2017 self.assertEqual(tt.tm_yday, t.toordinal() -
2018 date(t.year, 1, 1).toordinal() + 1)
2019 self.assertEqual(tt.tm_isdst, -1)
2020
2021 def test_more_strftime(self):
2022 # This tests fields beyond those tested by the TestDate.test_strftime.
2023 t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
2024 self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
2025 "12 31 04 000047 33 22 06 366")
2026
2027 def test_extract(self):
2028 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2029 self.assertEqual(dt.date(), date(2002, 3, 4))
2030 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2031
2032 def test_combine(self):
2033 d = date(2002, 3, 4)
2034 t = time(18, 45, 3, 1234)
2035 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2036 combine = self.theclass.combine
2037 dt = combine(d, t)
2038 self.assertEqual(dt, expected)
2039
2040 dt = combine(time=t, date=d)
2041 self.assertEqual(dt, expected)
2042
2043 self.assertEqual(d, dt.date())
2044 self.assertEqual(t, dt.time())
2045 self.assertEqual(dt, combine(dt.date(), dt.time()))
2046
2047 self.assertRaises(TypeError, combine) # need an arg
2048 self.assertRaises(TypeError, combine, d) # need two args
2049 self.assertRaises(TypeError, combine, t, d) # args reversed
2050 self.assertRaises(TypeError, combine, d, t, 1) # too many args
2051 self.assertRaises(TypeError, combine, "date", "time") # wrong types
2052 self.assertRaises(TypeError, combine, d, "time") # wrong type
2053 self.assertRaises(TypeError, combine, "date", t) # wrong type
2054
2055 def test_replace(self):
2056 cls = self.theclass
2057 args = [1, 2, 3, 4, 5, 6, 7]
2058 base = cls(*args)
2059 self.assertEqual(base, base.replace())
2060
2061 i = 0
2062 for name, newval in (("year", 2),
2063 ("month", 3),
2064 ("day", 4),
2065 ("hour", 5),
2066 ("minute", 6),
2067 ("second", 7),
2068 ("microsecond", 8)):
2069 newargs = args[:]
2070 newargs[i] = newval
2071 expected = cls(*newargs)
2072 got = base.replace(**{name: newval})
2073 self.assertEqual(expected, got)
2074 i += 1
2075
2076 # Out of bounds.
2077 base = cls(2000, 2, 29)
2078 self.assertRaises(ValueError, base.replace, year=2001)
2079
2080 def test_astimezone(self):
2081 # Pretty boring! The TZ test is more interesting here. astimezone()
2082 # simply can't be applied to a naive object.
2083 dt = self.theclass.now()
2084 f = FixedOffset(44, "")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04002085 self.assertRaises(ValueError, dt.astimezone) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002086 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
2087 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
2088 self.assertRaises(ValueError, dt.astimezone, f) # naive
2089 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
2090
2091 class Bogus(tzinfo):
2092 def utcoffset(self, dt): return None
2093 def dst(self, dt): return timedelta(0)
2094 bog = Bogus()
2095 self.assertRaises(ValueError, dt.astimezone, bog) # naive
2096 self.assertRaises(ValueError,
2097 dt.replace(tzinfo=bog).astimezone, f)
2098
2099 class AlsoBogus(tzinfo):
2100 def utcoffset(self, dt): return timedelta(0)
2101 def dst(self, dt): return None
2102 alsobog = AlsoBogus()
2103 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
2104
2105 def test_subclass_datetime(self):
2106
2107 class C(self.theclass):
2108 theAnswer = 42
2109
2110 def __new__(cls, *args, **kws):
2111 temp = kws.copy()
2112 extra = temp.pop('extra')
2113 result = self.theclass.__new__(cls, *args, **temp)
2114 result.extra = extra
2115 return result
2116
2117 def newmeth(self, start):
2118 return start + self.year + self.month + self.second
2119
2120 args = 2003, 4, 14, 12, 13, 41
2121
2122 dt1 = self.theclass(*args)
2123 dt2 = C(*args, **{'extra': 7})
2124
2125 self.assertEqual(dt2.__class__, C)
2126 self.assertEqual(dt2.theAnswer, 42)
2127 self.assertEqual(dt2.extra, 7)
2128 self.assertEqual(dt1.toordinal(), dt2.toordinal())
2129 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
2130 dt1.second - 7)
2131
2132class TestSubclassDateTime(TestDateTime):
2133 theclass = SubclassDatetime
2134 # Override tests not designed for subclass
Zachary Ware9fe6d862013-12-08 00:20:35 -06002135 @unittest.skip('not appropriate for subclasses')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002136 def test_roundtrip(self):
2137 pass
2138
2139class SubclassTime(time):
2140 sub_var = 1
2141
2142class TestTime(HarmlessMixedComparison, unittest.TestCase):
2143
2144 theclass = time
2145
2146 def test_basic_attributes(self):
2147 t = self.theclass(12, 0)
2148 self.assertEqual(t.hour, 12)
2149 self.assertEqual(t.minute, 0)
2150 self.assertEqual(t.second, 0)
2151 self.assertEqual(t.microsecond, 0)
2152
2153 def test_basic_attributes_nonzero(self):
2154 # Make sure all attributes are non-zero so bugs in
2155 # bit-shifting access show up.
2156 t = self.theclass(12, 59, 59, 8000)
2157 self.assertEqual(t.hour, 12)
2158 self.assertEqual(t.minute, 59)
2159 self.assertEqual(t.second, 59)
2160 self.assertEqual(t.microsecond, 8000)
2161
2162 def test_roundtrip(self):
2163 t = self.theclass(1, 2, 3, 4)
2164
2165 # Verify t -> string -> time identity.
2166 s = repr(t)
2167 self.assertTrue(s.startswith('datetime.'))
2168 s = s[9:]
2169 t2 = eval(s)
2170 self.assertEqual(t, t2)
2171
2172 # Verify identity via reconstructing from pieces.
2173 t2 = self.theclass(t.hour, t.minute, t.second,
2174 t.microsecond)
2175 self.assertEqual(t, t2)
2176
2177 def test_comparing(self):
2178 args = [1, 2, 3, 4]
2179 t1 = self.theclass(*args)
2180 t2 = self.theclass(*args)
2181 self.assertEqual(t1, t2)
2182 self.assertTrue(t1 <= t2)
2183 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002184 self.assertFalse(t1 != t2)
2185 self.assertFalse(t1 < t2)
2186 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002187
2188 for i in range(len(args)):
2189 newargs = args[:]
2190 newargs[i] = args[i] + 1
2191 t2 = self.theclass(*newargs) # this is larger than t1
2192 self.assertTrue(t1 < t2)
2193 self.assertTrue(t2 > t1)
2194 self.assertTrue(t1 <= t2)
2195 self.assertTrue(t2 >= t1)
2196 self.assertTrue(t1 != t2)
2197 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002198 self.assertFalse(t1 == t2)
2199 self.assertFalse(t2 == t1)
2200 self.assertFalse(t1 > t2)
2201 self.assertFalse(t2 < t1)
2202 self.assertFalse(t1 >= t2)
2203 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002204
2205 for badarg in OTHERSTUFF:
2206 self.assertEqual(t1 == badarg, False)
2207 self.assertEqual(t1 != badarg, True)
2208 self.assertEqual(badarg == t1, False)
2209 self.assertEqual(badarg != t1, True)
2210
2211 self.assertRaises(TypeError, lambda: t1 <= badarg)
2212 self.assertRaises(TypeError, lambda: t1 < badarg)
2213 self.assertRaises(TypeError, lambda: t1 > badarg)
2214 self.assertRaises(TypeError, lambda: t1 >= badarg)
2215 self.assertRaises(TypeError, lambda: badarg <= t1)
2216 self.assertRaises(TypeError, lambda: badarg < t1)
2217 self.assertRaises(TypeError, lambda: badarg > t1)
2218 self.assertRaises(TypeError, lambda: badarg >= t1)
2219
2220 def test_bad_constructor_arguments(self):
2221 # bad hours
2222 self.theclass(0, 0) # no exception
2223 self.theclass(23, 0) # no exception
2224 self.assertRaises(ValueError, self.theclass, -1, 0)
2225 self.assertRaises(ValueError, self.theclass, 24, 0)
2226 # bad minutes
2227 self.theclass(23, 0) # no exception
2228 self.theclass(23, 59) # no exception
2229 self.assertRaises(ValueError, self.theclass, 23, -1)
2230 self.assertRaises(ValueError, self.theclass, 23, 60)
2231 # bad seconds
2232 self.theclass(23, 59, 0) # no exception
2233 self.theclass(23, 59, 59) # no exception
2234 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
2235 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
2236 # bad microseconds
2237 self.theclass(23, 59, 59, 0) # no exception
2238 self.theclass(23, 59, 59, 999999) # no exception
2239 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
2240 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
2241
2242 def test_hash_equality(self):
2243 d = self.theclass(23, 30, 17)
2244 e = self.theclass(23, 30, 17)
2245 self.assertEqual(d, e)
2246 self.assertEqual(hash(d), hash(e))
2247
2248 dic = {d: 1}
2249 dic[e] = 2
2250 self.assertEqual(len(dic), 1)
2251 self.assertEqual(dic[d], 2)
2252 self.assertEqual(dic[e], 2)
2253
2254 d = self.theclass(0, 5, 17)
2255 e = self.theclass(0, 5, 17)
2256 self.assertEqual(d, e)
2257 self.assertEqual(hash(d), hash(e))
2258
2259 dic = {d: 1}
2260 dic[e] = 2
2261 self.assertEqual(len(dic), 1)
2262 self.assertEqual(dic[d], 2)
2263 self.assertEqual(dic[e], 2)
2264
2265 def test_isoformat(self):
2266 t = self.theclass(4, 5, 1, 123)
2267 self.assertEqual(t.isoformat(), "04:05:01.000123")
2268 self.assertEqual(t.isoformat(), str(t))
2269
2270 t = self.theclass()
2271 self.assertEqual(t.isoformat(), "00:00:00")
2272 self.assertEqual(t.isoformat(), str(t))
2273
2274 t = self.theclass(microsecond=1)
2275 self.assertEqual(t.isoformat(), "00:00:00.000001")
2276 self.assertEqual(t.isoformat(), str(t))
2277
2278 t = self.theclass(microsecond=10)
2279 self.assertEqual(t.isoformat(), "00:00:00.000010")
2280 self.assertEqual(t.isoformat(), str(t))
2281
2282 t = self.theclass(microsecond=100)
2283 self.assertEqual(t.isoformat(), "00:00:00.000100")
2284 self.assertEqual(t.isoformat(), str(t))
2285
2286 t = self.theclass(microsecond=1000)
2287 self.assertEqual(t.isoformat(), "00:00:00.001000")
2288 self.assertEqual(t.isoformat(), str(t))
2289
2290 t = self.theclass(microsecond=10000)
2291 self.assertEqual(t.isoformat(), "00:00:00.010000")
2292 self.assertEqual(t.isoformat(), str(t))
2293
2294 t = self.theclass(microsecond=100000)
2295 self.assertEqual(t.isoformat(), "00:00:00.100000")
2296 self.assertEqual(t.isoformat(), str(t))
2297
2298 def test_1653736(self):
2299 # verify it doesn't accept extra keyword arguments
2300 t = self.theclass(second=1)
2301 self.assertRaises(TypeError, t.isoformat, foo=3)
2302
2303 def test_strftime(self):
2304 t = self.theclass(1, 2, 3, 4)
2305 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
2306 # A naive object replaces %z and %Z with empty strings.
2307 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
2308
2309 def test_format(self):
2310 t = self.theclass(1, 2, 3, 4)
2311 self.assertEqual(t.__format__(''), str(t))
2312
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002313 with self.assertRaisesRegex(TypeError, '^must be str, not int$'):
2314 t.__format__(123)
2315
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002316 # check that a derived class's __str__() gets called
2317 class A(self.theclass):
2318 def __str__(self):
2319 return 'A'
2320 a = A(1, 2, 3, 4)
2321 self.assertEqual(a.__format__(''), 'A')
2322
2323 # check that a derived class's strftime gets called
2324 class B(self.theclass):
2325 def strftime(self, format_spec):
2326 return 'B'
2327 b = B(1, 2, 3, 4)
2328 self.assertEqual(b.__format__(''), str(t))
2329
2330 for fmt in ['%H %M %S',
2331 ]:
2332 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
2333 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
2334 self.assertEqual(b.__format__(fmt), 'B')
2335
2336 def test_str(self):
2337 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
2338 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
2339 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
2340 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
2341 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
2342
2343 def test_repr(self):
2344 name = 'datetime.' + self.theclass.__name__
2345 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
2346 "%s(1, 2, 3, 4)" % name)
2347 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
2348 "%s(10, 2, 3, 4000)" % name)
2349 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
2350 "%s(0, 2, 3, 400000)" % name)
2351 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
2352 "%s(12, 2, 3)" % name)
2353 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
2354 "%s(23, 15)" % name)
2355
2356 def test_resolution_info(self):
2357 self.assertIsInstance(self.theclass.min, self.theclass)
2358 self.assertIsInstance(self.theclass.max, self.theclass)
2359 self.assertIsInstance(self.theclass.resolution, timedelta)
2360 self.assertTrue(self.theclass.max > self.theclass.min)
2361
2362 def test_pickling(self):
2363 args = 20, 59, 16, 64**2
2364 orig = self.theclass(*args)
2365 for pickler, unpickler, proto in pickle_choices:
2366 green = pickler.dumps(orig, proto)
2367 derived = unpickler.loads(green)
2368 self.assertEqual(orig, derived)
2369
2370 def test_pickling_subclass_time(self):
2371 args = 20, 59, 16, 64**2
2372 orig = SubclassTime(*args)
2373 for pickler, unpickler, proto in pickle_choices:
2374 green = pickler.dumps(orig, proto)
2375 derived = unpickler.loads(green)
2376 self.assertEqual(orig, derived)
2377
2378 def test_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002379 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002380 cls = self.theclass
2381 self.assertTrue(cls(1))
2382 self.assertTrue(cls(0, 1))
2383 self.assertTrue(cls(0, 0, 1))
2384 self.assertTrue(cls(0, 0, 0, 1))
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002385 self.assertTrue(cls(0))
2386 self.assertTrue(cls())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002387
2388 def test_replace(self):
2389 cls = self.theclass
2390 args = [1, 2, 3, 4]
2391 base = cls(*args)
2392 self.assertEqual(base, base.replace())
2393
2394 i = 0
2395 for name, newval in (("hour", 5),
2396 ("minute", 6),
2397 ("second", 7),
2398 ("microsecond", 8)):
2399 newargs = args[:]
2400 newargs[i] = newval
2401 expected = cls(*newargs)
2402 got = base.replace(**{name: newval})
2403 self.assertEqual(expected, got)
2404 i += 1
2405
2406 # Out of bounds.
2407 base = cls(1)
2408 self.assertRaises(ValueError, base.replace, hour=24)
2409 self.assertRaises(ValueError, base.replace, minute=-1)
2410 self.assertRaises(ValueError, base.replace, second=100)
2411 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2412
2413 def test_subclass_time(self):
2414
2415 class C(self.theclass):
2416 theAnswer = 42
2417
2418 def __new__(cls, *args, **kws):
2419 temp = kws.copy()
2420 extra = temp.pop('extra')
2421 result = self.theclass.__new__(cls, *args, **temp)
2422 result.extra = extra
2423 return result
2424
2425 def newmeth(self, start):
2426 return start + self.hour + self.second
2427
2428 args = 4, 5, 6
2429
2430 dt1 = self.theclass(*args)
2431 dt2 = C(*args, **{'extra': 7})
2432
2433 self.assertEqual(dt2.__class__, C)
2434 self.assertEqual(dt2.theAnswer, 42)
2435 self.assertEqual(dt2.extra, 7)
2436 self.assertEqual(dt1.isoformat(), dt2.isoformat())
2437 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2438
2439 def test_backdoor_resistance(self):
2440 # see TestDate.test_backdoor_resistance().
2441 base = '2:59.0'
2442 for hour_byte in ' ', '9', chr(24), '\xff':
2443 self.assertRaises(TypeError, self.theclass,
2444 hour_byte + base[1:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002445 # Good bytes, but bad tzinfo:
2446 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
2447 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002448
2449# A mixin for classes with a tzinfo= argument. Subclasses must define
2450# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
2451# must be legit (which is true for time and datetime).
2452class TZInfoBase:
2453
2454 def test_argument_passing(self):
2455 cls = self.theclass
2456 # A datetime passes itself on, a time passes None.
2457 class introspective(tzinfo):
2458 def tzname(self, dt): return dt and "real" or "none"
2459 def utcoffset(self, dt):
2460 return timedelta(minutes = dt and 42 or -42)
2461 dst = utcoffset
2462
2463 obj = cls(1, 2, 3, tzinfo=introspective())
2464
2465 expected = cls is time and "none" or "real"
2466 self.assertEqual(obj.tzname(), expected)
2467
2468 expected = timedelta(minutes=(cls is time and -42 or 42))
2469 self.assertEqual(obj.utcoffset(), expected)
2470 self.assertEqual(obj.dst(), expected)
2471
2472 def test_bad_tzinfo_classes(self):
2473 cls = self.theclass
2474 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
2475
2476 class NiceTry(object):
2477 def __init__(self): pass
2478 def utcoffset(self, dt): pass
2479 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
2480
2481 class BetterTry(tzinfo):
2482 def __init__(self): pass
2483 def utcoffset(self, dt): pass
2484 b = BetterTry()
2485 t = cls(1, 1, 1, tzinfo=b)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002486 self.assertIs(t.tzinfo, b)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002487
2488 def test_utc_offset_out_of_bounds(self):
2489 class Edgy(tzinfo):
2490 def __init__(self, offset):
2491 self.offset = timedelta(minutes=offset)
2492 def utcoffset(self, dt):
2493 return self.offset
2494
2495 cls = self.theclass
2496 for offset, legit in ((-1440, False),
2497 (-1439, True),
2498 (1439, True),
2499 (1440, False)):
2500 if cls is time:
2501 t = cls(1, 2, 3, tzinfo=Edgy(offset))
2502 elif cls is datetime:
2503 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
2504 else:
2505 assert 0, "impossible"
2506 if legit:
2507 aofs = abs(offset)
2508 h, m = divmod(aofs, 60)
2509 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
2510 if isinstance(t, datetime):
2511 t = t.timetz()
2512 self.assertEqual(str(t), "01:02:03" + tag)
2513 else:
2514 self.assertRaises(ValueError, str, t)
2515
2516 def test_tzinfo_classes(self):
2517 cls = self.theclass
2518 class C1(tzinfo):
2519 def utcoffset(self, dt): return None
2520 def dst(self, dt): return None
2521 def tzname(self, dt): return None
2522 for t in (cls(1, 1, 1),
2523 cls(1, 1, 1, tzinfo=None),
2524 cls(1, 1, 1, tzinfo=C1())):
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002525 self.assertIsNone(t.utcoffset())
2526 self.assertIsNone(t.dst())
2527 self.assertIsNone(t.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002528
2529 class C3(tzinfo):
2530 def utcoffset(self, dt): return timedelta(minutes=-1439)
2531 def dst(self, dt): return timedelta(minutes=1439)
2532 def tzname(self, dt): return "aname"
2533 t = cls(1, 1, 1, tzinfo=C3())
2534 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
2535 self.assertEqual(t.dst(), timedelta(minutes=1439))
2536 self.assertEqual(t.tzname(), "aname")
2537
2538 # Wrong types.
2539 class C4(tzinfo):
2540 def utcoffset(self, dt): return "aname"
2541 def dst(self, dt): return 7
2542 def tzname(self, dt): return 0
2543 t = cls(1, 1, 1, tzinfo=C4())
2544 self.assertRaises(TypeError, t.utcoffset)
2545 self.assertRaises(TypeError, t.dst)
2546 self.assertRaises(TypeError, t.tzname)
2547
2548 # Offset out of range.
2549 class C6(tzinfo):
2550 def utcoffset(self, dt): return timedelta(hours=-24)
2551 def dst(self, dt): return timedelta(hours=24)
2552 t = cls(1, 1, 1, tzinfo=C6())
2553 self.assertRaises(ValueError, t.utcoffset)
2554 self.assertRaises(ValueError, t.dst)
2555
2556 # Not a whole number of minutes.
2557 class C7(tzinfo):
2558 def utcoffset(self, dt): return timedelta(seconds=61)
2559 def dst(self, dt): return timedelta(microseconds=-81)
2560 t = cls(1, 1, 1, tzinfo=C7())
2561 self.assertRaises(ValueError, t.utcoffset)
2562 self.assertRaises(ValueError, t.dst)
2563
2564 def test_aware_compare(self):
2565 cls = self.theclass
2566
2567 # Ensure that utcoffset() gets ignored if the comparands have
2568 # the same tzinfo member.
2569 class OperandDependentOffset(tzinfo):
2570 def utcoffset(self, t):
2571 if t.minute < 10:
2572 # d0 and d1 equal after adjustment
2573 return timedelta(minutes=t.minute)
2574 else:
2575 # d2 off in the weeds
2576 return timedelta(minutes=59)
2577
2578 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
2579 d0 = base.replace(minute=3)
2580 d1 = base.replace(minute=9)
2581 d2 = base.replace(minute=11)
2582 for x in d0, d1, d2:
2583 for y in d0, d1, d2:
2584 for op in lt, le, gt, ge, eq, ne:
2585 got = op(x, y)
2586 expected = op(x.minute, y.minute)
2587 self.assertEqual(got, expected)
2588
2589 # However, if they're different members, uctoffset is not ignored.
2590 # Note that a time can't actually have an operand-depedent offset,
2591 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
2592 # so skip this test for time.
2593 if cls is not time:
2594 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2595 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2596 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2597 for x in d0, d1, d2:
2598 for y in d0, d1, d2:
2599 got = (x > y) - (x < y)
2600 if (x is d0 or x is d1) and (y is d0 or y is d1):
2601 expected = 0
2602 elif x is y is d2:
2603 expected = 0
2604 elif x is d2:
2605 expected = -1
2606 else:
2607 assert y is d2
2608 expected = 1
2609 self.assertEqual(got, expected)
2610
2611
2612# Testing time objects with a non-None tzinfo.
2613class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
2614 theclass = time
2615
2616 def test_empty(self):
2617 t = self.theclass()
2618 self.assertEqual(t.hour, 0)
2619 self.assertEqual(t.minute, 0)
2620 self.assertEqual(t.second, 0)
2621 self.assertEqual(t.microsecond, 0)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002622 self.assertIsNone(t.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002623
2624 def test_zones(self):
2625 est = FixedOffset(-300, "EST", 1)
2626 utc = FixedOffset(0, "UTC", -2)
2627 met = FixedOffset(60, "MET", 3)
2628 t1 = time( 7, 47, tzinfo=est)
2629 t2 = time(12, 47, tzinfo=utc)
2630 t3 = time(13, 47, tzinfo=met)
2631 t4 = time(microsecond=40)
2632 t5 = time(microsecond=40, tzinfo=utc)
2633
2634 self.assertEqual(t1.tzinfo, est)
2635 self.assertEqual(t2.tzinfo, utc)
2636 self.assertEqual(t3.tzinfo, met)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002637 self.assertIsNone(t4.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002638 self.assertEqual(t5.tzinfo, utc)
2639
2640 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2641 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2642 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002643 self.assertIsNone(t4.utcoffset())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002644 self.assertRaises(TypeError, t1.utcoffset, "no args")
2645
2646 self.assertEqual(t1.tzname(), "EST")
2647 self.assertEqual(t2.tzname(), "UTC")
2648 self.assertEqual(t3.tzname(), "MET")
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002649 self.assertIsNone(t4.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002650 self.assertRaises(TypeError, t1.tzname, "no args")
2651
2652 self.assertEqual(t1.dst(), timedelta(minutes=1))
2653 self.assertEqual(t2.dst(), timedelta(minutes=-2))
2654 self.assertEqual(t3.dst(), timedelta(minutes=3))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002655 self.assertIsNone(t4.dst())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002656 self.assertRaises(TypeError, t1.dst, "no args")
2657
2658 self.assertEqual(hash(t1), hash(t2))
2659 self.assertEqual(hash(t1), hash(t3))
2660 self.assertEqual(hash(t2), hash(t3))
2661
2662 self.assertEqual(t1, t2)
2663 self.assertEqual(t1, t3)
2664 self.assertEqual(t2, t3)
Alexander Belopolsky08313822012-06-15 20:19:47 -04002665 self.assertNotEqual(t4, t5) # mixed tz-aware & naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002666 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
2667 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
2668
2669 self.assertEqual(str(t1), "07:47:00-05:00")
2670 self.assertEqual(str(t2), "12:47:00+00:00")
2671 self.assertEqual(str(t3), "13:47:00+01:00")
2672 self.assertEqual(str(t4), "00:00:00.000040")
2673 self.assertEqual(str(t5), "00:00:00.000040+00:00")
2674
2675 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
2676 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
2677 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
2678 self.assertEqual(t4.isoformat(), "00:00:00.000040")
2679 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
2680
2681 d = 'datetime.time'
2682 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
2683 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
2684 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
2685 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
2686 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2687
2688 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2689 "07:47:00 %Z=EST %z=-0500")
2690 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2691 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2692
2693 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
2694 t1 = time(23, 59, tzinfo=yuck)
2695 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2696 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2697
2698 # Check that an invalid tzname result raises an exception.
2699 class Badtzname(tzinfo):
Alexander Belopolskye239d232010-12-08 23:31:48 +00002700 tz = 42
2701 def tzname(self, dt): return self.tz
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002702 t = time(2, 3, 4, tzinfo=Badtzname())
2703 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2704 self.assertRaises(TypeError, t.strftime, "%Z")
2705
Alexander Belopolskye239d232010-12-08 23:31:48 +00002706 # Issue #6697:
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002707 if '_Fast' in str(self):
Alexander Belopolskye239d232010-12-08 23:31:48 +00002708 Badtzname.tz = '\ud800'
2709 self.assertRaises(ValueError, t.strftime, "%Z")
2710
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002711 def test_hash_edge_cases(self):
2712 # Offsets that overflow a basic time.
2713 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2714 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2715 self.assertEqual(hash(t1), hash(t2))
2716
2717 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2718 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2719 self.assertEqual(hash(t1), hash(t2))
2720
2721 def test_pickling(self):
2722 # Try one without a tzinfo.
2723 args = 20, 59, 16, 64**2
2724 orig = self.theclass(*args)
2725 for pickler, unpickler, proto in pickle_choices:
2726 green = pickler.dumps(orig, proto)
2727 derived = unpickler.loads(green)
2728 self.assertEqual(orig, derived)
2729
2730 # Try one with a tzinfo.
2731 tinfo = PicklableFixedOffset(-300, 'cookie')
2732 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
2733 for pickler, unpickler, proto in pickle_choices:
2734 green = pickler.dumps(orig, proto)
2735 derived = unpickler.loads(green)
2736 self.assertEqual(orig, derived)
2737 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
2738 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2739 self.assertEqual(derived.tzname(), 'cookie')
2740
2741 def test_more_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002742 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002743 cls = self.theclass
2744
2745 t = cls(0, tzinfo=FixedOffset(-300, ""))
2746 self.assertTrue(t)
2747
2748 t = cls(5, tzinfo=FixedOffset(-300, ""))
2749 self.assertTrue(t)
2750
2751 t = cls(5, tzinfo=FixedOffset(300, ""))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002752 self.assertTrue(t)
2753
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002754 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2755 self.assertTrue(t)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002756
2757 def test_replace(self):
2758 cls = self.theclass
2759 z100 = FixedOffset(100, "+100")
2760 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2761 args = [1, 2, 3, 4, z100]
2762 base = cls(*args)
2763 self.assertEqual(base, base.replace())
2764
2765 i = 0
2766 for name, newval in (("hour", 5),
2767 ("minute", 6),
2768 ("second", 7),
2769 ("microsecond", 8),
2770 ("tzinfo", zm200)):
2771 newargs = args[:]
2772 newargs[i] = newval
2773 expected = cls(*newargs)
2774 got = base.replace(**{name: newval})
2775 self.assertEqual(expected, got)
2776 i += 1
2777
2778 # Ensure we can get rid of a tzinfo.
2779 self.assertEqual(base.tzname(), "+100")
2780 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002781 self.assertIsNone(base2.tzinfo)
2782 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002783
2784 # Ensure we can add one.
2785 base3 = base2.replace(tzinfo=z100)
2786 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002787 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002788
2789 # Out of bounds.
2790 base = cls(1)
2791 self.assertRaises(ValueError, base.replace, hour=24)
2792 self.assertRaises(ValueError, base.replace, minute=-1)
2793 self.assertRaises(ValueError, base.replace, second=100)
2794 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2795
2796 def test_mixed_compare(self):
2797 t1 = time(1, 2, 3)
2798 t2 = time(1, 2, 3)
2799 self.assertEqual(t1, t2)
2800 t2 = t2.replace(tzinfo=None)
2801 self.assertEqual(t1, t2)
2802 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2803 self.assertEqual(t1, t2)
2804 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04002805 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002806
2807 # In time w/ identical tzinfo objects, utcoffset is ignored.
2808 class Varies(tzinfo):
2809 def __init__(self):
2810 self.offset = timedelta(minutes=22)
2811 def utcoffset(self, t):
2812 self.offset += timedelta(minutes=1)
2813 return self.offset
2814
2815 v = Varies()
2816 t1 = t2.replace(tzinfo=v)
2817 t2 = t2.replace(tzinfo=v)
2818 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2819 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2820 self.assertEqual(t1, t2)
2821
2822 # But if they're not identical, it isn't ignored.
2823 t2 = t2.replace(tzinfo=Varies())
2824 self.assertTrue(t1 < t2) # t1's offset counter still going up
2825
2826 def test_subclass_timetz(self):
2827
2828 class C(self.theclass):
2829 theAnswer = 42
2830
2831 def __new__(cls, *args, **kws):
2832 temp = kws.copy()
2833 extra = temp.pop('extra')
2834 result = self.theclass.__new__(cls, *args, **temp)
2835 result.extra = extra
2836 return result
2837
2838 def newmeth(self, start):
2839 return start + self.hour + self.second
2840
2841 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2842
2843 dt1 = self.theclass(*args)
2844 dt2 = C(*args, **{'extra': 7})
2845
2846 self.assertEqual(dt2.__class__, C)
2847 self.assertEqual(dt2.theAnswer, 42)
2848 self.assertEqual(dt2.extra, 7)
2849 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2850 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2851
2852
2853# Testing datetime objects with a non-None tzinfo.
2854
2855class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
2856 theclass = datetime
2857
2858 def test_trivial(self):
2859 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2860 self.assertEqual(dt.year, 1)
2861 self.assertEqual(dt.month, 2)
2862 self.assertEqual(dt.day, 3)
2863 self.assertEqual(dt.hour, 4)
2864 self.assertEqual(dt.minute, 5)
2865 self.assertEqual(dt.second, 6)
2866 self.assertEqual(dt.microsecond, 7)
2867 self.assertEqual(dt.tzinfo, None)
2868
2869 def test_even_more_compare(self):
2870 # The test_compare() and test_more_compare() inherited from TestDate
2871 # and TestDateTime covered non-tzinfo cases.
2872
2873 # Smallest possible after UTC adjustment.
2874 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2875 # Largest possible after UTC adjustment.
2876 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2877 tzinfo=FixedOffset(-1439, ""))
2878
2879 # Make sure those compare correctly, and w/o overflow.
2880 self.assertTrue(t1 < t2)
2881 self.assertTrue(t1 != t2)
2882 self.assertTrue(t2 > t1)
2883
2884 self.assertEqual(t1, t1)
2885 self.assertEqual(t2, t2)
2886
2887 # Equal afer adjustment.
2888 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2889 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2890 self.assertEqual(t1, t2)
2891
2892 # Change t1 not to subtract a minute, and t1 should be larger.
2893 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2894 self.assertTrue(t1 > t2)
2895
2896 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2897 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2898 self.assertTrue(t1 < t2)
2899
2900 # Back to the original t1, but make seconds resolve it.
2901 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2902 second=1)
2903 self.assertTrue(t1 > t2)
2904
2905 # Likewise, but make microseconds resolve it.
2906 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2907 microsecond=1)
2908 self.assertTrue(t1 > t2)
2909
Alexander Belopolsky08313822012-06-15 20:19:47 -04002910 # Make t2 naive and it should differ.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002911 t2 = self.theclass.min
Alexander Belopolsky08313822012-06-15 20:19:47 -04002912 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002913 self.assertEqual(t2, t2)
2914
2915 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2916 class Naive(tzinfo):
2917 def utcoffset(self, dt): return None
2918 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
Alexander Belopolsky08313822012-06-15 20:19:47 -04002919 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002920 self.assertEqual(t2, t2)
2921
2922 # OTOH, it's OK to compare two of these mixing the two ways of being
2923 # naive.
2924 t1 = self.theclass(5, 6, 7)
2925 self.assertEqual(t1, t2)
2926
2927 # Try a bogus uctoffset.
2928 class Bogus(tzinfo):
2929 def utcoffset(self, dt):
2930 return timedelta(minutes=1440) # out of bounds
2931 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2932 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
2933 self.assertRaises(ValueError, lambda: t1 == t2)
2934
2935 def test_pickling(self):
2936 # Try one without a tzinfo.
2937 args = 6, 7, 23, 20, 59, 1, 64**2
2938 orig = self.theclass(*args)
2939 for pickler, unpickler, proto in pickle_choices:
2940 green = pickler.dumps(orig, proto)
2941 derived = unpickler.loads(green)
2942 self.assertEqual(orig, derived)
2943
2944 # Try one with a tzinfo.
2945 tinfo = PicklableFixedOffset(-300, 'cookie')
2946 orig = self.theclass(*args, **{'tzinfo': tinfo})
2947 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
2948 for pickler, unpickler, proto in pickle_choices:
2949 green = pickler.dumps(orig, proto)
2950 derived = unpickler.loads(green)
2951 self.assertEqual(orig, derived)
2952 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
2953 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2954 self.assertEqual(derived.tzname(), 'cookie')
2955
2956 def test_extreme_hashes(self):
2957 # If an attempt is made to hash these via subtracting the offset
2958 # then hashing a datetime object, OverflowError results. The
2959 # Python implementation used to blow up here.
2960 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2961 hash(t)
2962 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2963 tzinfo=FixedOffset(-1439, ""))
2964 hash(t)
2965
2966 # OTOH, an OOB offset should blow up.
2967 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2968 self.assertRaises(ValueError, hash, t)
2969
2970 def test_zones(self):
2971 est = FixedOffset(-300, "EST")
2972 utc = FixedOffset(0, "UTC")
2973 met = FixedOffset(60, "MET")
2974 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2975 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2976 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
2977 self.assertEqual(t1.tzinfo, est)
2978 self.assertEqual(t2.tzinfo, utc)
2979 self.assertEqual(t3.tzinfo, met)
2980 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2981 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2982 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
2983 self.assertEqual(t1.tzname(), "EST")
2984 self.assertEqual(t2.tzname(), "UTC")
2985 self.assertEqual(t3.tzname(), "MET")
2986 self.assertEqual(hash(t1), hash(t2))
2987 self.assertEqual(hash(t1), hash(t3))
2988 self.assertEqual(hash(t2), hash(t3))
2989 self.assertEqual(t1, t2)
2990 self.assertEqual(t1, t3)
2991 self.assertEqual(t2, t3)
2992 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2993 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2994 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
2995 d = 'datetime.datetime(2002, 3, 19, '
2996 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2997 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2998 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2999
3000 def test_combine(self):
3001 met = FixedOffset(60, "MET")
3002 d = date(2002, 3, 4)
3003 tz = time(18, 45, 3, 1234, tzinfo=met)
3004 dt = datetime.combine(d, tz)
3005 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
3006 tzinfo=met))
3007
3008 def test_extract(self):
3009 met = FixedOffset(60, "MET")
3010 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
3011 self.assertEqual(dt.date(), date(2002, 3, 4))
3012 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
3013 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
3014
3015 def test_tz_aware_arithmetic(self):
3016 import random
3017
3018 now = self.theclass.now()
3019 tz55 = FixedOffset(-330, "west 5:30")
3020 timeaware = now.time().replace(tzinfo=tz55)
3021 nowaware = self.theclass.combine(now.date(), timeaware)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003022 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003023 self.assertEqual(nowaware.timetz(), timeaware)
3024
3025 # Can't mix aware and non-aware.
3026 self.assertRaises(TypeError, lambda: now - nowaware)
3027 self.assertRaises(TypeError, lambda: nowaware - now)
3028
3029 # And adding datetime's doesn't make sense, aware or not.
3030 self.assertRaises(TypeError, lambda: now + nowaware)
3031 self.assertRaises(TypeError, lambda: nowaware + now)
3032 self.assertRaises(TypeError, lambda: nowaware + nowaware)
3033
3034 # Subtracting should yield 0.
3035 self.assertEqual(now - now, timedelta(0))
3036 self.assertEqual(nowaware - nowaware, timedelta(0))
3037
3038 # Adding a delta should preserve tzinfo.
3039 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
3040 nowawareplus = nowaware + delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003041 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003042 nowawareplus2 = delta + nowaware
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003043 self.assertIs(nowawareplus2.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003044 self.assertEqual(nowawareplus, nowawareplus2)
3045
3046 # that - delta should be what we started with, and that - what we
3047 # started with should be delta.
3048 diff = nowawareplus - delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003049 self.assertIs(diff.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003050 self.assertEqual(nowaware, diff)
3051 self.assertRaises(TypeError, lambda: delta - nowawareplus)
3052 self.assertEqual(nowawareplus - nowaware, delta)
3053
3054 # Make up a random timezone.
3055 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
3056 # Attach it to nowawareplus.
3057 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003058 self.assertIs(nowawareplus.tzinfo, tzr)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003059 # Make sure the difference takes the timezone adjustments into account.
3060 got = nowaware - nowawareplus
3061 # Expected: (nowaware base - nowaware offset) -
3062 # (nowawareplus base - nowawareplus offset) =
3063 # (nowaware base - nowawareplus base) +
3064 # (nowawareplus offset - nowaware offset) =
3065 # -delta + nowawareplus offset - nowaware offset
3066 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
3067 self.assertEqual(got, expected)
3068
3069 # Try max possible difference.
3070 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
3071 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3072 tzinfo=FixedOffset(-1439, "max"))
3073 maxdiff = max - min
3074 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
3075 timedelta(minutes=2*1439))
3076 # Different tzinfo, but the same offset
3077 tza = timezone(HOUR, 'A')
3078 tzb = timezone(HOUR, 'B')
3079 delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
3080 self.assertEqual(delta, self.theclass.min - self.theclass.max)
3081
3082 def test_tzinfo_now(self):
3083 meth = self.theclass.now
3084 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3085 base = meth()
3086 # Try with and without naming the keyword.
3087 off42 = FixedOffset(42, "42")
3088 another = meth(off42)
3089 again = meth(tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003090 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003091 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3092 # Bad argument with and w/o naming the keyword.
3093 self.assertRaises(TypeError, meth, 16)
3094 self.assertRaises(TypeError, meth, tzinfo=16)
3095 # Bad keyword name.
3096 self.assertRaises(TypeError, meth, tinfo=off42)
3097 # Too many args.
3098 self.assertRaises(TypeError, meth, off42, off42)
3099
3100 # We don't know which time zone we're in, and don't have a tzinfo
3101 # class to represent it, so seeing whether a tz argument actually
3102 # does a conversion is tricky.
3103 utc = FixedOffset(0, "utc", 0)
3104 for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
3105 timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
3106 for dummy in range(3):
3107 now = datetime.now(weirdtz)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003108 self.assertIs(now.tzinfo, weirdtz)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003109 utcnow = datetime.utcnow().replace(tzinfo=utc)
3110 now2 = utcnow.astimezone(weirdtz)
3111 if abs(now - now2) < timedelta(seconds=30):
3112 break
3113 # Else the code is broken, or more than 30 seconds passed between
3114 # calls; assuming the latter, just try again.
3115 else:
3116 # Three strikes and we're out.
3117 self.fail("utcnow(), now(tz), or astimezone() may be broken")
3118
3119 def test_tzinfo_fromtimestamp(self):
3120 import time
3121 meth = self.theclass.fromtimestamp
3122 ts = time.time()
3123 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3124 base = meth(ts)
3125 # Try with and without naming the keyword.
3126 off42 = FixedOffset(42, "42")
3127 another = meth(ts, off42)
3128 again = meth(ts, tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003129 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003130 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3131 # Bad argument with and w/o naming the keyword.
3132 self.assertRaises(TypeError, meth, ts, 16)
3133 self.assertRaises(TypeError, meth, ts, tzinfo=16)
3134 # Bad keyword name.
3135 self.assertRaises(TypeError, meth, ts, tinfo=off42)
3136 # Too many args.
3137 self.assertRaises(TypeError, meth, ts, off42, off42)
3138 # Too few args.
3139 self.assertRaises(TypeError, meth)
3140
3141 # Try to make sure tz= actually does some conversion.
3142 timestamp = 1000000000
3143 utcdatetime = datetime.utcfromtimestamp(timestamp)
3144 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
3145 # But on some flavor of Mac, it's nowhere near that. So we can't have
3146 # any idea here what time that actually is, we can only test that
3147 # relative changes match.
3148 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
3149 tz = FixedOffset(utcoffset, "tz", 0)
3150 expected = utcdatetime + utcoffset
3151 got = datetime.fromtimestamp(timestamp, tz)
3152 self.assertEqual(expected, got.replace(tzinfo=None))
3153
3154 def test_tzinfo_utcnow(self):
3155 meth = self.theclass.utcnow
3156 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3157 base = meth()
3158 # Try with and without naming the keyword; for whatever reason,
3159 # utcnow() doesn't accept a tzinfo argument.
3160 off42 = FixedOffset(42, "42")
3161 self.assertRaises(TypeError, meth, off42)
3162 self.assertRaises(TypeError, meth, tzinfo=off42)
3163
3164 def test_tzinfo_utcfromtimestamp(self):
3165 import time
3166 meth = self.theclass.utcfromtimestamp
3167 ts = time.time()
3168 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3169 base = meth(ts)
3170 # Try with and without naming the keyword; for whatever reason,
3171 # utcfromtimestamp() doesn't accept a tzinfo argument.
3172 off42 = FixedOffset(42, "42")
3173 self.assertRaises(TypeError, meth, ts, off42)
3174 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
3175
3176 def test_tzinfo_timetuple(self):
3177 # TestDateTime tested most of this. datetime adds a twist to the
3178 # DST flag.
3179 class DST(tzinfo):
3180 def __init__(self, dstvalue):
3181 if isinstance(dstvalue, int):
3182 dstvalue = timedelta(minutes=dstvalue)
3183 self.dstvalue = dstvalue
3184 def dst(self, dt):
3185 return self.dstvalue
3186
3187 cls = self.theclass
3188 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
3189 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
3190 t = d.timetuple()
3191 self.assertEqual(1, t.tm_year)
3192 self.assertEqual(1, t.tm_mon)
3193 self.assertEqual(1, t.tm_mday)
3194 self.assertEqual(10, t.tm_hour)
3195 self.assertEqual(20, t.tm_min)
3196 self.assertEqual(30, t.tm_sec)
3197 self.assertEqual(0, t.tm_wday)
3198 self.assertEqual(1, t.tm_yday)
3199 self.assertEqual(flag, t.tm_isdst)
3200
3201 # dst() returns wrong type.
3202 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
3203
3204 # dst() at the edge.
3205 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
3206 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
3207
3208 # dst() out of range.
3209 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
3210 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
3211
3212 def test_utctimetuple(self):
3213 class DST(tzinfo):
3214 def __init__(self, dstvalue=0):
3215 if isinstance(dstvalue, int):
3216 dstvalue = timedelta(minutes=dstvalue)
3217 self.dstvalue = dstvalue
3218 def dst(self, dt):
3219 return self.dstvalue
3220
3221 cls = self.theclass
3222 # This can't work: DST didn't implement utcoffset.
3223 self.assertRaises(NotImplementedError,
3224 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
3225
3226 class UOFS(DST):
3227 def __init__(self, uofs, dofs=None):
3228 DST.__init__(self, dofs)
3229 self.uofs = timedelta(minutes=uofs)
3230 def utcoffset(self, dt):
3231 return self.uofs
3232
3233 for dstvalue in -33, 33, 0, None:
3234 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
3235 t = d.utctimetuple()
3236 self.assertEqual(d.year, t.tm_year)
3237 self.assertEqual(d.month, t.tm_mon)
3238 self.assertEqual(d.day, t.tm_mday)
3239 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
3240 self.assertEqual(13, t.tm_min)
3241 self.assertEqual(d.second, t.tm_sec)
3242 self.assertEqual(d.weekday(), t.tm_wday)
3243 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
3244 t.tm_yday)
3245 # Ensure tm_isdst is 0 regardless of what dst() says: DST
3246 # is never in effect for a UTC time.
3247 self.assertEqual(0, t.tm_isdst)
3248
3249 # For naive datetime, utctimetuple == timetuple except for isdst
3250 d = cls(1, 2, 3, 10, 20, 30, 40)
3251 t = d.utctimetuple()
3252 self.assertEqual(t[:-1], d.timetuple()[:-1])
3253 self.assertEqual(0, t.tm_isdst)
3254 # Same if utcoffset is None
3255 class NOFS(DST):
3256 def utcoffset(self, dt):
3257 return None
3258 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
3259 t = d.utctimetuple()
3260 self.assertEqual(t[:-1], d.timetuple()[:-1])
3261 self.assertEqual(0, t.tm_isdst)
3262 # Check that bad tzinfo is detected
3263 class BOFS(DST):
3264 def utcoffset(self, dt):
3265 return "EST"
3266 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
3267 self.assertRaises(TypeError, d.utctimetuple)
3268
3269 # Check that utctimetuple() is the same as
3270 # astimezone(utc).timetuple()
3271 d = cls(2010, 11, 13, 14, 15, 16, 171819)
3272 for tz in [timezone.min, timezone.utc, timezone.max]:
3273 dtz = d.replace(tzinfo=tz)
3274 self.assertEqual(dtz.utctimetuple()[:-1],
3275 dtz.astimezone(timezone.utc).timetuple()[:-1])
3276 # At the edges, UTC adjustment can produce years out-of-range
3277 # for a datetime object. Ensure that an OverflowError is
3278 # raised.
3279 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
3280 # That goes back 1 minute less than a full day.
3281 self.assertRaises(OverflowError, tiny.utctimetuple)
3282
3283 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
3284 # That goes forward 1 minute less than a full day.
3285 self.assertRaises(OverflowError, huge.utctimetuple)
3286 # More overflow cases
3287 tiny = cls.min.replace(tzinfo=timezone(MINUTE))
3288 self.assertRaises(OverflowError, tiny.utctimetuple)
3289 huge = cls.max.replace(tzinfo=timezone(-MINUTE))
3290 self.assertRaises(OverflowError, huge.utctimetuple)
3291
3292 def test_tzinfo_isoformat(self):
3293 zero = FixedOffset(0, "+00:00")
3294 plus = FixedOffset(220, "+03:40")
3295 minus = FixedOffset(-231, "-03:51")
3296 unknown = FixedOffset(None, "")
3297
3298 cls = self.theclass
3299 datestr = '0001-02-03'
3300 for ofs in None, zero, plus, minus, unknown:
3301 for us in 0, 987001:
3302 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
3303 timestr = '04:05:59' + (us and '.987001' or '')
3304 ofsstr = ofs is not None and d.tzname() or ''
3305 tailstr = timestr + ofsstr
3306 iso = d.isoformat()
3307 self.assertEqual(iso, datestr + 'T' + tailstr)
3308 self.assertEqual(iso, d.isoformat('T'))
3309 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
3310 self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
3311 self.assertEqual(str(d), datestr + ' ' + tailstr)
3312
3313 def test_replace(self):
3314 cls = self.theclass
3315 z100 = FixedOffset(100, "+100")
3316 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3317 args = [1, 2, 3, 4, 5, 6, 7, z100]
3318 base = cls(*args)
3319 self.assertEqual(base, base.replace())
3320
3321 i = 0
3322 for name, newval in (("year", 2),
3323 ("month", 3),
3324 ("day", 4),
3325 ("hour", 5),
3326 ("minute", 6),
3327 ("second", 7),
3328 ("microsecond", 8),
3329 ("tzinfo", zm200)):
3330 newargs = args[:]
3331 newargs[i] = newval
3332 expected = cls(*newargs)
3333 got = base.replace(**{name: newval})
3334 self.assertEqual(expected, got)
3335 i += 1
3336
3337 # Ensure we can get rid of a tzinfo.
3338 self.assertEqual(base.tzname(), "+100")
3339 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003340 self.assertIsNone(base2.tzinfo)
3341 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003342
3343 # Ensure we can add one.
3344 base3 = base2.replace(tzinfo=z100)
3345 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003346 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003347
3348 # Out of bounds.
3349 base = cls(2000, 2, 29)
3350 self.assertRaises(ValueError, base.replace, year=2001)
3351
3352 def test_more_astimezone(self):
3353 # The inherited test_astimezone covered some trivial and error cases.
3354 fnone = FixedOffset(None, "None")
3355 f44m = FixedOffset(44, "44")
3356 fm5h = FixedOffset(-timedelta(hours=5), "m300")
3357
3358 dt = self.theclass.now(tz=f44m)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003359 self.assertIs(dt.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003360 # Replacing with degenerate tzinfo raises an exception.
3361 self.assertRaises(ValueError, dt.astimezone, fnone)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003362 # Replacing with same tzinfo makes no change.
3363 x = dt.astimezone(dt.tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003364 self.assertIs(x.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003365 self.assertEqual(x.date(), dt.date())
3366 self.assertEqual(x.time(), dt.time())
3367
3368 # Replacing with different tzinfo does adjust.
3369 got = dt.astimezone(fm5h)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003370 self.assertIs(got.tzinfo, fm5h)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003371 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
3372 expected = dt - dt.utcoffset() # in effect, convert to UTC
3373 expected += fm5h.utcoffset(dt) # and from there to local time
3374 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
3375 self.assertEqual(got.date(), expected.date())
3376 self.assertEqual(got.time(), expected.time())
3377 self.assertEqual(got.timetz(), expected.timetz())
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003378 self.assertIs(got.tzinfo, expected.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003379 self.assertEqual(got, expected)
3380
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003381 @support.run_with_tz('UTC')
3382 def test_astimezone_default_utc(self):
3383 dt = self.theclass.now(timezone.utc)
3384 self.assertEqual(dt.astimezone(None), dt)
3385 self.assertEqual(dt.astimezone(), dt)
3386
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003387 # Note that offset in TZ variable has the opposite sign to that
3388 # produced by %z directive.
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003389 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
3390 def test_astimezone_default_eastern(self):
3391 dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
3392 local = dt.astimezone()
3393 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003394 self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003395 dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
3396 local = dt.astimezone()
3397 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003398 self.assertEqual(local.strftime("%z %Z"), "-0400 EDT")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003399
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003400 def test_aware_subtract(self):
3401 cls = self.theclass
3402
3403 # Ensure that utcoffset() is ignored when the operands have the
3404 # same tzinfo member.
3405 class OperandDependentOffset(tzinfo):
3406 def utcoffset(self, t):
3407 if t.minute < 10:
3408 # d0 and d1 equal after adjustment
3409 return timedelta(minutes=t.minute)
3410 else:
3411 # d2 off in the weeds
3412 return timedelta(minutes=59)
3413
3414 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
3415 d0 = base.replace(minute=3)
3416 d1 = base.replace(minute=9)
3417 d2 = base.replace(minute=11)
3418 for x in d0, d1, d2:
3419 for y in d0, d1, d2:
3420 got = x - y
3421 expected = timedelta(minutes=x.minute - y.minute)
3422 self.assertEqual(got, expected)
3423
3424 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
3425 # ignored.
3426 base = cls(8, 9, 10, 11, 12, 13, 14)
3427 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3428 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3429 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3430 for x in d0, d1, d2:
3431 for y in d0, d1, d2:
3432 got = x - y
3433 if (x is d0 or x is d1) and (y is d0 or y is d1):
3434 expected = timedelta(0)
3435 elif x is y is d2:
3436 expected = timedelta(0)
3437 elif x is d2:
3438 expected = timedelta(minutes=(11-59)-0)
3439 else:
3440 assert y is d2
3441 expected = timedelta(minutes=0-(11-59))
3442 self.assertEqual(got, expected)
3443
3444 def test_mixed_compare(self):
3445 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
3446 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
3447 self.assertEqual(t1, t2)
3448 t2 = t2.replace(tzinfo=None)
3449 self.assertEqual(t1, t2)
3450 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3451 self.assertEqual(t1, t2)
3452 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04003453 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003454
3455 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
3456 class Varies(tzinfo):
3457 def __init__(self):
3458 self.offset = timedelta(minutes=22)
3459 def utcoffset(self, t):
3460 self.offset += timedelta(minutes=1)
3461 return self.offset
3462
3463 v = Varies()
3464 t1 = t2.replace(tzinfo=v)
3465 t2 = t2.replace(tzinfo=v)
3466 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3467 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3468 self.assertEqual(t1, t2)
3469
3470 # But if they're not identical, it isn't ignored.
3471 t2 = t2.replace(tzinfo=Varies())
3472 self.assertTrue(t1 < t2) # t1's offset counter still going up
3473
3474 def test_subclass_datetimetz(self):
3475
3476 class C(self.theclass):
3477 theAnswer = 42
3478
3479 def __new__(cls, *args, **kws):
3480 temp = kws.copy()
3481 extra = temp.pop('extra')
3482 result = self.theclass.__new__(cls, *args, **temp)
3483 result.extra = extra
3484 return result
3485
3486 def newmeth(self, start):
3487 return start + self.hour + self.year
3488
3489 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3490
3491 dt1 = self.theclass(*args)
3492 dt2 = C(*args, **{'extra': 7})
3493
3494 self.assertEqual(dt2.__class__, C)
3495 self.assertEqual(dt2.theAnswer, 42)
3496 self.assertEqual(dt2.extra, 7)
3497 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3498 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
3499
3500# Pain to set up DST-aware tzinfo classes.
3501
3502def first_sunday_on_or_after(dt):
3503 days_to_go = 6 - dt.weekday()
3504 if days_to_go:
3505 dt += timedelta(days_to_go)
3506 return dt
3507
3508ZERO = timedelta(0)
3509MINUTE = timedelta(minutes=1)
3510HOUR = timedelta(hours=1)
3511DAY = timedelta(days=1)
3512# In the US, DST starts at 2am (standard time) on the first Sunday in April.
3513DSTSTART = datetime(1, 4, 1, 2)
3514# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
3515# which is the first Sunday on or after Oct 25. Because we view 1:MM as
3516# being standard time on that day, there is no spelling in local time of
3517# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
3518DSTEND = datetime(1, 10, 25, 1)
3519
3520class USTimeZone(tzinfo):
3521
3522 def __init__(self, hours, reprname, stdname, dstname):
3523 self.stdoffset = timedelta(hours=hours)
3524 self.reprname = reprname
3525 self.stdname = stdname
3526 self.dstname = dstname
3527
3528 def __repr__(self):
3529 return self.reprname
3530
3531 def tzname(self, dt):
3532 if self.dst(dt):
3533 return self.dstname
3534 else:
3535 return self.stdname
3536
3537 def utcoffset(self, dt):
3538 return self.stdoffset + self.dst(dt)
3539
3540 def dst(self, dt):
3541 if dt is None or dt.tzinfo is None:
3542 # An exception instead may be sensible here, in one or more of
3543 # the cases.
3544 return ZERO
3545 assert dt.tzinfo is self
3546
3547 # Find first Sunday in April.
3548 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
3549 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
3550
3551 # Find last Sunday in October.
3552 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
3553 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
3554
3555 # Can't compare naive to aware objects, so strip the timezone from
3556 # dt first.
3557 if start <= dt.replace(tzinfo=None) < end:
3558 return HOUR
3559 else:
3560 return ZERO
3561
3562Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
3563Central = USTimeZone(-6, "Central", "CST", "CDT")
3564Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
3565Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
3566utc_real = FixedOffset(0, "UTC", 0)
3567# For better test coverage, we want another flavor of UTC that's west of
3568# the Eastern and Pacific timezones.
3569utc_fake = FixedOffset(-12*60, "UTCfake", 0)
3570
3571class TestTimezoneConversions(unittest.TestCase):
3572 # The DST switch times for 2002, in std time.
3573 dston = datetime(2002, 4, 7, 2)
3574 dstoff = datetime(2002, 10, 27, 1)
3575
3576 theclass = datetime
3577
3578 # Check a time that's inside DST.
3579 def checkinside(self, dt, tz, utc, dston, dstoff):
3580 self.assertEqual(dt.dst(), HOUR)
3581
3582 # Conversion to our own timezone is always an identity.
3583 self.assertEqual(dt.astimezone(tz), dt)
3584
3585 asutc = dt.astimezone(utc)
3586 there_and_back = asutc.astimezone(tz)
3587
3588 # Conversion to UTC and back isn't always an identity here,
3589 # because there are redundant spellings (in local time) of
3590 # UTC time when DST begins: the clock jumps from 1:59:59
3591 # to 3:00:00, and a local time of 2:MM:SS doesn't really
3592 # make sense then. The classes above treat 2:MM:SS as
3593 # daylight time then (it's "after 2am"), really an alias
3594 # for 1:MM:SS standard time. The latter form is what
3595 # conversion back from UTC produces.
3596 if dt.date() == dston.date() and dt.hour == 2:
3597 # We're in the redundant hour, and coming back from
3598 # UTC gives the 1:MM:SS standard-time spelling.
3599 self.assertEqual(there_and_back + HOUR, dt)
3600 # Although during was considered to be in daylight
3601 # time, there_and_back is not.
3602 self.assertEqual(there_and_back.dst(), ZERO)
3603 # They're the same times in UTC.
3604 self.assertEqual(there_and_back.astimezone(utc),
3605 dt.astimezone(utc))
3606 else:
3607 # We're not in the redundant hour.
3608 self.assertEqual(dt, there_and_back)
3609
3610 # Because we have a redundant spelling when DST begins, there is
Ezio Melotti3b3499b2011-03-16 11:35:38 +02003611 # (unfortunately) an hour when DST ends that can't be spelled at all in
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003612 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
3613 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
3614 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
3615 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
3616 # expressed in local time. Nevertheless, we want conversion back
3617 # from UTC to mimic the local clock's "repeat an hour" behavior.
3618 nexthour_utc = asutc + HOUR
3619 nexthour_tz = nexthour_utc.astimezone(tz)
3620 if dt.date() == dstoff.date() and dt.hour == 0:
3621 # We're in the hour before the last DST hour. The last DST hour
3622 # is ineffable. We want the conversion back to repeat 1:MM.
3623 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3624 nexthour_utc += HOUR
3625 nexthour_tz = nexthour_utc.astimezone(tz)
3626 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3627 else:
3628 self.assertEqual(nexthour_tz - dt, HOUR)
3629
3630 # Check a time that's outside DST.
3631 def checkoutside(self, dt, tz, utc):
3632 self.assertEqual(dt.dst(), ZERO)
3633
3634 # Conversion to our own timezone is always an identity.
3635 self.assertEqual(dt.astimezone(tz), dt)
3636
3637 # Converting to UTC and back is an identity too.
3638 asutc = dt.astimezone(utc)
3639 there_and_back = asutc.astimezone(tz)
3640 self.assertEqual(dt, there_and_back)
3641
3642 def convert_between_tz_and_utc(self, tz, utc):
3643 dston = self.dston.replace(tzinfo=tz)
3644 # Because 1:MM on the day DST ends is taken as being standard time,
3645 # there is no spelling in tz for the last hour of daylight time.
3646 # For purposes of the test, the last hour of DST is 0:MM, which is
3647 # taken as being daylight time (and 1:MM is taken as being standard
3648 # time).
3649 dstoff = self.dstoff.replace(tzinfo=tz)
3650 for delta in (timedelta(weeks=13),
3651 DAY,
3652 HOUR,
3653 timedelta(minutes=1),
3654 timedelta(microseconds=1)):
3655
3656 self.checkinside(dston, tz, utc, dston, dstoff)
3657 for during in dston + delta, dstoff - delta:
3658 self.checkinside(during, tz, utc, dston, dstoff)
3659
3660 self.checkoutside(dstoff, tz, utc)
3661 for outside in dston - delta, dstoff + delta:
3662 self.checkoutside(outside, tz, utc)
3663
3664 def test_easy(self):
3665 # Despite the name of this test, the endcases are excruciating.
3666 self.convert_between_tz_and_utc(Eastern, utc_real)
3667 self.convert_between_tz_and_utc(Pacific, utc_real)
3668 self.convert_between_tz_and_utc(Eastern, utc_fake)
3669 self.convert_between_tz_and_utc(Pacific, utc_fake)
3670 # The next is really dancing near the edge. It works because
3671 # Pacific and Eastern are far enough apart that their "problem
3672 # hours" don't overlap.
3673 self.convert_between_tz_and_utc(Eastern, Pacific)
3674 self.convert_between_tz_and_utc(Pacific, Eastern)
3675 # OTOH, these fail! Don't enable them. The difficulty is that
3676 # the edge case tests assume that every hour is representable in
3677 # the "utc" class. This is always true for a fixed-offset tzinfo
3678 # class (lke utc_real and utc_fake), but not for Eastern or Central.
3679 # For these adjacent DST-aware time zones, the range of time offsets
3680 # tested ends up creating hours in the one that aren't representable
3681 # in the other. For the same reason, we would see failures in the
3682 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
3683 # offset deltas in convert_between_tz_and_utc().
3684 #
3685 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
3686 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
3687
3688 def test_tricky(self):
3689 # 22:00 on day before daylight starts.
3690 fourback = self.dston - timedelta(hours=4)
3691 ninewest = FixedOffset(-9*60, "-0900", 0)
3692 fourback = fourback.replace(tzinfo=ninewest)
3693 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
3694 # 2", we should get the 3 spelling.
3695 # If we plug 22:00 the day before into Eastern, it "looks like std
3696 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
3697 # to 22:00 lands on 2:00, which makes no sense in local time (the
3698 # local clock jumps from 1 to 3). The point here is to make sure we
3699 # get the 3 spelling.
3700 expected = self.dston.replace(hour=3)
3701 got = fourback.astimezone(Eastern).replace(tzinfo=None)
3702 self.assertEqual(expected, got)
3703
3704 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
3705 # case we want the 1:00 spelling.
3706 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
3707 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
3708 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
3709 # spelling.
3710 expected = self.dston.replace(hour=1)
3711 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
3712 self.assertEqual(expected, got)
3713
3714 # Now on the day DST ends, we want "repeat an hour" behavior.
3715 # UTC 4:MM 5:MM 6:MM 7:MM checking these
3716 # EST 23:MM 0:MM 1:MM 2:MM
3717 # EDT 0:MM 1:MM 2:MM 3:MM
3718 # wall 0:MM 1:MM 1:MM 2:MM against these
3719 for utc in utc_real, utc_fake:
3720 for tz in Eastern, Pacific:
3721 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
3722 # Convert that to UTC.
3723 first_std_hour -= tz.utcoffset(None)
3724 # Adjust for possibly fake UTC.
3725 asutc = first_std_hour + utc.utcoffset(None)
3726 # First UTC hour to convert; this is 4:00 when utc=utc_real &
3727 # tz=Eastern.
3728 asutcbase = asutc.replace(tzinfo=utc)
3729 for tzhour in (0, 1, 1, 2):
3730 expectedbase = self.dstoff.replace(hour=tzhour)
3731 for minute in 0, 30, 59:
3732 expected = expectedbase.replace(minute=minute)
3733 asutc = asutcbase.replace(minute=minute)
3734 astz = asutc.astimezone(tz)
3735 self.assertEqual(astz.replace(tzinfo=None), expected)
3736 asutcbase += HOUR
3737
3738
3739 def test_bogus_dst(self):
3740 class ok(tzinfo):
3741 def utcoffset(self, dt): return HOUR
3742 def dst(self, dt): return HOUR
3743
3744 now = self.theclass.now().replace(tzinfo=utc_real)
3745 # Doesn't blow up.
3746 now.astimezone(ok())
3747
3748 # Does blow up.
3749 class notok(ok):
3750 def dst(self, dt): return None
3751 self.assertRaises(ValueError, now.astimezone, notok())
3752
3753 # Sometimes blow up. In the following, tzinfo.dst()
3754 # implementation may return None or not None depending on
3755 # whether DST is assumed to be in effect. In this situation,
3756 # a ValueError should be raised by astimezone().
3757 class tricky_notok(ok):
3758 def dst(self, dt):
3759 if dt.year == 2000:
3760 return None
3761 else:
3762 return 10*HOUR
3763 dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
3764 self.assertRaises(ValueError, dt.astimezone, tricky_notok())
3765
3766 def test_fromutc(self):
3767 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
3768 now = datetime.utcnow().replace(tzinfo=utc_real)
3769 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3770 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
3771 enow = Eastern.fromutc(now) # doesn't blow up
3772 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3773 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3774 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3775
3776 # Always converts UTC to standard time.
3777 class FauxUSTimeZone(USTimeZone):
3778 def fromutc(self, dt):
3779 return dt + self.stdoffset
3780 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
3781
3782 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
3783 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
3784 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
3785
3786 # Check around DST start.
3787 start = self.dston.replace(hour=4, tzinfo=Eastern)
3788 fstart = start.replace(tzinfo=FEastern)
3789 for wall in 23, 0, 1, 3, 4, 5:
3790 expected = start.replace(hour=wall)
3791 if wall == 23:
3792 expected -= timedelta(days=1)
3793 got = Eastern.fromutc(start)
3794 self.assertEqual(expected, got)
3795
3796 expected = fstart + FEastern.stdoffset
3797 got = FEastern.fromutc(fstart)
3798 self.assertEqual(expected, got)
3799
3800 # Ensure astimezone() calls fromutc() too.
3801 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3802 self.assertEqual(expected, got)
3803
3804 start += HOUR
3805 fstart += HOUR
3806
3807 # Check around DST end.
3808 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
3809 fstart = start.replace(tzinfo=FEastern)
3810 for wall in 0, 1, 1, 2, 3, 4:
3811 expected = start.replace(hour=wall)
3812 got = Eastern.fromutc(start)
3813 self.assertEqual(expected, got)
3814
3815 expected = fstart + FEastern.stdoffset
3816 got = FEastern.fromutc(fstart)
3817 self.assertEqual(expected, got)
3818
3819 # Ensure astimezone() calls fromutc() too.
3820 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3821 self.assertEqual(expected, got)
3822
3823 start += HOUR
3824 fstart += HOUR
3825
3826
3827#############################################################################
3828# oddballs
3829
3830class Oddballs(unittest.TestCase):
3831
3832 def test_bug_1028306(self):
3833 # Trying to compare a date to a datetime should act like a mixed-
3834 # type comparison, despite that datetime is a subclass of date.
3835 as_date = date.today()
3836 as_datetime = datetime.combine(as_date, time())
3837 self.assertTrue(as_date != as_datetime)
3838 self.assertTrue(as_datetime != as_date)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003839 self.assertFalse(as_date == as_datetime)
3840 self.assertFalse(as_datetime == as_date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003841 self.assertRaises(TypeError, lambda: as_date < as_datetime)
3842 self.assertRaises(TypeError, lambda: as_datetime < as_date)
3843 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
3844 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
3845 self.assertRaises(TypeError, lambda: as_date > as_datetime)
3846 self.assertRaises(TypeError, lambda: as_datetime > as_date)
3847 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
3848 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
3849
3850 # Neverthelss, comparison should work with the base-class (date)
3851 # projection if use of a date method is forced.
3852 self.assertEqual(as_date.__eq__(as_datetime), True)
3853 different_day = (as_date.day + 1) % 20 + 1
3854 as_different = as_datetime.replace(day= different_day)
3855 self.assertEqual(as_date.__eq__(as_different), False)
3856
3857 # And date should compare with other subclasses of date. If a
3858 # subclass wants to stop this, it's up to the subclass to do so.
3859 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
3860 self.assertEqual(as_date, date_sc)
3861 self.assertEqual(date_sc, as_date)
3862
3863 # Ditto for datetimes.
3864 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
3865 as_date.day, 0, 0, 0)
3866 self.assertEqual(as_datetime, datetime_sc)
3867 self.assertEqual(datetime_sc, as_datetime)
3868
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04003869 def test_extra_attributes(self):
3870 for x in [date.today(),
3871 time(),
3872 datetime.utcnow(),
3873 timedelta(),
3874 tzinfo(),
3875 timezone(timedelta())]:
3876 with self.assertRaises(AttributeError):
3877 x.abc = 1
3878
3879 def test_check_arg_types(self):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04003880 class Number:
3881 def __init__(self, value):
3882 self.value = value
3883 def __int__(self):
3884 return self.value
3885
3886 for xx in [decimal.Decimal(10),
3887 decimal.Decimal('10.9'),
3888 Number(10)]:
3889 self.assertEqual(datetime(10, 10, 10, 10, 10, 10, 10),
3890 datetime(xx, xx, xx, xx, xx, xx, xx))
3891
3892 with self.assertRaisesRegex(TypeError, '^an integer is required '
3893 '\(got type str\)$'):
3894 datetime(10, 10, '10')
3895
3896 f10 = Number(10.9)
3897 with self.assertRaisesRegex(TypeError, '^__int__ returned non-int '
3898 '\(type float\)$'):
3899 datetime(10, 10, f10)
3900
3901 class Float(float):
3902 pass
3903 s10 = Float(10.9)
3904 with self.assertRaisesRegex(TypeError, '^integer argument expected, '
3905 'got float$'):
3906 datetime(10, 10, s10)
3907
3908 with self.assertRaises(TypeError):
3909 datetime(10., 10, 10)
3910 with self.assertRaises(TypeError):
3911 datetime(10, 10., 10)
3912 with self.assertRaises(TypeError):
3913 datetime(10, 10, 10.)
3914 with self.assertRaises(TypeError):
3915 datetime(10, 10, 10, 10.)
3916 with self.assertRaises(TypeError):
3917 datetime(10, 10, 10, 10, 10.)
3918 with self.assertRaises(TypeError):
3919 datetime(10, 10, 10, 10, 10, 10.)
3920 with self.assertRaises(TypeError):
3921 datetime(10, 10, 10, 10, 10, 10, 10.)
3922
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003923if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -05003924 unittest.main()