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