blob: 42944a2d0698a7d033ee3568f5c7eb3dd300cb08 [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"""
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04005from test.support import is_resource_enabled
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04006
7import itertools
8import bisect
Alexander Belopolskycf86e362010-07-23 19:25:47 +00009
Serhiy Storchakae28209f2015-11-16 11:12:58 +020010import copy
Antoine Pitrou392f4132014-10-03 11:25:30 +020011import decimal
Alexander Belopolskycf86e362010-07-23 19:25:47 +000012import sys
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -040013import os
Alexander Belopolskycf86e362010-07-23 19:25:47 +000014import pickle
Raymond Hettinger5a2146a2014-07-25 14:59:48 -070015import random
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -040016import struct
Alexander Belopolskycf86e362010-07-23 19:25:47 +000017import unittest
18
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -040019from array import array
20
Alexander Belopolskycf86e362010-07-23 19:25:47 +000021from operator import lt, le, gt, ge, eq, ne, truediv, floordiv, mod
22
23from test import support
24
25import datetime as datetime_module
26from datetime import MINYEAR, MAXYEAR
27from datetime import timedelta
28from datetime import tzinfo
29from datetime import time
30from datetime import timezone
31from datetime import date, datetime
32import time as _time
33
34# Needed by test_datetime
35import _strptime
36#
37
38
39pickle_choices = [(pickle, pickle, proto)
40 for proto in range(pickle.HIGHEST_PROTOCOL + 1)]
41assert len(pickle_choices) == pickle.HIGHEST_PROTOCOL + 1
42
43# An arbitrary collection of objects of non-datetime types, for testing
44# mixed-type comparisons.
45OTHERSTUFF = (10, 34.5, "abc", {}, [], ())
46
47
48# XXX Copied from test_float.
49INF = float("inf")
50NAN = float("nan")
51
Alexander Belopolskycf86e362010-07-23 19:25:47 +000052
53#############################################################################
54# module tests
55
56class TestModule(unittest.TestCase):
57
58 def test_constants(self):
59 datetime = datetime_module
60 self.assertEqual(datetime.MINYEAR, 1)
61 self.assertEqual(datetime.MAXYEAR, 9999)
62
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -040063 def test_name_cleanup(self):
64 if '_Fast' not in str(self):
65 return
66 datetime = datetime_module
67 names = set(name for name in dir(datetime)
68 if not name.startswith('__') and not name.endswith('__'))
69 allowed = set(['MAXYEAR', 'MINYEAR', 'date', 'datetime',
70 'datetime_CAPI', 'time', 'timedelta', 'timezone',
71 'tzinfo'])
72 self.assertEqual(names - allowed, set([]))
73
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -050074 def test_divide_and_round(self):
75 if '_Fast' in str(self):
76 return
77 dar = datetime_module._divide_and_round
78
79 self.assertEqual(dar(-10, -3), 3)
80 self.assertEqual(dar(5, -2), -2)
81
82 # four cases: (2 signs of a) x (2 signs of b)
83 self.assertEqual(dar(7, 3), 2)
84 self.assertEqual(dar(-7, 3), -2)
85 self.assertEqual(dar(7, -3), -2)
86 self.assertEqual(dar(-7, -3), 2)
87
88 # ties to even - eight cases:
89 # (2 signs of a) x (2 signs of b) x (even / odd quotient)
90 self.assertEqual(dar(10, 4), 2)
91 self.assertEqual(dar(-10, 4), -2)
92 self.assertEqual(dar(10, -4), -2)
93 self.assertEqual(dar(-10, -4), 2)
94
95 self.assertEqual(dar(6, 4), 2)
96 self.assertEqual(dar(-6, 4), -2)
97 self.assertEqual(dar(6, -4), -2)
98 self.assertEqual(dar(-6, -4), 2)
99
100
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000101#############################################################################
102# tzinfo tests
103
104class FixedOffset(tzinfo):
105
106 def __init__(self, offset, name, dstoffset=42):
107 if isinstance(offset, int):
108 offset = timedelta(minutes=offset)
109 if isinstance(dstoffset, int):
110 dstoffset = timedelta(minutes=dstoffset)
111 self.__offset = offset
112 self.__name = name
113 self.__dstoffset = dstoffset
114 def __repr__(self):
115 return self.__name.lower()
116 def utcoffset(self, dt):
117 return self.__offset
118 def tzname(self, dt):
119 return self.__name
120 def dst(self, dt):
121 return self.__dstoffset
122
123class PicklableFixedOffset(FixedOffset):
124
125 def __init__(self, offset=None, name=None, dstoffset=None):
126 FixedOffset.__init__(self, offset, name, dstoffset)
127
Berker Peksage3385b42016-03-19 13:16:32 +0200128 def __getstate__(self):
129 return self.__dict__
130
Raymond Hettinger5a2146a2014-07-25 14:59:48 -0700131class _TZInfo(tzinfo):
132 def utcoffset(self, datetime_module):
133 return random.random()
134
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000135class TestTZInfo(unittest.TestCase):
136
Raymond Hettinger5a2146a2014-07-25 14:59:48 -0700137 def test_refcnt_crash_bug_22044(self):
138 tz1 = _TZInfo()
139 dt1 = datetime(2014, 7, 21, 11, 32, 3, 0, tz1)
140 with self.assertRaises(TypeError):
141 dt1.utcoffset()
142
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000143 def test_non_abstractness(self):
144 # In order to allow subclasses to get pickled, the C implementation
145 # wasn't able to get away with having __init__ raise
146 # NotImplementedError.
147 useless = tzinfo()
148 dt = datetime.max
149 self.assertRaises(NotImplementedError, useless.tzname, dt)
150 self.assertRaises(NotImplementedError, useless.utcoffset, dt)
151 self.assertRaises(NotImplementedError, useless.dst, dt)
152
153 def test_subclass_must_override(self):
154 class NotEnough(tzinfo):
155 def __init__(self, offset, name):
156 self.__offset = offset
157 self.__name = name
158 self.assertTrue(issubclass(NotEnough, tzinfo))
159 ne = NotEnough(3, "NotByALongShot")
160 self.assertIsInstance(ne, tzinfo)
161
162 dt = datetime.now()
163 self.assertRaises(NotImplementedError, ne.tzname, dt)
164 self.assertRaises(NotImplementedError, ne.utcoffset, dt)
165 self.assertRaises(NotImplementedError, ne.dst, dt)
166
167 def test_normal(self):
168 fo = FixedOffset(3, "Three")
169 self.assertIsInstance(fo, tzinfo)
170 for dt in datetime.now(), None:
171 self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3))
172 self.assertEqual(fo.tzname(dt), "Three")
173 self.assertEqual(fo.dst(dt), timedelta(minutes=42))
174
175 def test_pickling_base(self):
176 # There's no point to pickling tzinfo objects on their own (they
177 # carry no data), but they need to be picklable anyway else
178 # concrete subclasses can't be pickled.
179 orig = tzinfo.__new__(tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200180 self.assertIs(type(orig), tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000181 for pickler, unpickler, proto in pickle_choices:
182 green = pickler.dumps(orig, proto)
183 derived = unpickler.loads(green)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200184 self.assertIs(type(derived), tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000185
186 def test_pickling_subclass(self):
187 # Make sure we can pickle/unpickle an instance of a subclass.
188 offset = timedelta(minutes=-300)
189 for otype, args in [
190 (PicklableFixedOffset, (offset, 'cookie')),
191 (timezone, (offset,)),
192 (timezone, (offset, "EST"))]:
193 orig = otype(*args)
194 oname = orig.tzname(None)
195 self.assertIsInstance(orig, tzinfo)
196 self.assertIs(type(orig), otype)
197 self.assertEqual(orig.utcoffset(None), offset)
198 self.assertEqual(orig.tzname(None), oname)
199 for pickler, unpickler, proto in pickle_choices:
200 green = pickler.dumps(orig, proto)
201 derived = unpickler.loads(green)
202 self.assertIsInstance(derived, tzinfo)
203 self.assertIs(type(derived), otype)
204 self.assertEqual(derived.utcoffset(None), offset)
205 self.assertEqual(derived.tzname(None), oname)
206
Alexander Belopolskyc79447b2015-09-27 21:41:55 -0400207 def test_issue23600(self):
208 DSTDIFF = DSTOFFSET = timedelta(hours=1)
209
210 class UKSummerTime(tzinfo):
211 """Simple time zone which pretends to always be in summer time, since
212 that's what shows the failure.
213 """
214
215 def utcoffset(self, dt):
216 return DSTOFFSET
217
218 def dst(self, dt):
219 return DSTDIFF
220
221 def tzname(self, dt):
222 return 'UKSummerTime'
223
224 tz = UKSummerTime()
225 u = datetime(2014, 4, 26, 12, 1, tzinfo=tz)
226 t = tz.fromutc(u)
227 self.assertEqual(t - t.utcoffset(), u)
228
229
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000230class TestTimeZone(unittest.TestCase):
231
232 def setUp(self):
233 self.ACDT = timezone(timedelta(hours=9.5), 'ACDT')
234 self.EST = timezone(-timedelta(hours=5), 'EST')
235 self.DT = datetime(2010, 1, 1)
236
237 def test_str(self):
238 for tz in [self.ACDT, self.EST, timezone.utc,
239 timezone.min, timezone.max]:
240 self.assertEqual(str(tz), tz.tzname(None))
241
242 def test_repr(self):
243 datetime = datetime_module
244 for tz in [self.ACDT, self.EST, timezone.utc,
245 timezone.min, timezone.max]:
246 # test round-trip
247 tzrep = repr(tz)
248 self.assertEqual(tz, eval(tzrep))
249
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000250 def test_class_members(self):
251 limit = timedelta(hours=23, minutes=59)
252 self.assertEqual(timezone.utc.utcoffset(None), ZERO)
253 self.assertEqual(timezone.min.utcoffset(None), -limit)
254 self.assertEqual(timezone.max.utcoffset(None), limit)
255
256
257 def test_constructor(self):
Alexander Belopolsky1bcbaab2010-10-14 17:03:51 +0000258 self.assertIs(timezone.utc, timezone(timedelta(0)))
259 self.assertIsNot(timezone.utc, timezone(timedelta(0), 'UTC'))
260 self.assertEqual(timezone.utc, timezone(timedelta(0), 'UTC'))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000261 # invalid offsets
262 for invalid in [timedelta(microseconds=1), timedelta(1, 1),
263 timedelta(seconds=1), timedelta(1), -timedelta(1)]:
264 self.assertRaises(ValueError, timezone, invalid)
265 self.assertRaises(ValueError, timezone, -invalid)
266
267 with self.assertRaises(TypeError): timezone(None)
268 with self.assertRaises(TypeError): timezone(42)
269 with self.assertRaises(TypeError): timezone(ZERO, None)
270 with self.assertRaises(TypeError): timezone(ZERO, 42)
271 with self.assertRaises(TypeError): timezone(ZERO, 'ABC', 'extra')
272
273 def test_inheritance(self):
274 self.assertIsInstance(timezone.utc, tzinfo)
275 self.assertIsInstance(self.EST, tzinfo)
276
277 def test_utcoffset(self):
278 dummy = self.DT
279 for h in [0, 1.5, 12]:
280 offset = h * HOUR
281 self.assertEqual(offset, timezone(offset).utcoffset(dummy))
282 self.assertEqual(-offset, timezone(-offset).utcoffset(dummy))
283
284 with self.assertRaises(TypeError): self.EST.utcoffset('')
285 with self.assertRaises(TypeError): self.EST.utcoffset(5)
286
287
288 def test_dst(self):
Raymond Hettinger7beae8a2011-01-06 05:34:17 +0000289 self.assertIsNone(timezone.utc.dst(self.DT))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000290
291 with self.assertRaises(TypeError): self.EST.dst('')
292 with self.assertRaises(TypeError): self.EST.dst(5)
293
294 def test_tzname(self):
Alexander Belopolsky7827a5b2015-09-06 13:07:21 -0400295 self.assertEqual('UTC', timezone.utc.tzname(None))
296 self.assertEqual('UTC', timezone(ZERO).tzname(None))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000297 self.assertEqual('UTC-05:00', timezone(-5 * HOUR).tzname(None))
298 self.assertEqual('UTC+09:30', timezone(9.5 * HOUR).tzname(None))
299 self.assertEqual('UTC-00:01', timezone(timedelta(minutes=-1)).tzname(None))
300 self.assertEqual('XYZ', timezone(-5 * HOUR, 'XYZ').tzname(None))
301
302 with self.assertRaises(TypeError): self.EST.tzname('')
303 with self.assertRaises(TypeError): self.EST.tzname(5)
304
305 def test_fromutc(self):
306 with self.assertRaises(ValueError):
307 timezone.utc.fromutc(self.DT)
308 with self.assertRaises(TypeError):
309 timezone.utc.fromutc('not datetime')
310 for tz in [self.EST, self.ACDT, Eastern]:
311 utctime = self.DT.replace(tzinfo=tz)
312 local = tz.fromutc(utctime)
313 self.assertEqual(local - utctime, tz.utcoffset(local))
314 self.assertEqual(local,
315 self.DT.replace(tzinfo=timezone.utc))
316
317 def test_comparison(self):
318 self.assertNotEqual(timezone(ZERO), timezone(HOUR))
319 self.assertEqual(timezone(HOUR), timezone(HOUR))
320 self.assertEqual(timezone(-5 * HOUR), timezone(-5 * HOUR, 'EST'))
321 with self.assertRaises(TypeError): timezone(ZERO) < timezone(ZERO)
322 self.assertIn(timezone(ZERO), {timezone(ZERO)})
Georg Brandl0085a242012-09-22 09:23:12 +0200323 self.assertTrue(timezone(ZERO) != None)
324 self.assertFalse(timezone(ZERO) == None)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000325
326 def test_aware_datetime(self):
327 # test that timezone instances can be used by datetime
328 t = datetime(1, 1, 1)
329 for tz in [timezone.min, timezone.max, timezone.utc]:
330 self.assertEqual(tz.tzname(t),
331 t.replace(tzinfo=tz).tzname())
332 self.assertEqual(tz.utcoffset(t),
333 t.replace(tzinfo=tz).utcoffset())
334 self.assertEqual(tz.dst(t),
335 t.replace(tzinfo=tz).dst())
336
Serhiy Storchakae28209f2015-11-16 11:12:58 +0200337 def test_pickle(self):
338 for tz in self.ACDT, self.EST, timezone.min, timezone.max:
339 for pickler, unpickler, proto in pickle_choices:
340 tz_copy = unpickler.loads(pickler.dumps(tz, proto))
341 self.assertEqual(tz_copy, tz)
342 tz = timezone.utc
343 for pickler, unpickler, proto in pickle_choices:
344 tz_copy = unpickler.loads(pickler.dumps(tz, proto))
345 self.assertIs(tz_copy, tz)
346
347 def test_copy(self):
348 for tz in self.ACDT, self.EST, timezone.min, timezone.max:
349 tz_copy = copy.copy(tz)
350 self.assertEqual(tz_copy, tz)
351 tz = timezone.utc
352 tz_copy = copy.copy(tz)
353 self.assertIs(tz_copy, tz)
354
355 def test_deepcopy(self):
356 for tz in self.ACDT, self.EST, timezone.min, timezone.max:
357 tz_copy = copy.deepcopy(tz)
358 self.assertEqual(tz_copy, tz)
359 tz = timezone.utc
360 tz_copy = copy.deepcopy(tz)
361 self.assertIs(tz_copy, tz)
362
363
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000364#############################################################################
Ezio Melotti85a86292013-08-17 16:57:41 +0300365# Base class for testing a particular aspect of timedelta, time, date and
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000366# datetime comparisons.
367
368class HarmlessMixedComparison:
369 # Test that __eq__ and __ne__ don't complain for mixed-type comparisons.
370
371 # Subclasses must define 'theclass', and theclass(1, 1, 1) must be a
372 # legit constructor.
373
374 def test_harmless_mixed_comparison(self):
375 me = self.theclass(1, 1, 1)
376
377 self.assertFalse(me == ())
378 self.assertTrue(me != ())
379 self.assertFalse(() == me)
380 self.assertTrue(() != me)
381
382 self.assertIn(me, [1, 20, [], me])
383 self.assertIn([], [me, 1, 20, []])
384
385 def test_harmful_mixed_comparison(self):
386 me = self.theclass(1, 1, 1)
387
388 self.assertRaises(TypeError, lambda: me < ())
389 self.assertRaises(TypeError, lambda: me <= ())
390 self.assertRaises(TypeError, lambda: me > ())
391 self.assertRaises(TypeError, lambda: me >= ())
392
393 self.assertRaises(TypeError, lambda: () < me)
394 self.assertRaises(TypeError, lambda: () <= me)
395 self.assertRaises(TypeError, lambda: () > me)
396 self.assertRaises(TypeError, lambda: () >= me)
397
398#############################################################################
399# timedelta tests
400
401class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
402
403 theclass = timedelta
404
405 def test_constructor(self):
406 eq = self.assertEqual
407 td = timedelta
408
409 # Check keyword args to constructor
410 eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
411 milliseconds=0, microseconds=0))
412 eq(td(1), td(days=1))
413 eq(td(0, 1), td(seconds=1))
414 eq(td(0, 0, 1), td(microseconds=1))
415 eq(td(weeks=1), td(days=7))
416 eq(td(days=1), td(hours=24))
417 eq(td(hours=1), td(minutes=60))
418 eq(td(minutes=1), td(seconds=60))
419 eq(td(seconds=1), td(milliseconds=1000))
420 eq(td(milliseconds=1), td(microseconds=1000))
421
422 # Check float args to constructor
423 eq(td(weeks=1.0/7), td(days=1))
424 eq(td(days=1.0/24), td(hours=1))
425 eq(td(hours=1.0/60), td(minutes=1))
426 eq(td(minutes=1.0/60), td(seconds=1))
427 eq(td(seconds=0.001), td(milliseconds=1))
428 eq(td(milliseconds=0.001), td(microseconds=1))
429
430 def test_computations(self):
431 eq = self.assertEqual
432 td = timedelta
433
434 a = td(7) # One week
435 b = td(0, 60) # One minute
436 c = td(0, 0, 1000) # One millisecond
437 eq(a+b+c, td(7, 60, 1000))
438 eq(a-b, td(6, 24*3600 - 60))
439 eq(b.__rsub__(a), td(6, 24*3600 - 60))
440 eq(-a, td(-7))
441 eq(+a, td(7))
442 eq(-b, td(-1, 24*3600 - 60))
443 eq(-c, td(-1, 24*3600 - 1, 999000))
444 eq(abs(a), a)
445 eq(abs(-a), a)
446 eq(td(6, 24*3600), a)
447 eq(td(0, 0, 60*1000000), b)
448 eq(a*10, td(70))
449 eq(a*10, 10*a)
450 eq(a*10, 10*a)
451 eq(b*10, td(0, 600))
452 eq(10*b, td(0, 600))
453 eq(b*10, td(0, 600))
454 eq(c*10, td(0, 0, 10000))
455 eq(10*c, td(0, 0, 10000))
456 eq(c*10, td(0, 0, 10000))
457 eq(a*-1, -a)
458 eq(b*-2, -b-b)
459 eq(c*-2, -c+-c)
460 eq(b*(60*24), (b*60)*24)
461 eq(b*(60*24), (60*b)*24)
462 eq(c*1000, td(0, 1))
463 eq(1000*c, td(0, 1))
464 eq(a//7, td(1))
465 eq(b//10, td(0, 6))
466 eq(c//1000, td(0, 0, 1))
467 eq(a//10, td(0, 7*24*360))
468 eq(a//3600000, td(0, 0, 7*24*1000))
469 eq(a/0.5, td(14))
470 eq(b/0.5, td(0, 120))
471 eq(a/7, td(1))
472 eq(b/10, td(0, 6))
473 eq(c/1000, td(0, 0, 1))
474 eq(a/10, td(0, 7*24*360))
475 eq(a/3600000, td(0, 0, 7*24*1000))
476
477 # Multiplication by float
478 us = td(microseconds=1)
479 eq((3*us) * 0.5, 2*us)
480 eq((5*us) * 0.5, 2*us)
481 eq(0.5 * (3*us), 2*us)
482 eq(0.5 * (5*us), 2*us)
483 eq((-3*us) * 0.5, -2*us)
484 eq((-5*us) * 0.5, -2*us)
485
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -0500486 # Issue #23521
487 eq(td(seconds=1) * 0.123456, td(microseconds=123456))
488 eq(td(seconds=1) * 0.6112295, td(microseconds=611229))
489
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000490 # Division by int and float
491 eq((3*us) / 2, 2*us)
492 eq((5*us) / 2, 2*us)
493 eq((-3*us) / 2.0, -2*us)
494 eq((-5*us) / 2.0, -2*us)
495 eq((3*us) / -2, -2*us)
496 eq((5*us) / -2, -2*us)
497 eq((3*us) / -2.0, -2*us)
498 eq((5*us) / -2.0, -2*us)
499 for i in range(-10, 10):
500 eq((i*us/3)//us, round(i/3))
501 for i in range(-10, 10):
502 eq((i*us/-3)//us, round(i/-3))
503
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -0500504 # Issue #23521
505 eq(td(seconds=1) / (1 / 0.6112295), td(microseconds=611229))
506
Alexander Belopolskyb6f5ec72011-04-05 20:07:38 -0400507 # Issue #11576
508 eq(td(999999999, 86399, 999999) - td(999999999, 86399, 999998),
509 td(0, 0, 1))
510 eq(td(999999999, 1, 1) - td(999999999, 1, 0),
511 td(0, 0, 1))
512
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000513 def test_disallowed_computations(self):
514 a = timedelta(42)
515
516 # Add/sub ints or floats should be illegal
517 for i in 1, 1.0:
518 self.assertRaises(TypeError, lambda: a+i)
519 self.assertRaises(TypeError, lambda: a-i)
520 self.assertRaises(TypeError, lambda: i+a)
521 self.assertRaises(TypeError, lambda: i-a)
522
523 # Division of int by timedelta doesn't make sense.
524 # Division by zero doesn't make sense.
525 zero = 0
526 self.assertRaises(TypeError, lambda: zero // a)
527 self.assertRaises(ZeroDivisionError, lambda: a // zero)
528 self.assertRaises(ZeroDivisionError, lambda: a / zero)
529 self.assertRaises(ZeroDivisionError, lambda: a / 0.0)
530 self.assertRaises(TypeError, lambda: a / '')
531
Eric Smith3ab08ca2010-12-04 15:17:38 +0000532 @support.requires_IEEE_754
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000533 def test_disallowed_special(self):
534 a = timedelta(42)
535 self.assertRaises(ValueError, a.__mul__, NAN)
536 self.assertRaises(ValueError, a.__truediv__, NAN)
537
538 def test_basic_attributes(self):
539 days, seconds, us = 1, 7, 31
540 td = timedelta(days, seconds, us)
541 self.assertEqual(td.days, days)
542 self.assertEqual(td.seconds, seconds)
543 self.assertEqual(td.microseconds, us)
544
545 def test_total_seconds(self):
546 td = timedelta(days=365)
547 self.assertEqual(td.total_seconds(), 31536000.0)
548 for total_seconds in [123456.789012, -123456.789012, 0.123456, 0, 1e6]:
549 td = timedelta(seconds=total_seconds)
550 self.assertEqual(td.total_seconds(), total_seconds)
551 # Issue8644: Test that td.total_seconds() has the same
552 # accuracy as td / timedelta(seconds=1).
553 for ms in [-1, -2, -123]:
554 td = timedelta(microseconds=ms)
555 self.assertEqual(td.total_seconds(), td / timedelta(seconds=1))
556
557 def test_carries(self):
558 t1 = timedelta(days=100,
559 weeks=-7,
560 hours=-24*(100-49),
561 minutes=-3,
562 seconds=12,
563 microseconds=(3*60 - 12) * 1e6 + 1)
564 t2 = timedelta(microseconds=1)
565 self.assertEqual(t1, t2)
566
567 def test_hash_equality(self):
568 t1 = timedelta(days=100,
569 weeks=-7,
570 hours=-24*(100-49),
571 minutes=-3,
572 seconds=12,
573 microseconds=(3*60 - 12) * 1000000)
574 t2 = timedelta()
575 self.assertEqual(hash(t1), hash(t2))
576
577 t1 += timedelta(weeks=7)
578 t2 += timedelta(days=7*7)
579 self.assertEqual(t1, t2)
580 self.assertEqual(hash(t1), hash(t2))
581
582 d = {t1: 1}
583 d[t2] = 2
584 self.assertEqual(len(d), 1)
585 self.assertEqual(d[t1], 2)
586
587 def test_pickling(self):
588 args = 12, 34, 56
589 orig = timedelta(*args)
590 for pickler, unpickler, proto in pickle_choices:
591 green = pickler.dumps(orig, proto)
592 derived = unpickler.loads(green)
593 self.assertEqual(orig, derived)
594
595 def test_compare(self):
596 t1 = timedelta(2, 3, 4)
597 t2 = timedelta(2, 3, 4)
598 self.assertEqual(t1, t2)
599 self.assertTrue(t1 <= t2)
600 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200601 self.assertFalse(t1 != t2)
602 self.assertFalse(t1 < t2)
603 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000604
605 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
606 t2 = timedelta(*args) # this is larger than t1
607 self.assertTrue(t1 < t2)
608 self.assertTrue(t2 > t1)
609 self.assertTrue(t1 <= t2)
610 self.assertTrue(t2 >= t1)
611 self.assertTrue(t1 != t2)
612 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200613 self.assertFalse(t1 == t2)
614 self.assertFalse(t2 == t1)
615 self.assertFalse(t1 > t2)
616 self.assertFalse(t2 < t1)
617 self.assertFalse(t1 >= t2)
618 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000619
620 for badarg in OTHERSTUFF:
621 self.assertEqual(t1 == badarg, False)
622 self.assertEqual(t1 != badarg, True)
623 self.assertEqual(badarg == t1, False)
624 self.assertEqual(badarg != t1, True)
625
626 self.assertRaises(TypeError, lambda: t1 <= badarg)
627 self.assertRaises(TypeError, lambda: t1 < badarg)
628 self.assertRaises(TypeError, lambda: t1 > badarg)
629 self.assertRaises(TypeError, lambda: t1 >= badarg)
630 self.assertRaises(TypeError, lambda: badarg <= t1)
631 self.assertRaises(TypeError, lambda: badarg < t1)
632 self.assertRaises(TypeError, lambda: badarg > t1)
633 self.assertRaises(TypeError, lambda: badarg >= t1)
634
635 def test_str(self):
636 td = timedelta
637 eq = self.assertEqual
638
639 eq(str(td(1)), "1 day, 0:00:00")
640 eq(str(td(-1)), "-1 day, 0:00:00")
641 eq(str(td(2)), "2 days, 0:00:00")
642 eq(str(td(-2)), "-2 days, 0:00:00")
643
644 eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
645 eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
646 eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
647 "-210 days, 23:12:34")
648
649 eq(str(td(milliseconds=1)), "0:00:00.001000")
650 eq(str(td(microseconds=3)), "0:00:00.000003")
651
652 eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
653 microseconds=999999)),
654 "999999999 days, 23:59:59.999999")
655
656 def test_repr(self):
657 name = 'datetime.' + self.theclass.__name__
658 self.assertEqual(repr(self.theclass(1)),
659 "%s(1)" % name)
660 self.assertEqual(repr(self.theclass(10, 2)),
661 "%s(10, 2)" % name)
662 self.assertEqual(repr(self.theclass(-10, 2, 400000)),
663 "%s(-10, 2, 400000)" % name)
664
665 def test_roundtrip(self):
666 for td in (timedelta(days=999999999, hours=23, minutes=59,
667 seconds=59, microseconds=999999),
668 timedelta(days=-999999999),
669 timedelta(days=-999999999, seconds=1),
670 timedelta(days=1, seconds=2, microseconds=3)):
671
672 # Verify td -> string -> td identity.
673 s = repr(td)
674 self.assertTrue(s.startswith('datetime.'))
675 s = s[9:]
676 td2 = eval(s)
677 self.assertEqual(td, td2)
678
679 # Verify identity via reconstructing from pieces.
680 td2 = timedelta(td.days, td.seconds, td.microseconds)
681 self.assertEqual(td, td2)
682
683 def test_resolution_info(self):
684 self.assertIsInstance(timedelta.min, timedelta)
685 self.assertIsInstance(timedelta.max, timedelta)
686 self.assertIsInstance(timedelta.resolution, timedelta)
687 self.assertTrue(timedelta.max > timedelta.min)
688 self.assertEqual(timedelta.min, timedelta(-999999999))
689 self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
690 self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
691
692 def test_overflow(self):
693 tiny = timedelta.resolution
694
695 td = timedelta.min + tiny
696 td -= tiny # no problem
697 self.assertRaises(OverflowError, td.__sub__, tiny)
698 self.assertRaises(OverflowError, td.__add__, -tiny)
699
700 td = timedelta.max - tiny
701 td += tiny # no problem
702 self.assertRaises(OverflowError, td.__add__, tiny)
703 self.assertRaises(OverflowError, td.__sub__, -tiny)
704
705 self.assertRaises(OverflowError, lambda: -timedelta.max)
706
707 day = timedelta(1)
708 self.assertRaises(OverflowError, day.__mul__, 10**9)
709 self.assertRaises(OverflowError, day.__mul__, 1e9)
710 self.assertRaises(OverflowError, day.__truediv__, 1e-20)
711 self.assertRaises(OverflowError, day.__truediv__, 1e-10)
712 self.assertRaises(OverflowError, day.__truediv__, 9e-10)
713
Eric Smith3ab08ca2010-12-04 15:17:38 +0000714 @support.requires_IEEE_754
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000715 def _test_overflow_special(self):
716 day = timedelta(1)
717 self.assertRaises(OverflowError, day.__mul__, INF)
718 self.assertRaises(OverflowError, day.__mul__, -INF)
719
720 def test_microsecond_rounding(self):
721 td = timedelta
722 eq = self.assertEqual
723
724 # Single-field rounding.
725 eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
726 eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
Victor Stinner69cc4872015-09-08 23:58:54 +0200727 eq(td(milliseconds=0.5/1000), td(microseconds=0))
728 eq(td(milliseconds=-0.5/1000), td(microseconds=-0))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000729 eq(td(milliseconds=0.6/1000), td(microseconds=1))
730 eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
Victor Stinner69cc4872015-09-08 23:58:54 +0200731 eq(td(milliseconds=1.5/1000), td(microseconds=2))
732 eq(td(milliseconds=-1.5/1000), td(microseconds=-2))
733 eq(td(seconds=0.5/10**6), td(microseconds=0))
734 eq(td(seconds=-0.5/10**6), td(microseconds=-0))
735 eq(td(seconds=1/2**7), td(microseconds=7812))
736 eq(td(seconds=-1/2**7), td(microseconds=-7812))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000737
738 # Rounding due to contributions from more than one field.
739 us_per_hour = 3600e6
740 us_per_day = us_per_hour * 24
741 eq(td(days=.4/us_per_day), td(0))
742 eq(td(hours=.2/us_per_hour), td(0))
Victor Stinnercd5d7652015-09-09 01:09:21 +0200743 eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000744
745 eq(td(days=-.4/us_per_day), td(0))
746 eq(td(hours=-.2/us_per_hour), td(0))
747 eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
748
Victor Stinner69cc4872015-09-08 23:58:54 +0200749 # Test for a patch in Issue 8860
750 eq(td(microseconds=0.5), 0.5*td(microseconds=1.0))
751 eq(td(microseconds=0.5)//td.resolution, 0.5*td.resolution//td.resolution)
752
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000753 def test_massive_normalization(self):
754 td = timedelta(microseconds=-1)
755 self.assertEqual((td.days, td.seconds, td.microseconds),
756 (-1, 24*3600-1, 999999))
757
758 def test_bool(self):
759 self.assertTrue(timedelta(1))
760 self.assertTrue(timedelta(0, 1))
761 self.assertTrue(timedelta(0, 0, 1))
762 self.assertTrue(timedelta(microseconds=1))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200763 self.assertFalse(timedelta(0))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000764
765 def test_subclass_timedelta(self):
766
767 class T(timedelta):
768 @staticmethod
769 def from_td(td):
770 return T(td.days, td.seconds, td.microseconds)
771
772 def as_hours(self):
773 sum = (self.days * 24 +
774 self.seconds / 3600.0 +
775 self.microseconds / 3600e6)
776 return round(sum)
777
778 t1 = T(days=1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200779 self.assertIs(type(t1), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000780 self.assertEqual(t1.as_hours(), 24)
781
782 t2 = T(days=-1, seconds=-3600)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200783 self.assertIs(type(t2), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000784 self.assertEqual(t2.as_hours(), -25)
785
786 t3 = t1 + t2
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200787 self.assertIs(type(t3), timedelta)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000788 t4 = T.from_td(t3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200789 self.assertIs(type(t4), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000790 self.assertEqual(t3.days, t4.days)
791 self.assertEqual(t3.seconds, t4.seconds)
792 self.assertEqual(t3.microseconds, t4.microseconds)
793 self.assertEqual(str(t3), str(t4))
794 self.assertEqual(t4.as_hours(), -1)
795
796 def test_division(self):
797 t = timedelta(hours=1, minutes=24, seconds=19)
798 second = timedelta(seconds=1)
799 self.assertEqual(t / second, 5059.0)
800 self.assertEqual(t // second, 5059)
801
802 t = timedelta(minutes=2, seconds=30)
803 minute = timedelta(minutes=1)
804 self.assertEqual(t / minute, 2.5)
805 self.assertEqual(t // minute, 2)
806
807 zerotd = timedelta(0)
808 self.assertRaises(ZeroDivisionError, truediv, t, zerotd)
809 self.assertRaises(ZeroDivisionError, floordiv, t, zerotd)
810
811 # self.assertRaises(TypeError, truediv, t, 2)
812 # note: floor division of a timedelta by an integer *is*
813 # currently permitted.
814
815 def test_remainder(self):
816 t = timedelta(minutes=2, seconds=30)
817 minute = timedelta(minutes=1)
818 r = t % minute
819 self.assertEqual(r, timedelta(seconds=30))
820
821 t = timedelta(minutes=-2, seconds=30)
822 r = t % minute
823 self.assertEqual(r, timedelta(seconds=30))
824
825 zerotd = timedelta(0)
826 self.assertRaises(ZeroDivisionError, mod, t, zerotd)
827
828 self.assertRaises(TypeError, mod, t, 10)
829
830 def test_divmod(self):
831 t = timedelta(minutes=2, seconds=30)
832 minute = timedelta(minutes=1)
833 q, r = divmod(t, minute)
834 self.assertEqual(q, 2)
835 self.assertEqual(r, timedelta(seconds=30))
836
837 t = timedelta(minutes=-2, seconds=30)
838 q, r = divmod(t, minute)
839 self.assertEqual(q, -2)
840 self.assertEqual(r, timedelta(seconds=30))
841
842 zerotd = timedelta(0)
843 self.assertRaises(ZeroDivisionError, divmod, t, zerotd)
844
845 self.assertRaises(TypeError, divmod, t, 10)
846
847
848#############################################################################
849# date tests
850
851class TestDateOnly(unittest.TestCase):
852 # Tests here won't pass if also run on datetime objects, so don't
853 # subclass this to test datetimes too.
854
855 def test_delta_non_days_ignored(self):
856 dt = date(2000, 1, 2)
857 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
858 microseconds=5)
859 days = timedelta(delta.days)
860 self.assertEqual(days, timedelta(1))
861
862 dt2 = dt + delta
863 self.assertEqual(dt2, dt + days)
864
865 dt2 = delta + dt
866 self.assertEqual(dt2, dt + days)
867
868 dt2 = dt - delta
869 self.assertEqual(dt2, dt - days)
870
871 delta = -delta
872 days = timedelta(delta.days)
873 self.assertEqual(days, timedelta(-2))
874
875 dt2 = dt + delta
876 self.assertEqual(dt2, dt + days)
877
878 dt2 = delta + dt
879 self.assertEqual(dt2, dt + days)
880
881 dt2 = dt - delta
882 self.assertEqual(dt2, dt - days)
883
884class SubclassDate(date):
885 sub_var = 1
886
887class TestDate(HarmlessMixedComparison, unittest.TestCase):
888 # Tests here should pass for both dates and datetimes, except for a
889 # few tests that TestDateTime overrides.
890
891 theclass = date
892
893 def test_basic_attributes(self):
894 dt = self.theclass(2002, 3, 1)
895 self.assertEqual(dt.year, 2002)
896 self.assertEqual(dt.month, 3)
897 self.assertEqual(dt.day, 1)
898
899 def test_roundtrip(self):
900 for dt in (self.theclass(1, 2, 3),
901 self.theclass.today()):
902 # Verify dt -> string -> date identity.
903 s = repr(dt)
904 self.assertTrue(s.startswith('datetime.'))
905 s = s[9:]
906 dt2 = eval(s)
907 self.assertEqual(dt, dt2)
908
909 # Verify identity via reconstructing from pieces.
910 dt2 = self.theclass(dt.year, dt.month, dt.day)
911 self.assertEqual(dt, dt2)
912
913 def test_ordinal_conversions(self):
914 # Check some fixed values.
915 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
916 (1, 12, 31, 365),
917 (2, 1, 1, 366),
918 # first example from "Calendrical Calculations"
919 (1945, 11, 12, 710347)]:
920 d = self.theclass(y, m, d)
921 self.assertEqual(n, d.toordinal())
922 fromord = self.theclass.fromordinal(n)
923 self.assertEqual(d, fromord)
924 if hasattr(fromord, "hour"):
925 # if we're checking something fancier than a date, verify
926 # the extra fields have been zeroed out
927 self.assertEqual(fromord.hour, 0)
928 self.assertEqual(fromord.minute, 0)
929 self.assertEqual(fromord.second, 0)
930 self.assertEqual(fromord.microsecond, 0)
931
932 # Check first and last days of year spottily across the whole
933 # range of years supported.
934 for year in range(MINYEAR, MAXYEAR+1, 7):
935 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
936 d = self.theclass(year, 1, 1)
937 n = d.toordinal()
938 d2 = self.theclass.fromordinal(n)
939 self.assertEqual(d, d2)
940 # Verify that moving back a day gets to the end of year-1.
941 if year > 1:
942 d = self.theclass.fromordinal(n-1)
943 d2 = self.theclass(year-1, 12, 31)
944 self.assertEqual(d, d2)
945 self.assertEqual(d2.toordinal(), n-1)
946
947 # Test every day in a leap-year and a non-leap year.
948 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
949 for year, isleap in (2000, True), (2002, False):
950 n = self.theclass(year, 1, 1).toordinal()
951 for month, maxday in zip(range(1, 13), dim):
952 if month == 2 and isleap:
953 maxday += 1
954 for day in range(1, maxday+1):
955 d = self.theclass(year, month, day)
956 self.assertEqual(d.toordinal(), n)
957 self.assertEqual(d, self.theclass.fromordinal(n))
958 n += 1
959
960 def test_extreme_ordinals(self):
961 a = self.theclass.min
962 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
963 aord = a.toordinal()
964 b = a.fromordinal(aord)
965 self.assertEqual(a, b)
966
967 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
968
969 b = a + timedelta(days=1)
970 self.assertEqual(b.toordinal(), aord + 1)
971 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
972
973 a = self.theclass.max
974 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
975 aord = a.toordinal()
976 b = a.fromordinal(aord)
977 self.assertEqual(a, b)
978
979 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
980
981 b = a - timedelta(days=1)
982 self.assertEqual(b.toordinal(), aord - 1)
983 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
984
985 def test_bad_constructor_arguments(self):
986 # bad years
987 self.theclass(MINYEAR, 1, 1) # no exception
988 self.theclass(MAXYEAR, 1, 1) # no exception
989 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
990 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
991 # bad months
992 self.theclass(2000, 1, 1) # no exception
993 self.theclass(2000, 12, 1) # no exception
994 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
995 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
996 # bad days
997 self.theclass(2000, 2, 29) # no exception
998 self.theclass(2004, 2, 29) # no exception
999 self.theclass(2400, 2, 29) # no exception
1000 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1001 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1002 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1003 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1004 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1005 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1006
1007 def test_hash_equality(self):
1008 d = self.theclass(2000, 12, 31)
1009 # same thing
1010 e = self.theclass(2000, 12, 31)
1011 self.assertEqual(d, e)
1012 self.assertEqual(hash(d), hash(e))
1013
1014 dic = {d: 1}
1015 dic[e] = 2
1016 self.assertEqual(len(dic), 1)
1017 self.assertEqual(dic[d], 2)
1018 self.assertEqual(dic[e], 2)
1019
1020 d = self.theclass(2001, 1, 1)
1021 # same thing
1022 e = self.theclass(2001, 1, 1)
1023 self.assertEqual(d, e)
1024 self.assertEqual(hash(d), hash(e))
1025
1026 dic = {d: 1}
1027 dic[e] = 2
1028 self.assertEqual(len(dic), 1)
1029 self.assertEqual(dic[d], 2)
1030 self.assertEqual(dic[e], 2)
1031
1032 def test_computations(self):
1033 a = self.theclass(2002, 1, 31)
1034 b = self.theclass(1956, 1, 31)
1035 c = self.theclass(2001,2,1)
1036
1037 diff = a-b
1038 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1039 self.assertEqual(diff.seconds, 0)
1040 self.assertEqual(diff.microseconds, 0)
1041
1042 day = timedelta(1)
1043 week = timedelta(7)
1044 a = self.theclass(2002, 3, 2)
1045 self.assertEqual(a + day, self.theclass(2002, 3, 3))
1046 self.assertEqual(day + a, self.theclass(2002, 3, 3))
1047 self.assertEqual(a - day, self.theclass(2002, 3, 1))
1048 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
1049 self.assertEqual(a + week, self.theclass(2002, 3, 9))
1050 self.assertEqual(a - week, self.theclass(2002, 2, 23))
1051 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
1052 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
1053 self.assertEqual((a + week) - a, week)
1054 self.assertEqual((a + day) - a, day)
1055 self.assertEqual((a - week) - a, -week)
1056 self.assertEqual((a - day) - a, -day)
1057 self.assertEqual(a - (a + week), -week)
1058 self.assertEqual(a - (a + day), -day)
1059 self.assertEqual(a - (a - week), week)
1060 self.assertEqual(a - (a - day), day)
1061 self.assertEqual(c - (c - day), day)
1062
1063 # Add/sub ints or floats should be illegal
1064 for i in 1, 1.0:
1065 self.assertRaises(TypeError, lambda: a+i)
1066 self.assertRaises(TypeError, lambda: a-i)
1067 self.assertRaises(TypeError, lambda: i+a)
1068 self.assertRaises(TypeError, lambda: i-a)
1069
1070 # delta - date is senseless.
1071 self.assertRaises(TypeError, lambda: day - a)
1072 # mixing date and (delta or date) via * or // is senseless
1073 self.assertRaises(TypeError, lambda: day * a)
1074 self.assertRaises(TypeError, lambda: a * day)
1075 self.assertRaises(TypeError, lambda: day // a)
1076 self.assertRaises(TypeError, lambda: a // day)
1077 self.assertRaises(TypeError, lambda: a * a)
1078 self.assertRaises(TypeError, lambda: a // a)
1079 # date + date is senseless
1080 self.assertRaises(TypeError, lambda: a + a)
1081
1082 def test_overflow(self):
1083 tiny = self.theclass.resolution
1084
1085 for delta in [tiny, timedelta(1), timedelta(2)]:
1086 dt = self.theclass.min + delta
1087 dt -= delta # no problem
1088 self.assertRaises(OverflowError, dt.__sub__, delta)
1089 self.assertRaises(OverflowError, dt.__add__, -delta)
1090
1091 dt = self.theclass.max - delta
1092 dt += delta # no problem
1093 self.assertRaises(OverflowError, dt.__add__, delta)
1094 self.assertRaises(OverflowError, dt.__sub__, -delta)
1095
1096 def test_fromtimestamp(self):
1097 import time
1098
1099 # Try an arbitrary fixed value.
1100 year, month, day = 1999, 9, 19
1101 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
1102 d = self.theclass.fromtimestamp(ts)
1103 self.assertEqual(d.year, year)
1104 self.assertEqual(d.month, month)
1105 self.assertEqual(d.day, day)
1106
1107 def test_insane_fromtimestamp(self):
1108 # It's possible that some platform maps time_t to double,
1109 # and that this test will fail there. This test should
1110 # exempt such platforms (provided they return reasonable
1111 # results!).
1112 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001113 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001114 insane)
1115
1116 def test_today(self):
1117 import time
1118
1119 # We claim that today() is like fromtimestamp(time.time()), so
1120 # prove it.
1121 for dummy in range(3):
1122 today = self.theclass.today()
1123 ts = time.time()
1124 todayagain = self.theclass.fromtimestamp(ts)
1125 if today == todayagain:
1126 break
1127 # There are several legit reasons that could fail:
1128 # 1. It recently became midnight, between the today() and the
1129 # time() calls.
1130 # 2. The platform time() has such fine resolution that we'll
1131 # never get the same value twice.
1132 # 3. The platform time() has poor resolution, and we just
1133 # happened to call today() right before a resolution quantum
1134 # boundary.
1135 # 4. The system clock got fiddled between calls.
1136 # In any case, wait a little while and try again.
1137 time.sleep(0.1)
1138
1139 # It worked or it didn't. If it didn't, assume it's reason #2, and
1140 # let the test pass if they're within half a second of each other.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001141 if today != todayagain:
1142 self.assertAlmostEqual(todayagain, today,
1143 delta=timedelta(seconds=0.5))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001144
1145 def test_weekday(self):
1146 for i in range(7):
1147 # March 4, 2002 is a Monday
1148 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
1149 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
1150 # January 2, 1956 is a Monday
1151 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
1152 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
1153
1154 def test_isocalendar(self):
1155 # Check examples from
1156 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1157 for i in range(7):
1158 d = self.theclass(2003, 12, 22+i)
1159 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
1160 d = self.theclass(2003, 12, 29) + timedelta(i)
1161 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
1162 d = self.theclass(2004, 1, 5+i)
1163 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
1164 d = self.theclass(2009, 12, 21+i)
1165 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
1166 d = self.theclass(2009, 12, 28) + timedelta(i)
1167 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
1168 d = self.theclass(2010, 1, 4+i)
1169 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
1170
1171 def test_iso_long_years(self):
1172 # Calculate long ISO years and compare to table from
1173 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1174 ISO_LONG_YEARS_TABLE = """
1175 4 32 60 88
1176 9 37 65 93
1177 15 43 71 99
1178 20 48 76
1179 26 54 82
1180
1181 105 133 161 189
1182 111 139 167 195
1183 116 144 172
1184 122 150 178
1185 128 156 184
1186
1187 201 229 257 285
1188 207 235 263 291
1189 212 240 268 296
1190 218 246 274
1191 224 252 280
1192
1193 303 331 359 387
1194 308 336 364 392
1195 314 342 370 398
1196 320 348 376
1197 325 353 381
1198 """
1199 iso_long_years = sorted(map(int, ISO_LONG_YEARS_TABLE.split()))
1200 L = []
1201 for i in range(400):
1202 d = self.theclass(2000+i, 12, 31)
1203 d1 = self.theclass(1600+i, 12, 31)
1204 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
1205 if d.isocalendar()[1] == 53:
1206 L.append(i)
1207 self.assertEqual(L, iso_long_years)
1208
1209 def test_isoformat(self):
1210 t = self.theclass(2, 3, 2)
1211 self.assertEqual(t.isoformat(), "0002-03-02")
1212
1213 def test_ctime(self):
1214 t = self.theclass(2002, 3, 2)
1215 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
1216
1217 def test_strftime(self):
1218 t = self.theclass(2005, 3, 2)
1219 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
1220 self.assertEqual(t.strftime(""), "") # SF bug #761337
1221 self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784
1222
1223 self.assertRaises(TypeError, t.strftime) # needs an arg
1224 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
1225 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
1226
1227 # test that unicode input is allowed (issue 2782)
1228 self.assertEqual(t.strftime("%m"), "03")
1229
1230 # A naive object replaces %z and %Z w/ empty strings.
1231 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1232
1233 #make sure that invalid format specifiers are handled correctly
1234 #self.assertRaises(ValueError, t.strftime, "%e")
1235 #self.assertRaises(ValueError, t.strftime, "%")
1236 #self.assertRaises(ValueError, t.strftime, "%#")
1237
1238 #oh well, some systems just ignore those invalid ones.
Martin Panter46f50722016-05-26 05:35:26 +00001239 #at least, exercise them to make sure that no crashes
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001240 #are generated
1241 for f in ["%e", "%", "%#"]:
1242 try:
1243 t.strftime(f)
1244 except ValueError:
1245 pass
1246
1247 #check that this standard extension works
1248 t.strftime("%f")
1249
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001250 def test_format(self):
1251 dt = self.theclass(2007, 9, 10)
1252 self.assertEqual(dt.__format__(''), str(dt))
1253
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02001254 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001255 dt.__format__(123)
1256
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001257 # check that a derived class's __str__() gets called
1258 class A(self.theclass):
1259 def __str__(self):
1260 return 'A'
1261 a = A(2007, 9, 10)
1262 self.assertEqual(a.__format__(''), 'A')
1263
1264 # check that a derived class's strftime gets called
1265 class B(self.theclass):
1266 def strftime(self, format_spec):
1267 return 'B'
1268 b = B(2007, 9, 10)
1269 self.assertEqual(b.__format__(''), str(dt))
1270
1271 for fmt in ["m:%m d:%d y:%y",
1272 "m:%m d:%d y:%y H:%H M:%M S:%S",
1273 "%z %Z",
1274 ]:
1275 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1276 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1277 self.assertEqual(b.__format__(fmt), 'B')
1278
1279 def test_resolution_info(self):
1280 # XXX: Should min and max respect subclassing?
1281 if issubclass(self.theclass, datetime):
1282 expected_class = datetime
1283 else:
1284 expected_class = date
1285 self.assertIsInstance(self.theclass.min, expected_class)
1286 self.assertIsInstance(self.theclass.max, expected_class)
1287 self.assertIsInstance(self.theclass.resolution, timedelta)
1288 self.assertTrue(self.theclass.max > self.theclass.min)
1289
1290 def test_extreme_timedelta(self):
1291 big = self.theclass.max - self.theclass.min
1292 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
1293 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
1294 # n == 315537897599999999 ~= 2**58.13
1295 justasbig = timedelta(0, 0, n)
1296 self.assertEqual(big, justasbig)
1297 self.assertEqual(self.theclass.min + big, self.theclass.max)
1298 self.assertEqual(self.theclass.max - big, self.theclass.min)
1299
1300 def test_timetuple(self):
1301 for i in range(7):
1302 # January 2, 1956 is a Monday (0)
1303 d = self.theclass(1956, 1, 2+i)
1304 t = d.timetuple()
1305 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
1306 # February 1, 1956 is a Wednesday (2)
1307 d = self.theclass(1956, 2, 1+i)
1308 t = d.timetuple()
1309 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
1310 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
1311 # of the year.
1312 d = self.theclass(1956, 3, 1+i)
1313 t = d.timetuple()
1314 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
1315 self.assertEqual(t.tm_year, 1956)
1316 self.assertEqual(t.tm_mon, 3)
1317 self.assertEqual(t.tm_mday, 1+i)
1318 self.assertEqual(t.tm_hour, 0)
1319 self.assertEqual(t.tm_min, 0)
1320 self.assertEqual(t.tm_sec, 0)
1321 self.assertEqual(t.tm_wday, (3+i)%7)
1322 self.assertEqual(t.tm_yday, 61+i)
1323 self.assertEqual(t.tm_isdst, -1)
1324
1325 def test_pickling(self):
1326 args = 6, 7, 23
1327 orig = self.theclass(*args)
1328 for pickler, unpickler, proto in pickle_choices:
1329 green = pickler.dumps(orig, proto)
1330 derived = unpickler.loads(green)
1331 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02001332 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001333
1334 def test_compare(self):
1335 t1 = self.theclass(2, 3, 4)
1336 t2 = self.theclass(2, 3, 4)
1337 self.assertEqual(t1, t2)
1338 self.assertTrue(t1 <= t2)
1339 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001340 self.assertFalse(t1 != t2)
1341 self.assertFalse(t1 < t2)
1342 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001343
1344 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
1345 t2 = self.theclass(*args) # this is larger than t1
1346 self.assertTrue(t1 < t2)
1347 self.assertTrue(t2 > t1)
1348 self.assertTrue(t1 <= t2)
1349 self.assertTrue(t2 >= t1)
1350 self.assertTrue(t1 != t2)
1351 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001352 self.assertFalse(t1 == t2)
1353 self.assertFalse(t2 == t1)
1354 self.assertFalse(t1 > t2)
1355 self.assertFalse(t2 < t1)
1356 self.assertFalse(t1 >= t2)
1357 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001358
1359 for badarg in OTHERSTUFF:
1360 self.assertEqual(t1 == badarg, False)
1361 self.assertEqual(t1 != badarg, True)
1362 self.assertEqual(badarg == t1, False)
1363 self.assertEqual(badarg != t1, True)
1364
1365 self.assertRaises(TypeError, lambda: t1 < badarg)
1366 self.assertRaises(TypeError, lambda: t1 > badarg)
1367 self.assertRaises(TypeError, lambda: t1 >= badarg)
1368 self.assertRaises(TypeError, lambda: badarg <= t1)
1369 self.assertRaises(TypeError, lambda: badarg < t1)
1370 self.assertRaises(TypeError, lambda: badarg > t1)
1371 self.assertRaises(TypeError, lambda: badarg >= t1)
1372
1373 def test_mixed_compare(self):
1374 our = self.theclass(2000, 4, 5)
1375
1376 # Our class can be compared for equality to other classes
1377 self.assertEqual(our == 1, False)
1378 self.assertEqual(1 == our, False)
1379 self.assertEqual(our != 1, True)
1380 self.assertEqual(1 != our, True)
1381
1382 # But the ordering is undefined
1383 self.assertRaises(TypeError, lambda: our < 1)
1384 self.assertRaises(TypeError, lambda: 1 < our)
1385
1386 # Repeat those tests with a different class
1387
1388 class SomeClass:
1389 pass
1390
1391 their = SomeClass()
1392 self.assertEqual(our == their, False)
1393 self.assertEqual(their == our, False)
1394 self.assertEqual(our != their, True)
1395 self.assertEqual(their != our, True)
1396 self.assertRaises(TypeError, lambda: our < their)
1397 self.assertRaises(TypeError, lambda: their < our)
1398
1399 # However, if the other class explicitly defines ordering
1400 # relative to our class, it is allowed to do so
1401
1402 class LargerThanAnything:
1403 def __lt__(self, other):
1404 return False
1405 def __le__(self, other):
1406 return isinstance(other, LargerThanAnything)
1407 def __eq__(self, other):
1408 return isinstance(other, LargerThanAnything)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001409 def __gt__(self, other):
1410 return not isinstance(other, LargerThanAnything)
1411 def __ge__(self, other):
1412 return True
1413
1414 their = LargerThanAnything()
1415 self.assertEqual(our == their, False)
1416 self.assertEqual(their == our, False)
1417 self.assertEqual(our != their, True)
1418 self.assertEqual(their != our, True)
1419 self.assertEqual(our < their, True)
1420 self.assertEqual(their < our, False)
1421
1422 def test_bool(self):
1423 # All dates are considered true.
1424 self.assertTrue(self.theclass.min)
1425 self.assertTrue(self.theclass.max)
1426
Alexander Belopolsky89da3492011-05-02 13:14:24 -04001427 def test_strftime_y2k(self):
1428 for y in (1, 49, 70, 99, 100, 999, 1000, 1970):
Florent Xicluna49ce0682011-11-01 12:56:14 +01001429 d = self.theclass(y, 1, 1)
1430 # Issue 13305: For years < 1000, the value is not always
1431 # padded to 4 digits across platforms. The C standard
1432 # assumes year >= 1900, so it does not specify the number
1433 # of digits.
1434 if d.strftime("%Y") != '%04d' % y:
1435 # Year 42 returns '42', not padded
1436 self.assertEqual(d.strftime("%Y"), '%d' % y)
Victor Stinner15a83e82016-03-12 08:16:48 +01001437 # '0042' is obtained anyway
1438 self.assertEqual(d.strftime("%4Y"), '%04d' % y)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001439
1440 def test_replace(self):
1441 cls = self.theclass
1442 args = [1, 2, 3]
1443 base = cls(*args)
1444 self.assertEqual(base, base.replace())
1445
1446 i = 0
1447 for name, newval in (("year", 2),
1448 ("month", 3),
1449 ("day", 4)):
1450 newargs = args[:]
1451 newargs[i] = newval
1452 expected = cls(*newargs)
1453 got = base.replace(**{name: newval})
1454 self.assertEqual(expected, got)
1455 i += 1
1456
1457 # Out of bounds.
1458 base = cls(2000, 2, 29)
1459 self.assertRaises(ValueError, base.replace, year=2001)
1460
1461 def test_subclass_date(self):
1462
1463 class C(self.theclass):
1464 theAnswer = 42
1465
1466 def __new__(cls, *args, **kws):
1467 temp = kws.copy()
1468 extra = temp.pop('extra')
1469 result = self.theclass.__new__(cls, *args, **temp)
1470 result.extra = extra
1471 return result
1472
1473 def newmeth(self, start):
1474 return start + self.year + self.month
1475
1476 args = 2003, 4, 14
1477
1478 dt1 = self.theclass(*args)
1479 dt2 = C(*args, **{'extra': 7})
1480
1481 self.assertEqual(dt2.__class__, C)
1482 self.assertEqual(dt2.theAnswer, 42)
1483 self.assertEqual(dt2.extra, 7)
1484 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1485 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1486
1487 def test_pickling_subclass_date(self):
1488
1489 args = 6, 7, 23
1490 orig = SubclassDate(*args)
1491 for pickler, unpickler, proto in pickle_choices:
1492 green = pickler.dumps(orig, proto)
1493 derived = unpickler.loads(green)
1494 self.assertEqual(orig, derived)
1495
1496 def test_backdoor_resistance(self):
1497 # For fast unpickling, the constructor accepts a pickle byte string.
1498 # This is a low-overhead backdoor. A user can (by intent or
1499 # mistake) pass a string directly, which (if it's the right length)
1500 # will get treated like a pickle, and bypass the normal sanity
1501 # checks in the constructor. This can create insane objects.
1502 # The constructor doesn't want to burn the time to validate all
1503 # fields, but does check the month field. This stops, e.g.,
1504 # datetime.datetime('1995-03-25') from yielding an insane object.
1505 base = b'1995-03-25'
1506 if not issubclass(self.theclass, datetime):
1507 base = base[:4]
1508 for month_byte in b'9', b'\0', b'\r', b'\xff':
1509 self.assertRaises(TypeError, self.theclass,
1510 base[:2] + month_byte + base[3:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001511 if issubclass(self.theclass, datetime):
1512 # Good bytes, but bad tzinfo:
1513 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
1514 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001515
1516 for ord_byte in range(1, 13):
1517 # This shouldn't blow up because of the month byte alone. If
1518 # the implementation changes to do more-careful checking, it may
1519 # blow up because other fields are insane.
1520 self.theclass(base[:2] + bytes([ord_byte]) + base[3:])
1521
1522#############################################################################
1523# datetime tests
1524
1525class SubclassDatetime(datetime):
1526 sub_var = 1
1527
1528class TestDateTime(TestDate):
1529
1530 theclass = datetime
1531
1532 def test_basic_attributes(self):
1533 dt = self.theclass(2002, 3, 1, 12, 0)
1534 self.assertEqual(dt.year, 2002)
1535 self.assertEqual(dt.month, 3)
1536 self.assertEqual(dt.day, 1)
1537 self.assertEqual(dt.hour, 12)
1538 self.assertEqual(dt.minute, 0)
1539 self.assertEqual(dt.second, 0)
1540 self.assertEqual(dt.microsecond, 0)
1541
1542 def test_basic_attributes_nonzero(self):
1543 # Make sure all attributes are non-zero so bugs in
1544 # bit-shifting access show up.
1545 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1546 self.assertEqual(dt.year, 2002)
1547 self.assertEqual(dt.month, 3)
1548 self.assertEqual(dt.day, 1)
1549 self.assertEqual(dt.hour, 12)
1550 self.assertEqual(dt.minute, 59)
1551 self.assertEqual(dt.second, 59)
1552 self.assertEqual(dt.microsecond, 8000)
1553
1554 def test_roundtrip(self):
1555 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1556 self.theclass.now()):
1557 # Verify dt -> string -> datetime identity.
1558 s = repr(dt)
1559 self.assertTrue(s.startswith('datetime.'))
1560 s = s[9:]
1561 dt2 = eval(s)
1562 self.assertEqual(dt, dt2)
1563
1564 # Verify identity via reconstructing from pieces.
1565 dt2 = self.theclass(dt.year, dt.month, dt.day,
1566 dt.hour, dt.minute, dt.second,
1567 dt.microsecond)
1568 self.assertEqual(dt, dt2)
1569
1570 def test_isoformat(self):
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001571 t = self.theclass(1, 2, 3, 4, 5, 1, 123)
1572 self.assertEqual(t.isoformat(), "0001-02-03T04:05:01.000123")
1573 self.assertEqual(t.isoformat('T'), "0001-02-03T04:05:01.000123")
1574 self.assertEqual(t.isoformat(' '), "0001-02-03 04:05:01.000123")
1575 self.assertEqual(t.isoformat('\x00'), "0001-02-03\x0004:05:01.000123")
1576 self.assertEqual(t.isoformat(timespec='hours'), "0001-02-03T04")
1577 self.assertEqual(t.isoformat(timespec='minutes'), "0001-02-03T04:05")
1578 self.assertEqual(t.isoformat(timespec='seconds'), "0001-02-03T04:05:01")
1579 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1580 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000123")
1581 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01.000123")
1582 self.assertEqual(t.isoformat(sep=' ', timespec='minutes'), "0001-02-03 04:05")
1583 self.assertRaises(ValueError, t.isoformat, timespec='foo')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001584 # str is ISO format with the separator forced to a blank.
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001585 self.assertEqual(str(t), "0001-02-03 04:05:01.000123")
1586
1587 t = self.theclass(1, 2, 3, 4, 5, 1, 999500, tzinfo=timezone.utc)
1588 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999+00:00")
1589
1590 t = self.theclass(1, 2, 3, 4, 5, 1, 999500)
1591 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999")
1592
1593 t = self.theclass(1, 2, 3, 4, 5, 1)
1594 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01")
1595 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1596 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000000")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001597
1598 t = self.theclass(2, 3, 2)
1599 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1600 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1601 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1602 # str is ISO format with the separator forced to a blank.
1603 self.assertEqual(str(t), "0002-03-02 00:00:00")
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001604 # ISO format with timezone
1605 tz = FixedOffset(timedelta(seconds=16), 'XXX')
1606 t = self.theclass(2, 3, 2, tzinfo=tz)
1607 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00+00:00:16")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001608
1609 def test_format(self):
1610 dt = self.theclass(2007, 9, 10, 4, 5, 1, 123)
1611 self.assertEqual(dt.__format__(''), str(dt))
1612
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02001613 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001614 dt.__format__(123)
1615
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001616 # check that a derived class's __str__() gets called
1617 class A(self.theclass):
1618 def __str__(self):
1619 return 'A'
1620 a = A(2007, 9, 10, 4, 5, 1, 123)
1621 self.assertEqual(a.__format__(''), 'A')
1622
1623 # check that a derived class's strftime gets called
1624 class B(self.theclass):
1625 def strftime(self, format_spec):
1626 return 'B'
1627 b = B(2007, 9, 10, 4, 5, 1, 123)
1628 self.assertEqual(b.__format__(''), str(dt))
1629
1630 for fmt in ["m:%m d:%d y:%y",
1631 "m:%m d:%d y:%y H:%H M:%M S:%S",
1632 "%z %Z",
1633 ]:
1634 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1635 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1636 self.assertEqual(b.__format__(fmt), 'B')
1637
1638 def test_more_ctime(self):
1639 # Test fields that TestDate doesn't touch.
1640 import time
1641
1642 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1643 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1644 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1645 # out. The difference is that t.ctime() produces " 2" for the day,
1646 # but platform ctime() produces "02" for the day. According to
1647 # C99, t.ctime() is correct here.
1648 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1649
1650 # So test a case where that difference doesn't matter.
1651 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1652 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1653
1654 def test_tz_independent_comparing(self):
1655 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1656 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1657 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1658 self.assertEqual(dt1, dt3)
1659 self.assertTrue(dt2 > dt3)
1660
1661 # Make sure comparison doesn't forget microseconds, and isn't done
1662 # via comparing a float timestamp (an IEEE double doesn't have enough
1663 # precision to span microsecond resolution across years 1 thru 9999,
1664 # so comparing via timestamp necessarily calls some distinct values
1665 # equal).
1666 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1667 us = timedelta(microseconds=1)
1668 dt2 = dt1 + us
1669 self.assertEqual(dt2 - dt1, us)
1670 self.assertTrue(dt1 < dt2)
1671
1672 def test_strftime_with_bad_tzname_replace(self):
1673 # verify ok if tzinfo.tzname().replace() returns a non-string
1674 class MyTzInfo(FixedOffset):
1675 def tzname(self, dt):
1676 class MyStr(str):
1677 def replace(self, *args):
1678 return None
1679 return MyStr('name')
1680 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
1681 self.assertRaises(TypeError, t.strftime, '%Z')
1682
1683 def test_bad_constructor_arguments(self):
1684 # bad years
1685 self.theclass(MINYEAR, 1, 1) # no exception
1686 self.theclass(MAXYEAR, 1, 1) # no exception
1687 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1688 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1689 # bad months
1690 self.theclass(2000, 1, 1) # no exception
1691 self.theclass(2000, 12, 1) # no exception
1692 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1693 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1694 # bad days
1695 self.theclass(2000, 2, 29) # no exception
1696 self.theclass(2004, 2, 29) # no exception
1697 self.theclass(2400, 2, 29) # no exception
1698 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1699 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1700 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1701 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1702 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1703 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1704 # bad hours
1705 self.theclass(2000, 1, 31, 0) # no exception
1706 self.theclass(2000, 1, 31, 23) # no exception
1707 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1708 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1709 # bad minutes
1710 self.theclass(2000, 1, 31, 23, 0) # no exception
1711 self.theclass(2000, 1, 31, 23, 59) # no exception
1712 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1713 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1714 # bad seconds
1715 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1716 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1717 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1718 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1719 # bad microseconds
1720 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1721 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1722 self.assertRaises(ValueError, self.theclass,
1723 2000, 1, 31, 23, 59, 59, -1)
1724 self.assertRaises(ValueError, self.theclass,
1725 2000, 1, 31, 23, 59, 59,
1726 1000000)
Alexander Belopolsky47649ab2016-08-08 17:05:40 -04001727 # bad fold
1728 self.assertRaises(ValueError, self.theclass,
1729 2000, 1, 31, fold=-1)
1730 self.assertRaises(ValueError, self.theclass,
1731 2000, 1, 31, fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001732 # Positional fold:
1733 self.assertRaises(TypeError, self.theclass,
1734 2000, 1, 31, 23, 59, 59, 0, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04001735
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001736 def test_hash_equality(self):
1737 d = self.theclass(2000, 12, 31, 23, 30, 17)
1738 e = self.theclass(2000, 12, 31, 23, 30, 17)
1739 self.assertEqual(d, e)
1740 self.assertEqual(hash(d), hash(e))
1741
1742 dic = {d: 1}
1743 dic[e] = 2
1744 self.assertEqual(len(dic), 1)
1745 self.assertEqual(dic[d], 2)
1746 self.assertEqual(dic[e], 2)
1747
1748 d = self.theclass(2001, 1, 1, 0, 5, 17)
1749 e = self.theclass(2001, 1, 1, 0, 5, 17)
1750 self.assertEqual(d, e)
1751 self.assertEqual(hash(d), hash(e))
1752
1753 dic = {d: 1}
1754 dic[e] = 2
1755 self.assertEqual(len(dic), 1)
1756 self.assertEqual(dic[d], 2)
1757 self.assertEqual(dic[e], 2)
1758
1759 def test_computations(self):
1760 a = self.theclass(2002, 1, 31)
1761 b = self.theclass(1956, 1, 31)
1762 diff = a-b
1763 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1764 self.assertEqual(diff.seconds, 0)
1765 self.assertEqual(diff.microseconds, 0)
1766 a = self.theclass(2002, 3, 2, 17, 6)
1767 millisec = timedelta(0, 0, 1000)
1768 hour = timedelta(0, 3600)
1769 day = timedelta(1)
1770 week = timedelta(7)
1771 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1772 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1773 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1774 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1775 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1776 self.assertEqual(a - hour, a + -hour)
1777 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1778 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1779 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1780 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1781 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1782 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1783 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1784 self.assertEqual((a + week) - a, week)
1785 self.assertEqual((a + day) - a, day)
1786 self.assertEqual((a + hour) - a, hour)
1787 self.assertEqual((a + millisec) - a, millisec)
1788 self.assertEqual((a - week) - a, -week)
1789 self.assertEqual((a - day) - a, -day)
1790 self.assertEqual((a - hour) - a, -hour)
1791 self.assertEqual((a - millisec) - a, -millisec)
1792 self.assertEqual(a - (a + week), -week)
1793 self.assertEqual(a - (a + day), -day)
1794 self.assertEqual(a - (a + hour), -hour)
1795 self.assertEqual(a - (a + millisec), -millisec)
1796 self.assertEqual(a - (a - week), week)
1797 self.assertEqual(a - (a - day), day)
1798 self.assertEqual(a - (a - hour), hour)
1799 self.assertEqual(a - (a - millisec), millisec)
1800 self.assertEqual(a + (week + day + hour + millisec),
1801 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1802 self.assertEqual(a + (week + day + hour + millisec),
1803 (((a + week) + day) + hour) + millisec)
1804 self.assertEqual(a - (week + day + hour + millisec),
1805 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1806 self.assertEqual(a - (week + day + hour + millisec),
1807 (((a - week) - day) - hour) - millisec)
1808 # Add/sub ints or floats should be illegal
1809 for i in 1, 1.0:
1810 self.assertRaises(TypeError, lambda: a+i)
1811 self.assertRaises(TypeError, lambda: a-i)
1812 self.assertRaises(TypeError, lambda: i+a)
1813 self.assertRaises(TypeError, lambda: i-a)
1814
1815 # delta - datetime is senseless.
1816 self.assertRaises(TypeError, lambda: day - a)
1817 # mixing datetime and (delta or datetime) via * or // is senseless
1818 self.assertRaises(TypeError, lambda: day * a)
1819 self.assertRaises(TypeError, lambda: a * day)
1820 self.assertRaises(TypeError, lambda: day // a)
1821 self.assertRaises(TypeError, lambda: a // day)
1822 self.assertRaises(TypeError, lambda: a * a)
1823 self.assertRaises(TypeError, lambda: a // a)
1824 # datetime + datetime is senseless
1825 self.assertRaises(TypeError, lambda: a + a)
1826
1827 def test_pickling(self):
1828 args = 6, 7, 23, 20, 59, 1, 64**2
1829 orig = self.theclass(*args)
1830 for pickler, unpickler, proto in pickle_choices:
1831 green = pickler.dumps(orig, proto)
1832 derived = unpickler.loads(green)
1833 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02001834 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001835
1836 def test_more_pickling(self):
1837 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
Serhiy Storchakabad12572014-12-15 14:03:42 +02001838 for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1839 s = pickle.dumps(a, proto)
1840 b = pickle.loads(s)
1841 self.assertEqual(b.year, 2003)
1842 self.assertEqual(b.month, 2)
1843 self.assertEqual(b.day, 7)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001844
1845 def test_pickling_subclass_datetime(self):
1846 args = 6, 7, 23, 20, 59, 1, 64**2
1847 orig = SubclassDatetime(*args)
1848 for pickler, unpickler, proto in pickle_choices:
1849 green = pickler.dumps(orig, proto)
1850 derived = unpickler.loads(green)
1851 self.assertEqual(orig, derived)
1852
1853 def test_more_compare(self):
1854 # The test_compare() inherited from TestDate covers the error cases.
1855 # We just want to test lexicographic ordering on the members datetime
1856 # has that date lacks.
1857 args = [2000, 11, 29, 20, 58, 16, 999998]
1858 t1 = self.theclass(*args)
1859 t2 = self.theclass(*args)
1860 self.assertEqual(t1, t2)
1861 self.assertTrue(t1 <= t2)
1862 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001863 self.assertFalse(t1 != t2)
1864 self.assertFalse(t1 < t2)
1865 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001866
1867 for i in range(len(args)):
1868 newargs = args[:]
1869 newargs[i] = args[i] + 1
1870 t2 = self.theclass(*newargs) # this is larger than t1
1871 self.assertTrue(t1 < t2)
1872 self.assertTrue(t2 > t1)
1873 self.assertTrue(t1 <= t2)
1874 self.assertTrue(t2 >= t1)
1875 self.assertTrue(t1 != t2)
1876 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001877 self.assertFalse(t1 == t2)
1878 self.assertFalse(t2 == t1)
1879 self.assertFalse(t1 > t2)
1880 self.assertFalse(t2 < t1)
1881 self.assertFalse(t1 >= t2)
1882 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001883
1884
1885 # A helper for timestamp constructor tests.
1886 def verify_field_equality(self, expected, got):
1887 self.assertEqual(expected.tm_year, got.year)
1888 self.assertEqual(expected.tm_mon, got.month)
1889 self.assertEqual(expected.tm_mday, got.day)
1890 self.assertEqual(expected.tm_hour, got.hour)
1891 self.assertEqual(expected.tm_min, got.minute)
1892 self.assertEqual(expected.tm_sec, got.second)
1893
1894 def test_fromtimestamp(self):
1895 import time
1896
1897 ts = time.time()
1898 expected = time.localtime(ts)
1899 got = self.theclass.fromtimestamp(ts)
1900 self.verify_field_equality(expected, got)
1901
1902 def test_utcfromtimestamp(self):
1903 import time
1904
1905 ts = time.time()
1906 expected = time.gmtime(ts)
1907 got = self.theclass.utcfromtimestamp(ts)
1908 self.verify_field_equality(expected, got)
1909
Alexander Belopolskya4415142012-06-08 12:33:09 -04001910 # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in
1911 # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0).
1912 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
1913 def test_timestamp_naive(self):
1914 t = self.theclass(1970, 1, 1)
1915 self.assertEqual(t.timestamp(), 18000.0)
1916 t = self.theclass(1970, 1, 1, 1, 2, 3, 4)
1917 self.assertEqual(t.timestamp(),
1918 18000.0 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001919 # Missing hour
1920 t0 = self.theclass(2012, 3, 11, 2, 30)
1921 t1 = t0.replace(fold=1)
1922 self.assertEqual(self.theclass.fromtimestamp(t1.timestamp()),
1923 t0 - timedelta(hours=1))
1924 self.assertEqual(self.theclass.fromtimestamp(t0.timestamp()),
1925 t1 + timedelta(hours=1))
Alexander Belopolskya4415142012-06-08 12:33:09 -04001926 # Ambiguous hour defaults to DST
1927 t = self.theclass(2012, 11, 4, 1, 30)
1928 self.assertEqual(self.theclass.fromtimestamp(t.timestamp()), t)
1929
1930 # Timestamp may raise an overflow error on some platforms
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001931 # XXX: Do we care to support the first and last year?
1932 for t in [self.theclass(2,1,1), self.theclass(9998,12,12)]:
Alexander Belopolskya4415142012-06-08 12:33:09 -04001933 try:
1934 s = t.timestamp()
1935 except OverflowError:
1936 pass
1937 else:
1938 self.assertEqual(self.theclass.fromtimestamp(s), t)
1939
1940 def test_timestamp_aware(self):
1941 t = self.theclass(1970, 1, 1, tzinfo=timezone.utc)
1942 self.assertEqual(t.timestamp(), 0.0)
1943 t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone.utc)
1944 self.assertEqual(t.timestamp(),
1945 3600 + 2*60 + 3 + 4*1e-6)
1946 t = self.theclass(1970, 1, 1, 1, 2, 3, 4,
1947 tzinfo=timezone(timedelta(hours=-5), 'EST'))
1948 self.assertEqual(t.timestamp(),
1949 18000 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001950
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001951 @support.run_with_tz('MSK-03') # Something east of Greenwich
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001952 def test_microsecond_rounding(self):
Victor Stinner5ebfe422015-09-18 14:52:15 +02001953 for fts in [self.theclass.fromtimestamp,
1954 self.theclass.utcfromtimestamp]:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001955 zero = fts(0)
1956 self.assertEqual(zero.second, 0)
1957 self.assertEqual(zero.microsecond, 0)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02001958 one = fts(1e-6)
Victor Stinner8050ca92012-03-14 00:17:05 +01001959 try:
1960 minus_one = fts(-1e-6)
1961 except OSError:
1962 # localtime(-1) and gmtime(-1) is not supported on Windows
1963 pass
1964 else:
1965 self.assertEqual(minus_one.second, 59)
1966 self.assertEqual(minus_one.microsecond, 999999)
Victor Stinner5d272cc2012-03-13 13:35:55 +01001967
Victor Stinner8050ca92012-03-14 00:17:05 +01001968 t = fts(-1e-8)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02001969 self.assertEqual(t, zero)
Victor Stinner8050ca92012-03-14 00:17:05 +01001970 t = fts(-9e-7)
1971 self.assertEqual(t, minus_one)
1972 t = fts(-1e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02001973 self.assertEqual(t, zero)
Victor Stinner8820a352015-09-05 10:50:20 +02001974 t = fts(-1/2**7)
1975 self.assertEqual(t.second, 59)
Victor Stinner7667f582015-09-09 01:02:23 +02001976 self.assertEqual(t.microsecond, 992188)
Victor Stinner5d272cc2012-03-13 13:35:55 +01001977
1978 t = fts(1e-7)
1979 self.assertEqual(t, zero)
1980 t = fts(9e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02001981 self.assertEqual(t, one)
Victor Stinner5d272cc2012-03-13 13:35:55 +01001982 t = fts(0.99999949)
1983 self.assertEqual(t.second, 0)
1984 self.assertEqual(t.microsecond, 999999)
1985 t = fts(0.9999999)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02001986 self.assertEqual(t.second, 1)
1987 self.assertEqual(t.microsecond, 0)
Victor Stinneradfefa52015-09-04 23:57:25 +02001988 t = fts(1/2**7)
1989 self.assertEqual(t.second, 0)
Victor Stinner7667f582015-09-09 01:02:23 +02001990 self.assertEqual(t.microsecond, 7812)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001991
Victor Stinnerb67f0962017-02-10 10:34:02 +01001992 def test_timestamp_limits(self):
1993 # minimum timestamp
1994 min_dt = self.theclass.min.replace(tzinfo=timezone.utc)
1995 min_ts = min_dt.timestamp()
Victor Stinner6f37e362017-02-10 11:45:14 +01001996 try:
1997 # date 0001-01-01 00:00:00+00:00: timestamp=-62135596800
1998 self.assertEqual(self.theclass.fromtimestamp(min_ts, tz=timezone.utc),
1999 min_dt)
2000 except OverflowError as exc:
2001 # the date 0001-01-01 doesn't fit into 32-bit time_t
2002 self.skipTest(str(exc))
Victor Stinnerb67f0962017-02-10 10:34:02 +01002003
2004 # maximum timestamp: set seconds to zero to avoid rounding issues
2005 max_dt = self.theclass.max.replace(tzinfo=timezone.utc,
2006 second=0, microsecond=0)
2007 max_ts = max_dt.timestamp()
2008 # date 9999-12-31 23:59:00+00:00: timestamp 253402300740
2009 self.assertEqual(self.theclass.fromtimestamp(max_ts, tz=timezone.utc),
2010 max_dt)
2011
2012 # number of seconds greater than 1 year: make sure that the new date
2013 # is not valid in datetime.datetime limits
2014 delta = 3600 * 24 * 400
2015
2016 # too small
2017 ts = min_ts - delta
2018 # converting a Python int to C time_t can raise a OverflowError,
2019 # especially on 32-bit platforms.
2020 with self.assertRaises((ValueError, OverflowError)):
2021 self.theclass.fromtimestamp(ts)
2022 with self.assertRaises((ValueError, OverflowError)):
2023 self.theclass.utcfromtimestamp(ts)
2024
2025 # too big
2026 ts = max_dt.timestamp() + delta
2027 with self.assertRaises((ValueError, OverflowError)):
2028 self.theclass.fromtimestamp(ts)
2029 with self.assertRaises((ValueError, OverflowError)):
2030 self.theclass.utcfromtimestamp(ts)
2031
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002032 def test_insane_fromtimestamp(self):
2033 # It's possible that some platform maps time_t to double,
2034 # and that this test will fail there. This test should
2035 # exempt such platforms (provided they return reasonable
2036 # results!).
2037 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002038 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002039 insane)
2040
2041 def test_insane_utcfromtimestamp(self):
2042 # It's possible that some platform maps time_t to double,
2043 # and that this test will fail there. This test should
2044 # exempt such platforms (provided they return reasonable
2045 # results!).
2046 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002047 self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002048 insane)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002049
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002050 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2051 def test_negative_float_fromtimestamp(self):
2052 # The result is tz-dependent; at least test that this doesn't
2053 # fail (like it did before bug 1646728 was fixed).
2054 self.theclass.fromtimestamp(-1.05)
2055
2056 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2057 def test_negative_float_utcfromtimestamp(self):
2058 d = self.theclass.utcfromtimestamp(-1.05)
2059 self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
2060
2061 def test_utcnow(self):
2062 import time
2063
2064 # Call it a success if utcnow() and utcfromtimestamp() are within
2065 # a second of each other.
2066 tolerance = timedelta(seconds=1)
2067 for dummy in range(3):
2068 from_now = self.theclass.utcnow()
2069 from_timestamp = self.theclass.utcfromtimestamp(time.time())
2070 if abs(from_timestamp - from_now) <= tolerance:
2071 break
2072 # Else try again a few times.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002073 self.assertLessEqual(abs(from_timestamp - from_now), tolerance)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002074
2075 def test_strptime(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002076 string = '2004-12-01 13:02:47.197'
2077 format = '%Y-%m-%d %H:%M:%S.%f'
2078 expected = _strptime._strptime_datetime(self.theclass, string, format)
2079 got = self.theclass.strptime(string, format)
2080 self.assertEqual(expected, got)
2081 self.assertIs(type(expected), self.theclass)
2082 self.assertIs(type(got), self.theclass)
2083
2084 strptime = self.theclass.strptime
2085 self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
2086 self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
2087 # Only local timezone and UTC are supported
2088 for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
2089 (-_time.timezone, _time.tzname[0])):
2090 if tzseconds < 0:
2091 sign = '-'
2092 seconds = -tzseconds
2093 else:
2094 sign ='+'
2095 seconds = tzseconds
2096 hours, minutes = divmod(seconds//60, 60)
2097 dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
Martin Panterfca22322015-11-16 09:22:19 +00002098 dt = strptime(dtstr, "%z %Z")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002099 self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
2100 self.assertEqual(dt.tzname(), tzname)
2101 # Can produce inconsistent datetime
2102 dtstr, fmt = "+1234 UTC", "%z %Z"
2103 dt = strptime(dtstr, fmt)
2104 self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
2105 self.assertEqual(dt.tzname(), 'UTC')
2106 # yet will roundtrip
2107 self.assertEqual(dt.strftime(fmt), dtstr)
2108
2109 # Produce naive datetime if no %z is provided
2110 self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
2111
2112 with self.assertRaises(ValueError): strptime("-2400", "%z")
2113 with self.assertRaises(ValueError): strptime("-000", "%z")
2114
2115 def test_more_timetuple(self):
2116 # This tests fields beyond those tested by the TestDate.test_timetuple.
2117 t = self.theclass(2004, 12, 31, 6, 22, 33)
2118 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
2119 self.assertEqual(t.timetuple(),
2120 (t.year, t.month, t.day,
2121 t.hour, t.minute, t.second,
2122 t.weekday(),
2123 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
2124 -1))
2125 tt = t.timetuple()
2126 self.assertEqual(tt.tm_year, t.year)
2127 self.assertEqual(tt.tm_mon, t.month)
2128 self.assertEqual(tt.tm_mday, t.day)
2129 self.assertEqual(tt.tm_hour, t.hour)
2130 self.assertEqual(tt.tm_min, t.minute)
2131 self.assertEqual(tt.tm_sec, t.second)
2132 self.assertEqual(tt.tm_wday, t.weekday())
2133 self.assertEqual(tt.tm_yday, t.toordinal() -
2134 date(t.year, 1, 1).toordinal() + 1)
2135 self.assertEqual(tt.tm_isdst, -1)
2136
2137 def test_more_strftime(self):
2138 # This tests fields beyond those tested by the TestDate.test_strftime.
2139 t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
2140 self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
2141 "12 31 04 000047 33 22 06 366")
2142
2143 def test_extract(self):
2144 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2145 self.assertEqual(dt.date(), date(2002, 3, 4))
2146 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2147
2148 def test_combine(self):
2149 d = date(2002, 3, 4)
2150 t = time(18, 45, 3, 1234)
2151 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2152 combine = self.theclass.combine
2153 dt = combine(d, t)
2154 self.assertEqual(dt, expected)
2155
2156 dt = combine(time=t, date=d)
2157 self.assertEqual(dt, expected)
2158
2159 self.assertEqual(d, dt.date())
2160 self.assertEqual(t, dt.time())
2161 self.assertEqual(dt, combine(dt.date(), dt.time()))
2162
2163 self.assertRaises(TypeError, combine) # need an arg
2164 self.assertRaises(TypeError, combine, d) # need two args
2165 self.assertRaises(TypeError, combine, t, d) # args reversed
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002166 self.assertRaises(TypeError, combine, d, t, 1) # wrong tzinfo type
2167 self.assertRaises(TypeError, combine, d, t, 1, 2) # too many args
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002168 self.assertRaises(TypeError, combine, "date", "time") # wrong types
2169 self.assertRaises(TypeError, combine, d, "time") # wrong type
2170 self.assertRaises(TypeError, combine, "date", t) # wrong type
2171
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002172 # tzinfo= argument
2173 dt = combine(d, t, timezone.utc)
2174 self.assertIs(dt.tzinfo, timezone.utc)
2175 dt = combine(d, t, tzinfo=timezone.utc)
2176 self.assertIs(dt.tzinfo, timezone.utc)
2177 t = time()
2178 dt = combine(dt, t)
2179 self.assertEqual(dt.date(), d)
2180 self.assertEqual(dt.time(), t)
2181
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002182 def test_replace(self):
2183 cls = self.theclass
2184 args = [1, 2, 3, 4, 5, 6, 7]
2185 base = cls(*args)
2186 self.assertEqual(base, base.replace())
2187
2188 i = 0
2189 for name, newval in (("year", 2),
2190 ("month", 3),
2191 ("day", 4),
2192 ("hour", 5),
2193 ("minute", 6),
2194 ("second", 7),
2195 ("microsecond", 8)):
2196 newargs = args[:]
2197 newargs[i] = newval
2198 expected = cls(*newargs)
2199 got = base.replace(**{name: newval})
2200 self.assertEqual(expected, got)
2201 i += 1
2202
2203 # Out of bounds.
2204 base = cls(2000, 2, 29)
2205 self.assertRaises(ValueError, base.replace, year=2001)
2206
2207 def test_astimezone(self):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002208 return # The rest is no longer applicable
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002209 # Pretty boring! The TZ test is more interesting here. astimezone()
2210 # simply can't be applied to a naive object.
2211 dt = self.theclass.now()
2212 f = FixedOffset(44, "")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04002213 self.assertRaises(ValueError, dt.astimezone) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002214 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
2215 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
2216 self.assertRaises(ValueError, dt.astimezone, f) # naive
2217 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
2218
2219 class Bogus(tzinfo):
2220 def utcoffset(self, dt): return None
2221 def dst(self, dt): return timedelta(0)
2222 bog = Bogus()
2223 self.assertRaises(ValueError, dt.astimezone, bog) # naive
2224 self.assertRaises(ValueError,
2225 dt.replace(tzinfo=bog).astimezone, f)
2226
2227 class AlsoBogus(tzinfo):
2228 def utcoffset(self, dt): return timedelta(0)
2229 def dst(self, dt): return None
2230 alsobog = AlsoBogus()
2231 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
2232
2233 def test_subclass_datetime(self):
2234
2235 class C(self.theclass):
2236 theAnswer = 42
2237
2238 def __new__(cls, *args, **kws):
2239 temp = kws.copy()
2240 extra = temp.pop('extra')
2241 result = self.theclass.__new__(cls, *args, **temp)
2242 result.extra = extra
2243 return result
2244
2245 def newmeth(self, start):
2246 return start + self.year + self.month + self.second
2247
2248 args = 2003, 4, 14, 12, 13, 41
2249
2250 dt1 = self.theclass(*args)
2251 dt2 = C(*args, **{'extra': 7})
2252
2253 self.assertEqual(dt2.__class__, C)
2254 self.assertEqual(dt2.theAnswer, 42)
2255 self.assertEqual(dt2.extra, 7)
2256 self.assertEqual(dt1.toordinal(), dt2.toordinal())
2257 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
2258 dt1.second - 7)
2259
2260class TestSubclassDateTime(TestDateTime):
2261 theclass = SubclassDatetime
2262 # Override tests not designed for subclass
Zachary Ware9fe6d862013-12-08 00:20:35 -06002263 @unittest.skip('not appropriate for subclasses')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002264 def test_roundtrip(self):
2265 pass
2266
2267class SubclassTime(time):
2268 sub_var = 1
2269
2270class TestTime(HarmlessMixedComparison, unittest.TestCase):
2271
2272 theclass = time
2273
2274 def test_basic_attributes(self):
2275 t = self.theclass(12, 0)
2276 self.assertEqual(t.hour, 12)
2277 self.assertEqual(t.minute, 0)
2278 self.assertEqual(t.second, 0)
2279 self.assertEqual(t.microsecond, 0)
2280
2281 def test_basic_attributes_nonzero(self):
2282 # Make sure all attributes are non-zero so bugs in
2283 # bit-shifting access show up.
2284 t = self.theclass(12, 59, 59, 8000)
2285 self.assertEqual(t.hour, 12)
2286 self.assertEqual(t.minute, 59)
2287 self.assertEqual(t.second, 59)
2288 self.assertEqual(t.microsecond, 8000)
2289
2290 def test_roundtrip(self):
2291 t = self.theclass(1, 2, 3, 4)
2292
2293 # Verify t -> string -> time identity.
2294 s = repr(t)
2295 self.assertTrue(s.startswith('datetime.'))
2296 s = s[9:]
2297 t2 = eval(s)
2298 self.assertEqual(t, t2)
2299
2300 # Verify identity via reconstructing from pieces.
2301 t2 = self.theclass(t.hour, t.minute, t.second,
2302 t.microsecond)
2303 self.assertEqual(t, t2)
2304
2305 def test_comparing(self):
2306 args = [1, 2, 3, 4]
2307 t1 = self.theclass(*args)
2308 t2 = self.theclass(*args)
2309 self.assertEqual(t1, t2)
2310 self.assertTrue(t1 <= t2)
2311 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002312 self.assertFalse(t1 != t2)
2313 self.assertFalse(t1 < t2)
2314 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002315
2316 for i in range(len(args)):
2317 newargs = args[:]
2318 newargs[i] = args[i] + 1
2319 t2 = self.theclass(*newargs) # this is larger than t1
2320 self.assertTrue(t1 < t2)
2321 self.assertTrue(t2 > t1)
2322 self.assertTrue(t1 <= t2)
2323 self.assertTrue(t2 >= t1)
2324 self.assertTrue(t1 != t2)
2325 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002326 self.assertFalse(t1 == t2)
2327 self.assertFalse(t2 == t1)
2328 self.assertFalse(t1 > t2)
2329 self.assertFalse(t2 < t1)
2330 self.assertFalse(t1 >= t2)
2331 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002332
2333 for badarg in OTHERSTUFF:
2334 self.assertEqual(t1 == badarg, False)
2335 self.assertEqual(t1 != badarg, True)
2336 self.assertEqual(badarg == t1, False)
2337 self.assertEqual(badarg != t1, True)
2338
2339 self.assertRaises(TypeError, lambda: t1 <= badarg)
2340 self.assertRaises(TypeError, lambda: t1 < badarg)
2341 self.assertRaises(TypeError, lambda: t1 > badarg)
2342 self.assertRaises(TypeError, lambda: t1 >= badarg)
2343 self.assertRaises(TypeError, lambda: badarg <= t1)
2344 self.assertRaises(TypeError, lambda: badarg < t1)
2345 self.assertRaises(TypeError, lambda: badarg > t1)
2346 self.assertRaises(TypeError, lambda: badarg >= t1)
2347
2348 def test_bad_constructor_arguments(self):
2349 # bad hours
2350 self.theclass(0, 0) # no exception
2351 self.theclass(23, 0) # no exception
2352 self.assertRaises(ValueError, self.theclass, -1, 0)
2353 self.assertRaises(ValueError, self.theclass, 24, 0)
2354 # bad minutes
2355 self.theclass(23, 0) # no exception
2356 self.theclass(23, 59) # no exception
2357 self.assertRaises(ValueError, self.theclass, 23, -1)
2358 self.assertRaises(ValueError, self.theclass, 23, 60)
2359 # bad seconds
2360 self.theclass(23, 59, 0) # no exception
2361 self.theclass(23, 59, 59) # no exception
2362 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
2363 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
2364 # bad microseconds
2365 self.theclass(23, 59, 59, 0) # no exception
2366 self.theclass(23, 59, 59, 999999) # no exception
2367 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
2368 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
2369
2370 def test_hash_equality(self):
2371 d = self.theclass(23, 30, 17)
2372 e = self.theclass(23, 30, 17)
2373 self.assertEqual(d, e)
2374 self.assertEqual(hash(d), hash(e))
2375
2376 dic = {d: 1}
2377 dic[e] = 2
2378 self.assertEqual(len(dic), 1)
2379 self.assertEqual(dic[d], 2)
2380 self.assertEqual(dic[e], 2)
2381
2382 d = self.theclass(0, 5, 17)
2383 e = self.theclass(0, 5, 17)
2384 self.assertEqual(d, e)
2385 self.assertEqual(hash(d), hash(e))
2386
2387 dic = {d: 1}
2388 dic[e] = 2
2389 self.assertEqual(len(dic), 1)
2390 self.assertEqual(dic[d], 2)
2391 self.assertEqual(dic[e], 2)
2392
2393 def test_isoformat(self):
2394 t = self.theclass(4, 5, 1, 123)
2395 self.assertEqual(t.isoformat(), "04:05:01.000123")
2396 self.assertEqual(t.isoformat(), str(t))
2397
2398 t = self.theclass()
2399 self.assertEqual(t.isoformat(), "00:00:00")
2400 self.assertEqual(t.isoformat(), str(t))
2401
2402 t = self.theclass(microsecond=1)
2403 self.assertEqual(t.isoformat(), "00:00:00.000001")
2404 self.assertEqual(t.isoformat(), str(t))
2405
2406 t = self.theclass(microsecond=10)
2407 self.assertEqual(t.isoformat(), "00:00:00.000010")
2408 self.assertEqual(t.isoformat(), str(t))
2409
2410 t = self.theclass(microsecond=100)
2411 self.assertEqual(t.isoformat(), "00:00:00.000100")
2412 self.assertEqual(t.isoformat(), str(t))
2413
2414 t = self.theclass(microsecond=1000)
2415 self.assertEqual(t.isoformat(), "00:00:00.001000")
2416 self.assertEqual(t.isoformat(), str(t))
2417
2418 t = self.theclass(microsecond=10000)
2419 self.assertEqual(t.isoformat(), "00:00:00.010000")
2420 self.assertEqual(t.isoformat(), str(t))
2421
2422 t = self.theclass(microsecond=100000)
2423 self.assertEqual(t.isoformat(), "00:00:00.100000")
2424 self.assertEqual(t.isoformat(), str(t))
2425
Alexander Belopolskya2998a62016-03-06 14:58:43 -05002426 t = self.theclass(hour=12, minute=34, second=56, microsecond=123456)
2427 self.assertEqual(t.isoformat(timespec='hours'), "12")
2428 self.assertEqual(t.isoformat(timespec='minutes'), "12:34")
2429 self.assertEqual(t.isoformat(timespec='seconds'), "12:34:56")
2430 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.123")
2431 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.123456")
2432 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56.123456")
2433 self.assertRaises(ValueError, t.isoformat, timespec='monkey')
2434
2435 t = self.theclass(hour=12, minute=34, second=56, microsecond=999500)
2436 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.999")
2437
2438 t = self.theclass(hour=12, minute=34, second=56, microsecond=0)
2439 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.000")
2440 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.000000")
2441 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56")
2442
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002443 def test_1653736(self):
2444 # verify it doesn't accept extra keyword arguments
2445 t = self.theclass(second=1)
2446 self.assertRaises(TypeError, t.isoformat, foo=3)
2447
2448 def test_strftime(self):
2449 t = self.theclass(1, 2, 3, 4)
2450 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
2451 # A naive object replaces %z and %Z with empty strings.
2452 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
2453
2454 def test_format(self):
2455 t = self.theclass(1, 2, 3, 4)
2456 self.assertEqual(t.__format__(''), str(t))
2457
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02002458 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002459 t.__format__(123)
2460
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002461 # check that a derived class's __str__() gets called
2462 class A(self.theclass):
2463 def __str__(self):
2464 return 'A'
2465 a = A(1, 2, 3, 4)
2466 self.assertEqual(a.__format__(''), 'A')
2467
2468 # check that a derived class's strftime gets called
2469 class B(self.theclass):
2470 def strftime(self, format_spec):
2471 return 'B'
2472 b = B(1, 2, 3, 4)
2473 self.assertEqual(b.__format__(''), str(t))
2474
2475 for fmt in ['%H %M %S',
2476 ]:
2477 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
2478 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
2479 self.assertEqual(b.__format__(fmt), 'B')
2480
2481 def test_str(self):
2482 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
2483 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
2484 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
2485 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
2486 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
2487
2488 def test_repr(self):
2489 name = 'datetime.' + self.theclass.__name__
2490 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
2491 "%s(1, 2, 3, 4)" % name)
2492 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
2493 "%s(10, 2, 3, 4000)" % name)
2494 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
2495 "%s(0, 2, 3, 400000)" % name)
2496 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
2497 "%s(12, 2, 3)" % name)
2498 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
2499 "%s(23, 15)" % name)
2500
2501 def test_resolution_info(self):
2502 self.assertIsInstance(self.theclass.min, self.theclass)
2503 self.assertIsInstance(self.theclass.max, self.theclass)
2504 self.assertIsInstance(self.theclass.resolution, timedelta)
2505 self.assertTrue(self.theclass.max > self.theclass.min)
2506
2507 def test_pickling(self):
2508 args = 20, 59, 16, 64**2
2509 orig = self.theclass(*args)
2510 for pickler, unpickler, proto in pickle_choices:
2511 green = pickler.dumps(orig, proto)
2512 derived = unpickler.loads(green)
2513 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02002514 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002515
2516 def test_pickling_subclass_time(self):
2517 args = 20, 59, 16, 64**2
2518 orig = SubclassTime(*args)
2519 for pickler, unpickler, proto in pickle_choices:
2520 green = pickler.dumps(orig, proto)
2521 derived = unpickler.loads(green)
2522 self.assertEqual(orig, derived)
2523
2524 def test_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002525 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002526 cls = self.theclass
2527 self.assertTrue(cls(1))
2528 self.assertTrue(cls(0, 1))
2529 self.assertTrue(cls(0, 0, 1))
2530 self.assertTrue(cls(0, 0, 0, 1))
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002531 self.assertTrue(cls(0))
2532 self.assertTrue(cls())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002533
2534 def test_replace(self):
2535 cls = self.theclass
2536 args = [1, 2, 3, 4]
2537 base = cls(*args)
2538 self.assertEqual(base, base.replace())
2539
2540 i = 0
2541 for name, newval in (("hour", 5),
2542 ("minute", 6),
2543 ("second", 7),
2544 ("microsecond", 8)):
2545 newargs = args[:]
2546 newargs[i] = newval
2547 expected = cls(*newargs)
2548 got = base.replace(**{name: newval})
2549 self.assertEqual(expected, got)
2550 i += 1
2551
2552 # Out of bounds.
2553 base = cls(1)
2554 self.assertRaises(ValueError, base.replace, hour=24)
2555 self.assertRaises(ValueError, base.replace, minute=-1)
2556 self.assertRaises(ValueError, base.replace, second=100)
2557 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2558
2559 def test_subclass_time(self):
2560
2561 class C(self.theclass):
2562 theAnswer = 42
2563
2564 def __new__(cls, *args, **kws):
2565 temp = kws.copy()
2566 extra = temp.pop('extra')
2567 result = self.theclass.__new__(cls, *args, **temp)
2568 result.extra = extra
2569 return result
2570
2571 def newmeth(self, start):
2572 return start + self.hour + self.second
2573
2574 args = 4, 5, 6
2575
2576 dt1 = self.theclass(*args)
2577 dt2 = C(*args, **{'extra': 7})
2578
2579 self.assertEqual(dt2.__class__, C)
2580 self.assertEqual(dt2.theAnswer, 42)
2581 self.assertEqual(dt2.extra, 7)
2582 self.assertEqual(dt1.isoformat(), dt2.isoformat())
2583 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2584
2585 def test_backdoor_resistance(self):
2586 # see TestDate.test_backdoor_resistance().
2587 base = '2:59.0'
2588 for hour_byte in ' ', '9', chr(24), '\xff':
2589 self.assertRaises(TypeError, self.theclass,
2590 hour_byte + base[1:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002591 # Good bytes, but bad tzinfo:
2592 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
2593 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002594
2595# A mixin for classes with a tzinfo= argument. Subclasses must define
Martin Panter46f50722016-05-26 05:35:26 +00002596# theclass as a class attribute, and theclass(1, 1, 1, tzinfo=whatever)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002597# must be legit (which is true for time and datetime).
2598class TZInfoBase:
2599
2600 def test_argument_passing(self):
2601 cls = self.theclass
2602 # A datetime passes itself on, a time passes None.
2603 class introspective(tzinfo):
2604 def tzname(self, dt): return dt and "real" or "none"
2605 def utcoffset(self, dt):
2606 return timedelta(minutes = dt and 42 or -42)
2607 dst = utcoffset
2608
2609 obj = cls(1, 2, 3, tzinfo=introspective())
2610
2611 expected = cls is time and "none" or "real"
2612 self.assertEqual(obj.tzname(), expected)
2613
2614 expected = timedelta(minutes=(cls is time and -42 or 42))
2615 self.assertEqual(obj.utcoffset(), expected)
2616 self.assertEqual(obj.dst(), expected)
2617
2618 def test_bad_tzinfo_classes(self):
2619 cls = self.theclass
2620 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
2621
2622 class NiceTry(object):
2623 def __init__(self): pass
2624 def utcoffset(self, dt): pass
2625 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
2626
2627 class BetterTry(tzinfo):
2628 def __init__(self): pass
2629 def utcoffset(self, dt): pass
2630 b = BetterTry()
2631 t = cls(1, 1, 1, tzinfo=b)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002632 self.assertIs(t.tzinfo, b)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002633
2634 def test_utc_offset_out_of_bounds(self):
2635 class Edgy(tzinfo):
2636 def __init__(self, offset):
2637 self.offset = timedelta(minutes=offset)
2638 def utcoffset(self, dt):
2639 return self.offset
2640
2641 cls = self.theclass
2642 for offset, legit in ((-1440, False),
2643 (-1439, True),
2644 (1439, True),
2645 (1440, False)):
2646 if cls is time:
2647 t = cls(1, 2, 3, tzinfo=Edgy(offset))
2648 elif cls is datetime:
2649 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
2650 else:
2651 assert 0, "impossible"
2652 if legit:
2653 aofs = abs(offset)
2654 h, m = divmod(aofs, 60)
2655 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
2656 if isinstance(t, datetime):
2657 t = t.timetz()
2658 self.assertEqual(str(t), "01:02:03" + tag)
2659 else:
2660 self.assertRaises(ValueError, str, t)
2661
2662 def test_tzinfo_classes(self):
2663 cls = self.theclass
2664 class C1(tzinfo):
2665 def utcoffset(self, dt): return None
2666 def dst(self, dt): return None
2667 def tzname(self, dt): return None
2668 for t in (cls(1, 1, 1),
2669 cls(1, 1, 1, tzinfo=None),
2670 cls(1, 1, 1, tzinfo=C1())):
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002671 self.assertIsNone(t.utcoffset())
2672 self.assertIsNone(t.dst())
2673 self.assertIsNone(t.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002674
2675 class C3(tzinfo):
2676 def utcoffset(self, dt): return timedelta(minutes=-1439)
2677 def dst(self, dt): return timedelta(minutes=1439)
2678 def tzname(self, dt): return "aname"
2679 t = cls(1, 1, 1, tzinfo=C3())
2680 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
2681 self.assertEqual(t.dst(), timedelta(minutes=1439))
2682 self.assertEqual(t.tzname(), "aname")
2683
2684 # Wrong types.
2685 class C4(tzinfo):
2686 def utcoffset(self, dt): return "aname"
2687 def dst(self, dt): return 7
2688 def tzname(self, dt): return 0
2689 t = cls(1, 1, 1, tzinfo=C4())
2690 self.assertRaises(TypeError, t.utcoffset)
2691 self.assertRaises(TypeError, t.dst)
2692 self.assertRaises(TypeError, t.tzname)
2693
2694 # Offset out of range.
2695 class C6(tzinfo):
2696 def utcoffset(self, dt): return timedelta(hours=-24)
2697 def dst(self, dt): return timedelta(hours=24)
2698 t = cls(1, 1, 1, tzinfo=C6())
2699 self.assertRaises(ValueError, t.utcoffset)
2700 self.assertRaises(ValueError, t.dst)
2701
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002702 # Not a whole number of seconds.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002703 class C7(tzinfo):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002704 def utcoffset(self, dt): return timedelta(microseconds=61)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002705 def dst(self, dt): return timedelta(microseconds=-81)
2706 t = cls(1, 1, 1, tzinfo=C7())
2707 self.assertRaises(ValueError, t.utcoffset)
2708 self.assertRaises(ValueError, t.dst)
2709
2710 def test_aware_compare(self):
2711 cls = self.theclass
2712
2713 # Ensure that utcoffset() gets ignored if the comparands have
2714 # the same tzinfo member.
2715 class OperandDependentOffset(tzinfo):
2716 def utcoffset(self, t):
2717 if t.minute < 10:
2718 # d0 and d1 equal after adjustment
2719 return timedelta(minutes=t.minute)
2720 else:
2721 # d2 off in the weeds
2722 return timedelta(minutes=59)
2723
2724 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
2725 d0 = base.replace(minute=3)
2726 d1 = base.replace(minute=9)
2727 d2 = base.replace(minute=11)
2728 for x in d0, d1, d2:
2729 for y in d0, d1, d2:
2730 for op in lt, le, gt, ge, eq, ne:
2731 got = op(x, y)
2732 expected = op(x.minute, y.minute)
2733 self.assertEqual(got, expected)
2734
2735 # However, if they're different members, uctoffset is not ignored.
2736 # Note that a time can't actually have an operand-depedent offset,
2737 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
2738 # so skip this test for time.
2739 if cls is not time:
2740 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2741 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2742 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2743 for x in d0, d1, d2:
2744 for y in d0, d1, d2:
2745 got = (x > y) - (x < y)
2746 if (x is d0 or x is d1) and (y is d0 or y is d1):
2747 expected = 0
2748 elif x is y is d2:
2749 expected = 0
2750 elif x is d2:
2751 expected = -1
2752 else:
2753 assert y is d2
2754 expected = 1
2755 self.assertEqual(got, expected)
2756
2757
2758# Testing time objects with a non-None tzinfo.
2759class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
2760 theclass = time
2761
2762 def test_empty(self):
2763 t = self.theclass()
2764 self.assertEqual(t.hour, 0)
2765 self.assertEqual(t.minute, 0)
2766 self.assertEqual(t.second, 0)
2767 self.assertEqual(t.microsecond, 0)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002768 self.assertIsNone(t.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002769
2770 def test_zones(self):
2771 est = FixedOffset(-300, "EST", 1)
2772 utc = FixedOffset(0, "UTC", -2)
2773 met = FixedOffset(60, "MET", 3)
2774 t1 = time( 7, 47, tzinfo=est)
2775 t2 = time(12, 47, tzinfo=utc)
2776 t3 = time(13, 47, tzinfo=met)
2777 t4 = time(microsecond=40)
2778 t5 = time(microsecond=40, tzinfo=utc)
2779
2780 self.assertEqual(t1.tzinfo, est)
2781 self.assertEqual(t2.tzinfo, utc)
2782 self.assertEqual(t3.tzinfo, met)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002783 self.assertIsNone(t4.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002784 self.assertEqual(t5.tzinfo, utc)
2785
2786 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2787 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2788 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002789 self.assertIsNone(t4.utcoffset())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002790 self.assertRaises(TypeError, t1.utcoffset, "no args")
2791
2792 self.assertEqual(t1.tzname(), "EST")
2793 self.assertEqual(t2.tzname(), "UTC")
2794 self.assertEqual(t3.tzname(), "MET")
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002795 self.assertIsNone(t4.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002796 self.assertRaises(TypeError, t1.tzname, "no args")
2797
2798 self.assertEqual(t1.dst(), timedelta(minutes=1))
2799 self.assertEqual(t2.dst(), timedelta(minutes=-2))
2800 self.assertEqual(t3.dst(), timedelta(minutes=3))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002801 self.assertIsNone(t4.dst())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002802 self.assertRaises(TypeError, t1.dst, "no args")
2803
2804 self.assertEqual(hash(t1), hash(t2))
2805 self.assertEqual(hash(t1), hash(t3))
2806 self.assertEqual(hash(t2), hash(t3))
2807
2808 self.assertEqual(t1, t2)
2809 self.assertEqual(t1, t3)
2810 self.assertEqual(t2, t3)
Alexander Belopolsky08313822012-06-15 20:19:47 -04002811 self.assertNotEqual(t4, t5) # mixed tz-aware & naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002812 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
2813 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
2814
2815 self.assertEqual(str(t1), "07:47:00-05:00")
2816 self.assertEqual(str(t2), "12:47:00+00:00")
2817 self.assertEqual(str(t3), "13:47:00+01:00")
2818 self.assertEqual(str(t4), "00:00:00.000040")
2819 self.assertEqual(str(t5), "00:00:00.000040+00:00")
2820
2821 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
2822 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
2823 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
2824 self.assertEqual(t4.isoformat(), "00:00:00.000040")
2825 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
2826
2827 d = 'datetime.time'
2828 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
2829 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
2830 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
2831 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
2832 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2833
2834 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2835 "07:47:00 %Z=EST %z=-0500")
2836 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2837 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2838
2839 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
2840 t1 = time(23, 59, tzinfo=yuck)
2841 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2842 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2843
2844 # Check that an invalid tzname result raises an exception.
2845 class Badtzname(tzinfo):
Alexander Belopolskye239d232010-12-08 23:31:48 +00002846 tz = 42
2847 def tzname(self, dt): return self.tz
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002848 t = time(2, 3, 4, tzinfo=Badtzname())
2849 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2850 self.assertRaises(TypeError, t.strftime, "%Z")
2851
Alexander Belopolskye239d232010-12-08 23:31:48 +00002852 # Issue #6697:
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002853 if '_Fast' in str(self):
Alexander Belopolskye239d232010-12-08 23:31:48 +00002854 Badtzname.tz = '\ud800'
2855 self.assertRaises(ValueError, t.strftime, "%Z")
2856
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002857 def test_hash_edge_cases(self):
2858 # Offsets that overflow a basic time.
2859 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2860 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2861 self.assertEqual(hash(t1), hash(t2))
2862
2863 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2864 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2865 self.assertEqual(hash(t1), hash(t2))
2866
2867 def test_pickling(self):
2868 # Try one without a tzinfo.
2869 args = 20, 59, 16, 64**2
2870 orig = self.theclass(*args)
2871 for pickler, unpickler, proto in pickle_choices:
2872 green = pickler.dumps(orig, proto)
2873 derived = unpickler.loads(green)
2874 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02002875 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002876
2877 # Try one with a tzinfo.
2878 tinfo = PicklableFixedOffset(-300, 'cookie')
2879 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
2880 for pickler, unpickler, proto in pickle_choices:
2881 green = pickler.dumps(orig, proto)
2882 derived = unpickler.loads(green)
2883 self.assertEqual(orig, derived)
2884 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
2885 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2886 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02002887 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002888
2889 def test_more_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002890 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002891 cls = self.theclass
2892
2893 t = cls(0, tzinfo=FixedOffset(-300, ""))
2894 self.assertTrue(t)
2895
2896 t = cls(5, tzinfo=FixedOffset(-300, ""))
2897 self.assertTrue(t)
2898
2899 t = cls(5, tzinfo=FixedOffset(300, ""))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002900 self.assertTrue(t)
2901
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002902 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2903 self.assertTrue(t)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002904
2905 def test_replace(self):
2906 cls = self.theclass
2907 z100 = FixedOffset(100, "+100")
2908 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2909 args = [1, 2, 3, 4, z100]
2910 base = cls(*args)
2911 self.assertEqual(base, base.replace())
2912
2913 i = 0
2914 for name, newval in (("hour", 5),
2915 ("minute", 6),
2916 ("second", 7),
2917 ("microsecond", 8),
2918 ("tzinfo", zm200)):
2919 newargs = args[:]
2920 newargs[i] = newval
2921 expected = cls(*newargs)
2922 got = base.replace(**{name: newval})
2923 self.assertEqual(expected, got)
2924 i += 1
2925
2926 # Ensure we can get rid of a tzinfo.
2927 self.assertEqual(base.tzname(), "+100")
2928 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002929 self.assertIsNone(base2.tzinfo)
2930 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002931
2932 # Ensure we can add one.
2933 base3 = base2.replace(tzinfo=z100)
2934 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002935 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002936
2937 # Out of bounds.
2938 base = cls(1)
2939 self.assertRaises(ValueError, base.replace, hour=24)
2940 self.assertRaises(ValueError, base.replace, minute=-1)
2941 self.assertRaises(ValueError, base.replace, second=100)
2942 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2943
2944 def test_mixed_compare(self):
2945 t1 = time(1, 2, 3)
2946 t2 = time(1, 2, 3)
2947 self.assertEqual(t1, t2)
2948 t2 = t2.replace(tzinfo=None)
2949 self.assertEqual(t1, t2)
2950 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2951 self.assertEqual(t1, t2)
2952 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04002953 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002954
2955 # In time w/ identical tzinfo objects, utcoffset is ignored.
2956 class Varies(tzinfo):
2957 def __init__(self):
2958 self.offset = timedelta(minutes=22)
2959 def utcoffset(self, t):
2960 self.offset += timedelta(minutes=1)
2961 return self.offset
2962
2963 v = Varies()
2964 t1 = t2.replace(tzinfo=v)
2965 t2 = t2.replace(tzinfo=v)
2966 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2967 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2968 self.assertEqual(t1, t2)
2969
2970 # But if they're not identical, it isn't ignored.
2971 t2 = t2.replace(tzinfo=Varies())
2972 self.assertTrue(t1 < t2) # t1's offset counter still going up
2973
2974 def test_subclass_timetz(self):
2975
2976 class C(self.theclass):
2977 theAnswer = 42
2978
2979 def __new__(cls, *args, **kws):
2980 temp = kws.copy()
2981 extra = temp.pop('extra')
2982 result = self.theclass.__new__(cls, *args, **temp)
2983 result.extra = extra
2984 return result
2985
2986 def newmeth(self, start):
2987 return start + self.hour + self.second
2988
2989 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2990
2991 dt1 = self.theclass(*args)
2992 dt2 = C(*args, **{'extra': 7})
2993
2994 self.assertEqual(dt2.__class__, C)
2995 self.assertEqual(dt2.theAnswer, 42)
2996 self.assertEqual(dt2.extra, 7)
2997 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2998 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2999
3000
3001# Testing datetime objects with a non-None tzinfo.
3002
3003class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
3004 theclass = datetime
3005
3006 def test_trivial(self):
3007 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
3008 self.assertEqual(dt.year, 1)
3009 self.assertEqual(dt.month, 2)
3010 self.assertEqual(dt.day, 3)
3011 self.assertEqual(dt.hour, 4)
3012 self.assertEqual(dt.minute, 5)
3013 self.assertEqual(dt.second, 6)
3014 self.assertEqual(dt.microsecond, 7)
3015 self.assertEqual(dt.tzinfo, None)
3016
3017 def test_even_more_compare(self):
3018 # The test_compare() and test_more_compare() inherited from TestDate
3019 # and TestDateTime covered non-tzinfo cases.
3020
3021 # Smallest possible after UTC adjustment.
3022 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3023 # Largest possible after UTC adjustment.
3024 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3025 tzinfo=FixedOffset(-1439, ""))
3026
3027 # Make sure those compare correctly, and w/o overflow.
3028 self.assertTrue(t1 < t2)
3029 self.assertTrue(t1 != t2)
3030 self.assertTrue(t2 > t1)
3031
3032 self.assertEqual(t1, t1)
3033 self.assertEqual(t2, t2)
3034
3035 # Equal afer adjustment.
3036 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
3037 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
3038 self.assertEqual(t1, t2)
3039
3040 # Change t1 not to subtract a minute, and t1 should be larger.
3041 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
3042 self.assertTrue(t1 > t2)
3043
3044 # Change t1 to subtract 2 minutes, and t1 should be smaller.
3045 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
3046 self.assertTrue(t1 < t2)
3047
3048 # Back to the original t1, but make seconds resolve it.
3049 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3050 second=1)
3051 self.assertTrue(t1 > t2)
3052
3053 # Likewise, but make microseconds resolve it.
3054 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3055 microsecond=1)
3056 self.assertTrue(t1 > t2)
3057
Alexander Belopolsky08313822012-06-15 20:19:47 -04003058 # Make t2 naive and it should differ.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003059 t2 = self.theclass.min
Alexander Belopolsky08313822012-06-15 20:19:47 -04003060 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003061 self.assertEqual(t2, t2)
3062
3063 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
3064 class Naive(tzinfo):
3065 def utcoffset(self, dt): return None
3066 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
Alexander Belopolsky08313822012-06-15 20:19:47 -04003067 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003068 self.assertEqual(t2, t2)
3069
3070 # OTOH, it's OK to compare two of these mixing the two ways of being
3071 # naive.
3072 t1 = self.theclass(5, 6, 7)
3073 self.assertEqual(t1, t2)
3074
3075 # Try a bogus uctoffset.
3076 class Bogus(tzinfo):
3077 def utcoffset(self, dt):
3078 return timedelta(minutes=1440) # out of bounds
3079 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
3080 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
3081 self.assertRaises(ValueError, lambda: t1 == t2)
3082
3083 def test_pickling(self):
3084 # Try one without a tzinfo.
3085 args = 6, 7, 23, 20, 59, 1, 64**2
3086 orig = self.theclass(*args)
3087 for pickler, unpickler, proto in pickle_choices:
3088 green = pickler.dumps(orig, proto)
3089 derived = unpickler.loads(green)
3090 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003091 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003092
3093 # Try one with a tzinfo.
3094 tinfo = PicklableFixedOffset(-300, 'cookie')
3095 orig = self.theclass(*args, **{'tzinfo': tinfo})
3096 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
3097 for pickler, unpickler, proto in pickle_choices:
3098 green = pickler.dumps(orig, proto)
3099 derived = unpickler.loads(green)
3100 self.assertEqual(orig, derived)
3101 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3102 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3103 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003104 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003105
3106 def test_extreme_hashes(self):
3107 # If an attempt is made to hash these via subtracting the offset
3108 # then hashing a datetime object, OverflowError results. The
3109 # Python implementation used to blow up here.
3110 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3111 hash(t)
3112 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3113 tzinfo=FixedOffset(-1439, ""))
3114 hash(t)
3115
3116 # OTOH, an OOB offset should blow up.
3117 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
3118 self.assertRaises(ValueError, hash, t)
3119
3120 def test_zones(self):
3121 est = FixedOffset(-300, "EST")
3122 utc = FixedOffset(0, "UTC")
3123 met = FixedOffset(60, "MET")
3124 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
3125 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
3126 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
3127 self.assertEqual(t1.tzinfo, est)
3128 self.assertEqual(t2.tzinfo, utc)
3129 self.assertEqual(t3.tzinfo, met)
3130 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
3131 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
3132 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
3133 self.assertEqual(t1.tzname(), "EST")
3134 self.assertEqual(t2.tzname(), "UTC")
3135 self.assertEqual(t3.tzname(), "MET")
3136 self.assertEqual(hash(t1), hash(t2))
3137 self.assertEqual(hash(t1), hash(t3))
3138 self.assertEqual(hash(t2), hash(t3))
3139 self.assertEqual(t1, t2)
3140 self.assertEqual(t1, t3)
3141 self.assertEqual(t2, t3)
3142 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
3143 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
3144 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
3145 d = 'datetime.datetime(2002, 3, 19, '
3146 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
3147 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
3148 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
3149
3150 def test_combine(self):
3151 met = FixedOffset(60, "MET")
3152 d = date(2002, 3, 4)
3153 tz = time(18, 45, 3, 1234, tzinfo=met)
3154 dt = datetime.combine(d, tz)
3155 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
3156 tzinfo=met))
3157
3158 def test_extract(self):
3159 met = FixedOffset(60, "MET")
3160 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
3161 self.assertEqual(dt.date(), date(2002, 3, 4))
3162 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
3163 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
3164
3165 def test_tz_aware_arithmetic(self):
3166 import random
3167
3168 now = self.theclass.now()
3169 tz55 = FixedOffset(-330, "west 5:30")
3170 timeaware = now.time().replace(tzinfo=tz55)
3171 nowaware = self.theclass.combine(now.date(), timeaware)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003172 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003173 self.assertEqual(nowaware.timetz(), timeaware)
3174
3175 # Can't mix aware and non-aware.
3176 self.assertRaises(TypeError, lambda: now - nowaware)
3177 self.assertRaises(TypeError, lambda: nowaware - now)
3178
3179 # And adding datetime's doesn't make sense, aware or not.
3180 self.assertRaises(TypeError, lambda: now + nowaware)
3181 self.assertRaises(TypeError, lambda: nowaware + now)
3182 self.assertRaises(TypeError, lambda: nowaware + nowaware)
3183
3184 # Subtracting should yield 0.
3185 self.assertEqual(now - now, timedelta(0))
3186 self.assertEqual(nowaware - nowaware, timedelta(0))
3187
3188 # Adding a delta should preserve tzinfo.
3189 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
3190 nowawareplus = nowaware + delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003191 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003192 nowawareplus2 = delta + nowaware
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003193 self.assertIs(nowawareplus2.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003194 self.assertEqual(nowawareplus, nowawareplus2)
3195
3196 # that - delta should be what we started with, and that - what we
3197 # started with should be delta.
3198 diff = nowawareplus - delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003199 self.assertIs(diff.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003200 self.assertEqual(nowaware, diff)
3201 self.assertRaises(TypeError, lambda: delta - nowawareplus)
3202 self.assertEqual(nowawareplus - nowaware, delta)
3203
3204 # Make up a random timezone.
3205 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
3206 # Attach it to nowawareplus.
3207 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003208 self.assertIs(nowawareplus.tzinfo, tzr)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003209 # Make sure the difference takes the timezone adjustments into account.
3210 got = nowaware - nowawareplus
3211 # Expected: (nowaware base - nowaware offset) -
3212 # (nowawareplus base - nowawareplus offset) =
3213 # (nowaware base - nowawareplus base) +
3214 # (nowawareplus offset - nowaware offset) =
3215 # -delta + nowawareplus offset - nowaware offset
3216 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
3217 self.assertEqual(got, expected)
3218
3219 # Try max possible difference.
3220 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
3221 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3222 tzinfo=FixedOffset(-1439, "max"))
3223 maxdiff = max - min
3224 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
3225 timedelta(minutes=2*1439))
3226 # Different tzinfo, but the same offset
3227 tza = timezone(HOUR, 'A')
3228 tzb = timezone(HOUR, 'B')
3229 delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
3230 self.assertEqual(delta, self.theclass.min - self.theclass.max)
3231
3232 def test_tzinfo_now(self):
3233 meth = self.theclass.now
3234 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3235 base = meth()
3236 # Try with and without naming the keyword.
3237 off42 = FixedOffset(42, "42")
3238 another = meth(off42)
3239 again = meth(tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003240 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003241 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3242 # Bad argument with and w/o naming the keyword.
3243 self.assertRaises(TypeError, meth, 16)
3244 self.assertRaises(TypeError, meth, tzinfo=16)
3245 # Bad keyword name.
3246 self.assertRaises(TypeError, meth, tinfo=off42)
3247 # Too many args.
3248 self.assertRaises(TypeError, meth, off42, off42)
3249
3250 # We don't know which time zone we're in, and don't have a tzinfo
3251 # class to represent it, so seeing whether a tz argument actually
3252 # does a conversion is tricky.
3253 utc = FixedOffset(0, "utc", 0)
3254 for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
3255 timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
3256 for dummy in range(3):
3257 now = datetime.now(weirdtz)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003258 self.assertIs(now.tzinfo, weirdtz)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003259 utcnow = datetime.utcnow().replace(tzinfo=utc)
3260 now2 = utcnow.astimezone(weirdtz)
3261 if abs(now - now2) < timedelta(seconds=30):
3262 break
3263 # Else the code is broken, or more than 30 seconds passed between
3264 # calls; assuming the latter, just try again.
3265 else:
3266 # Three strikes and we're out.
3267 self.fail("utcnow(), now(tz), or astimezone() may be broken")
3268
3269 def test_tzinfo_fromtimestamp(self):
3270 import time
3271 meth = self.theclass.fromtimestamp
3272 ts = time.time()
3273 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3274 base = meth(ts)
3275 # Try with and without naming the keyword.
3276 off42 = FixedOffset(42, "42")
3277 another = meth(ts, off42)
3278 again = meth(ts, tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003279 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003280 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3281 # Bad argument with and w/o naming the keyword.
3282 self.assertRaises(TypeError, meth, ts, 16)
3283 self.assertRaises(TypeError, meth, ts, tzinfo=16)
3284 # Bad keyword name.
3285 self.assertRaises(TypeError, meth, ts, tinfo=off42)
3286 # Too many args.
3287 self.assertRaises(TypeError, meth, ts, off42, off42)
3288 # Too few args.
3289 self.assertRaises(TypeError, meth)
3290
3291 # Try to make sure tz= actually does some conversion.
3292 timestamp = 1000000000
3293 utcdatetime = datetime.utcfromtimestamp(timestamp)
3294 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
3295 # But on some flavor of Mac, it's nowhere near that. So we can't have
3296 # any idea here what time that actually is, we can only test that
3297 # relative changes match.
3298 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
3299 tz = FixedOffset(utcoffset, "tz", 0)
3300 expected = utcdatetime + utcoffset
3301 got = datetime.fromtimestamp(timestamp, tz)
3302 self.assertEqual(expected, got.replace(tzinfo=None))
3303
3304 def test_tzinfo_utcnow(self):
3305 meth = self.theclass.utcnow
3306 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3307 base = meth()
3308 # Try with and without naming the keyword; for whatever reason,
3309 # utcnow() doesn't accept a tzinfo argument.
3310 off42 = FixedOffset(42, "42")
3311 self.assertRaises(TypeError, meth, off42)
3312 self.assertRaises(TypeError, meth, tzinfo=off42)
3313
3314 def test_tzinfo_utcfromtimestamp(self):
3315 import time
3316 meth = self.theclass.utcfromtimestamp
3317 ts = time.time()
3318 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3319 base = meth(ts)
3320 # Try with and without naming the keyword; for whatever reason,
3321 # utcfromtimestamp() doesn't accept a tzinfo argument.
3322 off42 = FixedOffset(42, "42")
3323 self.assertRaises(TypeError, meth, ts, off42)
3324 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
3325
3326 def test_tzinfo_timetuple(self):
3327 # TestDateTime tested most of this. datetime adds a twist to the
3328 # DST flag.
3329 class DST(tzinfo):
3330 def __init__(self, dstvalue):
3331 if isinstance(dstvalue, int):
3332 dstvalue = timedelta(minutes=dstvalue)
3333 self.dstvalue = dstvalue
3334 def dst(self, dt):
3335 return self.dstvalue
3336
3337 cls = self.theclass
3338 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
3339 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
3340 t = d.timetuple()
3341 self.assertEqual(1, t.tm_year)
3342 self.assertEqual(1, t.tm_mon)
3343 self.assertEqual(1, t.tm_mday)
3344 self.assertEqual(10, t.tm_hour)
3345 self.assertEqual(20, t.tm_min)
3346 self.assertEqual(30, t.tm_sec)
3347 self.assertEqual(0, t.tm_wday)
3348 self.assertEqual(1, t.tm_yday)
3349 self.assertEqual(flag, t.tm_isdst)
3350
3351 # dst() returns wrong type.
3352 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
3353
3354 # dst() at the edge.
3355 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
3356 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
3357
3358 # dst() out of range.
3359 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
3360 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
3361
3362 def test_utctimetuple(self):
3363 class DST(tzinfo):
3364 def __init__(self, dstvalue=0):
3365 if isinstance(dstvalue, int):
3366 dstvalue = timedelta(minutes=dstvalue)
3367 self.dstvalue = dstvalue
3368 def dst(self, dt):
3369 return self.dstvalue
3370
3371 cls = self.theclass
3372 # This can't work: DST didn't implement utcoffset.
3373 self.assertRaises(NotImplementedError,
3374 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
3375
3376 class UOFS(DST):
3377 def __init__(self, uofs, dofs=None):
3378 DST.__init__(self, dofs)
3379 self.uofs = timedelta(minutes=uofs)
3380 def utcoffset(self, dt):
3381 return self.uofs
3382
3383 for dstvalue in -33, 33, 0, None:
3384 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
3385 t = d.utctimetuple()
3386 self.assertEqual(d.year, t.tm_year)
3387 self.assertEqual(d.month, t.tm_mon)
3388 self.assertEqual(d.day, t.tm_mday)
3389 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
3390 self.assertEqual(13, t.tm_min)
3391 self.assertEqual(d.second, t.tm_sec)
3392 self.assertEqual(d.weekday(), t.tm_wday)
3393 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
3394 t.tm_yday)
3395 # Ensure tm_isdst is 0 regardless of what dst() says: DST
3396 # is never in effect for a UTC time.
3397 self.assertEqual(0, t.tm_isdst)
3398
3399 # For naive datetime, utctimetuple == timetuple except for isdst
3400 d = cls(1, 2, 3, 10, 20, 30, 40)
3401 t = d.utctimetuple()
3402 self.assertEqual(t[:-1], d.timetuple()[:-1])
3403 self.assertEqual(0, t.tm_isdst)
3404 # Same if utcoffset is None
3405 class NOFS(DST):
3406 def utcoffset(self, dt):
3407 return None
3408 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
3409 t = d.utctimetuple()
3410 self.assertEqual(t[:-1], d.timetuple()[:-1])
3411 self.assertEqual(0, t.tm_isdst)
3412 # Check that bad tzinfo is detected
3413 class BOFS(DST):
3414 def utcoffset(self, dt):
3415 return "EST"
3416 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
3417 self.assertRaises(TypeError, d.utctimetuple)
3418
3419 # Check that utctimetuple() is the same as
3420 # astimezone(utc).timetuple()
3421 d = cls(2010, 11, 13, 14, 15, 16, 171819)
3422 for tz in [timezone.min, timezone.utc, timezone.max]:
3423 dtz = d.replace(tzinfo=tz)
3424 self.assertEqual(dtz.utctimetuple()[:-1],
3425 dtz.astimezone(timezone.utc).timetuple()[:-1])
3426 # At the edges, UTC adjustment can produce years out-of-range
3427 # for a datetime object. Ensure that an OverflowError is
3428 # raised.
3429 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
3430 # That goes back 1 minute less than a full day.
3431 self.assertRaises(OverflowError, tiny.utctimetuple)
3432
3433 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
3434 # That goes forward 1 minute less than a full day.
3435 self.assertRaises(OverflowError, huge.utctimetuple)
3436 # More overflow cases
3437 tiny = cls.min.replace(tzinfo=timezone(MINUTE))
3438 self.assertRaises(OverflowError, tiny.utctimetuple)
3439 huge = cls.max.replace(tzinfo=timezone(-MINUTE))
3440 self.assertRaises(OverflowError, huge.utctimetuple)
3441
3442 def test_tzinfo_isoformat(self):
3443 zero = FixedOffset(0, "+00:00")
3444 plus = FixedOffset(220, "+03:40")
3445 minus = FixedOffset(-231, "-03:51")
3446 unknown = FixedOffset(None, "")
3447
3448 cls = self.theclass
3449 datestr = '0001-02-03'
3450 for ofs in None, zero, plus, minus, unknown:
3451 for us in 0, 987001:
3452 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
3453 timestr = '04:05:59' + (us and '.987001' or '')
3454 ofsstr = ofs is not None and d.tzname() or ''
3455 tailstr = timestr + ofsstr
3456 iso = d.isoformat()
3457 self.assertEqual(iso, datestr + 'T' + tailstr)
3458 self.assertEqual(iso, d.isoformat('T'))
3459 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
3460 self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
3461 self.assertEqual(str(d), datestr + ' ' + tailstr)
3462
3463 def test_replace(self):
3464 cls = self.theclass
3465 z100 = FixedOffset(100, "+100")
3466 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3467 args = [1, 2, 3, 4, 5, 6, 7, z100]
3468 base = cls(*args)
3469 self.assertEqual(base, base.replace())
3470
3471 i = 0
3472 for name, newval in (("year", 2),
3473 ("month", 3),
3474 ("day", 4),
3475 ("hour", 5),
3476 ("minute", 6),
3477 ("second", 7),
3478 ("microsecond", 8),
3479 ("tzinfo", zm200)):
3480 newargs = args[:]
3481 newargs[i] = newval
3482 expected = cls(*newargs)
3483 got = base.replace(**{name: newval})
3484 self.assertEqual(expected, got)
3485 i += 1
3486
3487 # Ensure we can get rid of a tzinfo.
3488 self.assertEqual(base.tzname(), "+100")
3489 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003490 self.assertIsNone(base2.tzinfo)
3491 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003492
3493 # Ensure we can add one.
3494 base3 = base2.replace(tzinfo=z100)
3495 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003496 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003497
3498 # Out of bounds.
3499 base = cls(2000, 2, 29)
3500 self.assertRaises(ValueError, base.replace, year=2001)
3501
3502 def test_more_astimezone(self):
3503 # The inherited test_astimezone covered some trivial and error cases.
3504 fnone = FixedOffset(None, "None")
3505 f44m = FixedOffset(44, "44")
3506 fm5h = FixedOffset(-timedelta(hours=5), "m300")
3507
3508 dt = self.theclass.now(tz=f44m)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003509 self.assertIs(dt.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003510 # Replacing with degenerate tzinfo raises an exception.
3511 self.assertRaises(ValueError, dt.astimezone, fnone)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003512 # Replacing with same tzinfo makes no change.
3513 x = dt.astimezone(dt.tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003514 self.assertIs(x.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003515 self.assertEqual(x.date(), dt.date())
3516 self.assertEqual(x.time(), dt.time())
3517
3518 # Replacing with different tzinfo does adjust.
3519 got = dt.astimezone(fm5h)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003520 self.assertIs(got.tzinfo, fm5h)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003521 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
3522 expected = dt - dt.utcoffset() # in effect, convert to UTC
3523 expected += fm5h.utcoffset(dt) # and from there to local time
3524 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
3525 self.assertEqual(got.date(), expected.date())
3526 self.assertEqual(got.time(), expected.time())
3527 self.assertEqual(got.timetz(), expected.timetz())
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003528 self.assertIs(got.tzinfo, expected.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003529 self.assertEqual(got, expected)
3530
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003531 @support.run_with_tz('UTC')
3532 def test_astimezone_default_utc(self):
3533 dt = self.theclass.now(timezone.utc)
3534 self.assertEqual(dt.astimezone(None), dt)
3535 self.assertEqual(dt.astimezone(), dt)
3536
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003537 # Note that offset in TZ variable has the opposite sign to that
3538 # produced by %z directive.
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003539 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
3540 def test_astimezone_default_eastern(self):
3541 dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
3542 local = dt.astimezone()
3543 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003544 self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003545 dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
3546 local = dt.astimezone()
3547 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003548 self.assertEqual(local.strftime("%z %Z"), "-0400 EDT")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003549
Alexander Belopolsky1dcf4f92016-03-25 15:42:59 -04003550 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
3551 def test_astimezone_default_near_fold(self):
3552 # Issue #26616.
3553 u = datetime(2015, 11, 1, 5, tzinfo=timezone.utc)
3554 t = u.astimezone()
3555 s = t.astimezone()
3556 self.assertEqual(t.tzinfo, s.tzinfo)
3557
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003558 def test_aware_subtract(self):
3559 cls = self.theclass
3560
3561 # Ensure that utcoffset() is ignored when the operands have the
3562 # same tzinfo member.
3563 class OperandDependentOffset(tzinfo):
3564 def utcoffset(self, t):
3565 if t.minute < 10:
3566 # d0 and d1 equal after adjustment
3567 return timedelta(minutes=t.minute)
3568 else:
3569 # d2 off in the weeds
3570 return timedelta(minutes=59)
3571
3572 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
3573 d0 = base.replace(minute=3)
3574 d1 = base.replace(minute=9)
3575 d2 = base.replace(minute=11)
3576 for x in d0, d1, d2:
3577 for y in d0, d1, d2:
3578 got = x - y
3579 expected = timedelta(minutes=x.minute - y.minute)
3580 self.assertEqual(got, expected)
3581
3582 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
3583 # ignored.
3584 base = cls(8, 9, 10, 11, 12, 13, 14)
3585 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3586 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3587 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3588 for x in d0, d1, d2:
3589 for y in d0, d1, d2:
3590 got = x - y
3591 if (x is d0 or x is d1) and (y is d0 or y is d1):
3592 expected = timedelta(0)
3593 elif x is y is d2:
3594 expected = timedelta(0)
3595 elif x is d2:
3596 expected = timedelta(minutes=(11-59)-0)
3597 else:
3598 assert y is d2
3599 expected = timedelta(minutes=0-(11-59))
3600 self.assertEqual(got, expected)
3601
3602 def test_mixed_compare(self):
3603 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
3604 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
3605 self.assertEqual(t1, t2)
3606 t2 = t2.replace(tzinfo=None)
3607 self.assertEqual(t1, t2)
3608 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3609 self.assertEqual(t1, t2)
3610 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04003611 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003612
3613 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
3614 class Varies(tzinfo):
3615 def __init__(self):
3616 self.offset = timedelta(minutes=22)
3617 def utcoffset(self, t):
3618 self.offset += timedelta(minutes=1)
3619 return self.offset
3620
3621 v = Varies()
3622 t1 = t2.replace(tzinfo=v)
3623 t2 = t2.replace(tzinfo=v)
3624 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3625 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3626 self.assertEqual(t1, t2)
3627
3628 # But if they're not identical, it isn't ignored.
3629 t2 = t2.replace(tzinfo=Varies())
3630 self.assertTrue(t1 < t2) # t1's offset counter still going up
3631
3632 def test_subclass_datetimetz(self):
3633
3634 class C(self.theclass):
3635 theAnswer = 42
3636
3637 def __new__(cls, *args, **kws):
3638 temp = kws.copy()
3639 extra = temp.pop('extra')
3640 result = self.theclass.__new__(cls, *args, **temp)
3641 result.extra = extra
3642 return result
3643
3644 def newmeth(self, start):
3645 return start + self.hour + self.year
3646
3647 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3648
3649 dt1 = self.theclass(*args)
3650 dt2 = C(*args, **{'extra': 7})
3651
3652 self.assertEqual(dt2.__class__, C)
3653 self.assertEqual(dt2.theAnswer, 42)
3654 self.assertEqual(dt2.extra, 7)
3655 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3656 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
3657
3658# Pain to set up DST-aware tzinfo classes.
3659
3660def first_sunday_on_or_after(dt):
3661 days_to_go = 6 - dt.weekday()
3662 if days_to_go:
3663 dt += timedelta(days_to_go)
3664 return dt
3665
3666ZERO = timedelta(0)
3667MINUTE = timedelta(minutes=1)
3668HOUR = timedelta(hours=1)
3669DAY = timedelta(days=1)
3670# In the US, DST starts at 2am (standard time) on the first Sunday in April.
3671DSTSTART = datetime(1, 4, 1, 2)
3672# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
3673# which is the first Sunday on or after Oct 25. Because we view 1:MM as
3674# being standard time on that day, there is no spelling in local time of
3675# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
3676DSTEND = datetime(1, 10, 25, 1)
3677
3678class USTimeZone(tzinfo):
3679
3680 def __init__(self, hours, reprname, stdname, dstname):
3681 self.stdoffset = timedelta(hours=hours)
3682 self.reprname = reprname
3683 self.stdname = stdname
3684 self.dstname = dstname
3685
3686 def __repr__(self):
3687 return self.reprname
3688
3689 def tzname(self, dt):
3690 if self.dst(dt):
3691 return self.dstname
3692 else:
3693 return self.stdname
3694
3695 def utcoffset(self, dt):
3696 return self.stdoffset + self.dst(dt)
3697
3698 def dst(self, dt):
3699 if dt is None or dt.tzinfo is None:
3700 # An exception instead may be sensible here, in one or more of
3701 # the cases.
3702 return ZERO
3703 assert dt.tzinfo is self
3704
3705 # Find first Sunday in April.
3706 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
3707 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
3708
3709 # Find last Sunday in October.
3710 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
3711 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
3712
3713 # Can't compare naive to aware objects, so strip the timezone from
3714 # dt first.
3715 if start <= dt.replace(tzinfo=None) < end:
3716 return HOUR
3717 else:
3718 return ZERO
3719
3720Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
3721Central = USTimeZone(-6, "Central", "CST", "CDT")
3722Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
3723Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
3724utc_real = FixedOffset(0, "UTC", 0)
3725# For better test coverage, we want another flavor of UTC that's west of
3726# the Eastern and Pacific timezones.
3727utc_fake = FixedOffset(-12*60, "UTCfake", 0)
3728
3729class TestTimezoneConversions(unittest.TestCase):
3730 # The DST switch times for 2002, in std time.
3731 dston = datetime(2002, 4, 7, 2)
3732 dstoff = datetime(2002, 10, 27, 1)
3733
3734 theclass = datetime
3735
3736 # Check a time that's inside DST.
3737 def checkinside(self, dt, tz, utc, dston, dstoff):
3738 self.assertEqual(dt.dst(), HOUR)
3739
3740 # Conversion to our own timezone is always an identity.
3741 self.assertEqual(dt.astimezone(tz), dt)
3742
3743 asutc = dt.astimezone(utc)
3744 there_and_back = asutc.astimezone(tz)
3745
3746 # Conversion to UTC and back isn't always an identity here,
3747 # because there are redundant spellings (in local time) of
3748 # UTC time when DST begins: the clock jumps from 1:59:59
3749 # to 3:00:00, and a local time of 2:MM:SS doesn't really
3750 # make sense then. The classes above treat 2:MM:SS as
3751 # daylight time then (it's "after 2am"), really an alias
3752 # for 1:MM:SS standard time. The latter form is what
3753 # conversion back from UTC produces.
3754 if dt.date() == dston.date() and dt.hour == 2:
3755 # We're in the redundant hour, and coming back from
3756 # UTC gives the 1:MM:SS standard-time spelling.
3757 self.assertEqual(there_and_back + HOUR, dt)
3758 # Although during was considered to be in daylight
3759 # time, there_and_back is not.
3760 self.assertEqual(there_and_back.dst(), ZERO)
3761 # They're the same times in UTC.
3762 self.assertEqual(there_and_back.astimezone(utc),
3763 dt.astimezone(utc))
3764 else:
3765 # We're not in the redundant hour.
3766 self.assertEqual(dt, there_and_back)
3767
3768 # Because we have a redundant spelling when DST begins, there is
Ezio Melotti3b3499b2011-03-16 11:35:38 +02003769 # (unfortunately) an hour when DST ends that can't be spelled at all in
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003770 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
3771 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
3772 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
3773 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
3774 # expressed in local time. Nevertheless, we want conversion back
3775 # from UTC to mimic the local clock's "repeat an hour" behavior.
3776 nexthour_utc = asutc + HOUR
3777 nexthour_tz = nexthour_utc.astimezone(tz)
3778 if dt.date() == dstoff.date() and dt.hour == 0:
3779 # We're in the hour before the last DST hour. The last DST hour
3780 # is ineffable. We want the conversion back to repeat 1:MM.
3781 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3782 nexthour_utc += HOUR
3783 nexthour_tz = nexthour_utc.astimezone(tz)
3784 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3785 else:
3786 self.assertEqual(nexthour_tz - dt, HOUR)
3787
3788 # Check a time that's outside DST.
3789 def checkoutside(self, dt, tz, utc):
3790 self.assertEqual(dt.dst(), ZERO)
3791
3792 # Conversion to our own timezone is always an identity.
3793 self.assertEqual(dt.astimezone(tz), dt)
3794
3795 # Converting to UTC and back is an identity too.
3796 asutc = dt.astimezone(utc)
3797 there_and_back = asutc.astimezone(tz)
3798 self.assertEqual(dt, there_and_back)
3799
3800 def convert_between_tz_and_utc(self, tz, utc):
3801 dston = self.dston.replace(tzinfo=tz)
3802 # Because 1:MM on the day DST ends is taken as being standard time,
3803 # there is no spelling in tz for the last hour of daylight time.
3804 # For purposes of the test, the last hour of DST is 0:MM, which is
3805 # taken as being daylight time (and 1:MM is taken as being standard
3806 # time).
3807 dstoff = self.dstoff.replace(tzinfo=tz)
3808 for delta in (timedelta(weeks=13),
3809 DAY,
3810 HOUR,
3811 timedelta(minutes=1),
3812 timedelta(microseconds=1)):
3813
3814 self.checkinside(dston, tz, utc, dston, dstoff)
3815 for during in dston + delta, dstoff - delta:
3816 self.checkinside(during, tz, utc, dston, dstoff)
3817
3818 self.checkoutside(dstoff, tz, utc)
3819 for outside in dston - delta, dstoff + delta:
3820 self.checkoutside(outside, tz, utc)
3821
3822 def test_easy(self):
3823 # Despite the name of this test, the endcases are excruciating.
3824 self.convert_between_tz_and_utc(Eastern, utc_real)
3825 self.convert_between_tz_and_utc(Pacific, utc_real)
3826 self.convert_between_tz_and_utc(Eastern, utc_fake)
3827 self.convert_between_tz_and_utc(Pacific, utc_fake)
3828 # The next is really dancing near the edge. It works because
3829 # Pacific and Eastern are far enough apart that their "problem
3830 # hours" don't overlap.
3831 self.convert_between_tz_and_utc(Eastern, Pacific)
3832 self.convert_between_tz_and_utc(Pacific, Eastern)
3833 # OTOH, these fail! Don't enable them. The difficulty is that
3834 # the edge case tests assume that every hour is representable in
3835 # the "utc" class. This is always true for a fixed-offset tzinfo
3836 # class (lke utc_real and utc_fake), but not for Eastern or Central.
3837 # For these adjacent DST-aware time zones, the range of time offsets
3838 # tested ends up creating hours in the one that aren't representable
3839 # in the other. For the same reason, we would see failures in the
3840 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
3841 # offset deltas in convert_between_tz_and_utc().
3842 #
3843 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
3844 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
3845
3846 def test_tricky(self):
3847 # 22:00 on day before daylight starts.
3848 fourback = self.dston - timedelta(hours=4)
3849 ninewest = FixedOffset(-9*60, "-0900", 0)
3850 fourback = fourback.replace(tzinfo=ninewest)
3851 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
3852 # 2", we should get the 3 spelling.
3853 # If we plug 22:00 the day before into Eastern, it "looks like std
3854 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
3855 # to 22:00 lands on 2:00, which makes no sense in local time (the
3856 # local clock jumps from 1 to 3). The point here is to make sure we
3857 # get the 3 spelling.
3858 expected = self.dston.replace(hour=3)
3859 got = fourback.astimezone(Eastern).replace(tzinfo=None)
3860 self.assertEqual(expected, got)
3861
3862 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
3863 # case we want the 1:00 spelling.
3864 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
3865 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
3866 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
3867 # spelling.
3868 expected = self.dston.replace(hour=1)
3869 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
3870 self.assertEqual(expected, got)
3871
3872 # Now on the day DST ends, we want "repeat an hour" behavior.
3873 # UTC 4:MM 5:MM 6:MM 7:MM checking these
3874 # EST 23:MM 0:MM 1:MM 2:MM
3875 # EDT 0:MM 1:MM 2:MM 3:MM
3876 # wall 0:MM 1:MM 1:MM 2:MM against these
3877 for utc in utc_real, utc_fake:
3878 for tz in Eastern, Pacific:
3879 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
3880 # Convert that to UTC.
3881 first_std_hour -= tz.utcoffset(None)
3882 # Adjust for possibly fake UTC.
3883 asutc = first_std_hour + utc.utcoffset(None)
3884 # First UTC hour to convert; this is 4:00 when utc=utc_real &
3885 # tz=Eastern.
3886 asutcbase = asutc.replace(tzinfo=utc)
3887 for tzhour in (0, 1, 1, 2):
3888 expectedbase = self.dstoff.replace(hour=tzhour)
3889 for minute in 0, 30, 59:
3890 expected = expectedbase.replace(minute=minute)
3891 asutc = asutcbase.replace(minute=minute)
3892 astz = asutc.astimezone(tz)
3893 self.assertEqual(astz.replace(tzinfo=None), expected)
3894 asutcbase += HOUR
3895
3896
3897 def test_bogus_dst(self):
3898 class ok(tzinfo):
3899 def utcoffset(self, dt): return HOUR
3900 def dst(self, dt): return HOUR
3901
3902 now = self.theclass.now().replace(tzinfo=utc_real)
3903 # Doesn't blow up.
3904 now.astimezone(ok())
3905
3906 # Does blow up.
3907 class notok(ok):
3908 def dst(self, dt): return None
3909 self.assertRaises(ValueError, now.astimezone, notok())
3910
3911 # Sometimes blow up. In the following, tzinfo.dst()
3912 # implementation may return None or not None depending on
3913 # whether DST is assumed to be in effect. In this situation,
3914 # a ValueError should be raised by astimezone().
3915 class tricky_notok(ok):
3916 def dst(self, dt):
3917 if dt.year == 2000:
3918 return None
3919 else:
3920 return 10*HOUR
3921 dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
3922 self.assertRaises(ValueError, dt.astimezone, tricky_notok())
3923
3924 def test_fromutc(self):
3925 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
3926 now = datetime.utcnow().replace(tzinfo=utc_real)
3927 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3928 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
3929 enow = Eastern.fromutc(now) # doesn't blow up
3930 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3931 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3932 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3933
3934 # Always converts UTC to standard time.
3935 class FauxUSTimeZone(USTimeZone):
3936 def fromutc(self, dt):
3937 return dt + self.stdoffset
3938 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
3939
3940 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
3941 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
3942 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
3943
3944 # Check around DST start.
3945 start = self.dston.replace(hour=4, tzinfo=Eastern)
3946 fstart = start.replace(tzinfo=FEastern)
3947 for wall in 23, 0, 1, 3, 4, 5:
3948 expected = start.replace(hour=wall)
3949 if wall == 23:
3950 expected -= timedelta(days=1)
3951 got = Eastern.fromutc(start)
3952 self.assertEqual(expected, got)
3953
3954 expected = fstart + FEastern.stdoffset
3955 got = FEastern.fromutc(fstart)
3956 self.assertEqual(expected, got)
3957
3958 # Ensure astimezone() calls fromutc() too.
3959 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3960 self.assertEqual(expected, got)
3961
3962 start += HOUR
3963 fstart += HOUR
3964
3965 # Check around DST end.
3966 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
3967 fstart = start.replace(tzinfo=FEastern)
3968 for wall in 0, 1, 1, 2, 3, 4:
3969 expected = start.replace(hour=wall)
3970 got = Eastern.fromutc(start)
3971 self.assertEqual(expected, got)
3972
3973 expected = fstart + FEastern.stdoffset
3974 got = FEastern.fromutc(fstart)
3975 self.assertEqual(expected, got)
3976
3977 # Ensure astimezone() calls fromutc() too.
3978 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3979 self.assertEqual(expected, got)
3980
3981 start += HOUR
3982 fstart += HOUR
3983
3984
3985#############################################################################
3986# oddballs
3987
3988class Oddballs(unittest.TestCase):
3989
3990 def test_bug_1028306(self):
3991 # Trying to compare a date to a datetime should act like a mixed-
3992 # type comparison, despite that datetime is a subclass of date.
3993 as_date = date.today()
3994 as_datetime = datetime.combine(as_date, time())
3995 self.assertTrue(as_date != as_datetime)
3996 self.assertTrue(as_datetime != as_date)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003997 self.assertFalse(as_date == as_datetime)
3998 self.assertFalse(as_datetime == as_date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003999 self.assertRaises(TypeError, lambda: as_date < as_datetime)
4000 self.assertRaises(TypeError, lambda: as_datetime < as_date)
4001 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
4002 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
4003 self.assertRaises(TypeError, lambda: as_date > as_datetime)
4004 self.assertRaises(TypeError, lambda: as_datetime > as_date)
4005 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
4006 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
4007
Raymond Hettinger15f44ab2016-08-30 10:47:49 -07004008 # Nevertheless, comparison should work with the base-class (date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004009 # projection if use of a date method is forced.
4010 self.assertEqual(as_date.__eq__(as_datetime), True)
4011 different_day = (as_date.day + 1) % 20 + 1
4012 as_different = as_datetime.replace(day= different_day)
4013 self.assertEqual(as_date.__eq__(as_different), False)
4014
4015 # And date should compare with other subclasses of date. If a
4016 # subclass wants to stop this, it's up to the subclass to do so.
4017 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
4018 self.assertEqual(as_date, date_sc)
4019 self.assertEqual(date_sc, as_date)
4020
4021 # Ditto for datetimes.
4022 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
4023 as_date.day, 0, 0, 0)
4024 self.assertEqual(as_datetime, datetime_sc)
4025 self.assertEqual(datetime_sc, as_datetime)
4026
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004027 def test_extra_attributes(self):
4028 for x in [date.today(),
4029 time(),
4030 datetime.utcnow(),
4031 timedelta(),
4032 tzinfo(),
4033 timezone(timedelta())]:
4034 with self.assertRaises(AttributeError):
4035 x.abc = 1
4036
4037 def test_check_arg_types(self):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004038 class Number:
4039 def __init__(self, value):
4040 self.value = value
4041 def __int__(self):
4042 return self.value
4043
4044 for xx in [decimal.Decimal(10),
4045 decimal.Decimal('10.9'),
4046 Number(10)]:
4047 self.assertEqual(datetime(10, 10, 10, 10, 10, 10, 10),
4048 datetime(xx, xx, xx, xx, xx, xx, xx))
4049
4050 with self.assertRaisesRegex(TypeError, '^an integer is required '
R David Murray44b548d2016-09-08 13:59:53 -04004051 r'\(got type str\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004052 datetime(10, 10, '10')
4053
4054 f10 = Number(10.9)
4055 with self.assertRaisesRegex(TypeError, '^__int__ returned non-int '
R David Murray44b548d2016-09-08 13:59:53 -04004056 r'\(type float\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004057 datetime(10, 10, f10)
4058
4059 class Float(float):
4060 pass
4061 s10 = Float(10.9)
4062 with self.assertRaisesRegex(TypeError, '^integer argument expected, '
4063 'got float$'):
4064 datetime(10, 10, s10)
4065
4066 with self.assertRaises(TypeError):
4067 datetime(10., 10, 10)
4068 with self.assertRaises(TypeError):
4069 datetime(10, 10., 10)
4070 with self.assertRaises(TypeError):
4071 datetime(10, 10, 10.)
4072 with self.assertRaises(TypeError):
4073 datetime(10, 10, 10, 10.)
4074 with self.assertRaises(TypeError):
4075 datetime(10, 10, 10, 10, 10.)
4076 with self.assertRaises(TypeError):
4077 datetime(10, 10, 10, 10, 10, 10.)
4078 with self.assertRaises(TypeError):
4079 datetime(10, 10, 10, 10, 10, 10, 10.)
4080
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004081#############################################################################
4082# Local Time Disambiguation
4083
4084# An experimental reimplementation of fromutc that respects the "fold" flag.
4085
4086class tzinfo2(tzinfo):
4087
4088 def fromutc(self, dt):
4089 "datetime in UTC -> datetime in local time."
4090
4091 if not isinstance(dt, datetime):
4092 raise TypeError("fromutc() requires a datetime argument")
4093 if dt.tzinfo is not self:
4094 raise ValueError("dt.tzinfo is not self")
4095 # Returned value satisfies
4096 # dt + ldt.utcoffset() = ldt
4097 off0 = dt.replace(fold=0).utcoffset()
4098 off1 = dt.replace(fold=1).utcoffset()
4099 if off0 is None or off1 is None or dt.dst() is None:
4100 raise ValueError
4101 if off0 == off1:
4102 ldt = dt + off0
4103 off1 = ldt.utcoffset()
4104 if off0 == off1:
4105 return ldt
4106 # Now, we discovered both possible offsets, so
4107 # we can just try four possible solutions:
4108 for off in [off0, off1]:
4109 ldt = dt + off
4110 if ldt.utcoffset() == off:
4111 return ldt
4112 ldt = ldt.replace(fold=1)
4113 if ldt.utcoffset() == off:
4114 return ldt
4115
4116 raise ValueError("No suitable local time found")
4117
4118# Reimplementing simplified US timezones to respect the "fold" flag:
4119
4120class USTimeZone2(tzinfo2):
4121
4122 def __init__(self, hours, reprname, stdname, dstname):
4123 self.stdoffset = timedelta(hours=hours)
4124 self.reprname = reprname
4125 self.stdname = stdname
4126 self.dstname = dstname
4127
4128 def __repr__(self):
4129 return self.reprname
4130
4131 def tzname(self, dt):
4132 if self.dst(dt):
4133 return self.dstname
4134 else:
4135 return self.stdname
4136
4137 def utcoffset(self, dt):
4138 return self.stdoffset + self.dst(dt)
4139
4140 def dst(self, dt):
4141 if dt is None or dt.tzinfo is None:
4142 # An exception instead may be sensible here, in one or more of
4143 # the cases.
4144 return ZERO
4145 assert dt.tzinfo is self
4146
4147 # Find first Sunday in April.
4148 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
4149 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
4150
4151 # Find last Sunday in October.
4152 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
4153 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
4154
4155 # Can't compare naive to aware objects, so strip the timezone from
4156 # dt first.
4157 dt = dt.replace(tzinfo=None)
4158 if start + HOUR <= dt < end:
4159 # DST is in effect.
4160 return HOUR
4161 elif end <= dt < end + HOUR:
4162 # Fold (an ambiguous hour): use dt.fold to disambiguate.
4163 return ZERO if dt.fold else HOUR
4164 elif start <= dt < start + HOUR:
4165 # Gap (a non-existent hour): reverse the fold rule.
4166 return HOUR if dt.fold else ZERO
4167 else:
4168 # DST is off.
4169 return ZERO
4170
4171Eastern2 = USTimeZone2(-5, "Eastern2", "EST", "EDT")
4172Central2 = USTimeZone2(-6, "Central2", "CST", "CDT")
4173Mountain2 = USTimeZone2(-7, "Mountain2", "MST", "MDT")
4174Pacific2 = USTimeZone2(-8, "Pacific2", "PST", "PDT")
4175
4176# Europe_Vilnius_1941 tzinfo implementation reproduces the following
4177# 1941 transition from Olson's tzdist:
4178#
4179# Zone NAME GMTOFF RULES FORMAT [UNTIL]
4180# ZoneEurope/Vilnius 1:00 - CET 1940 Aug 3
4181# 3:00 - MSK 1941 Jun 24
4182# 1:00 C-Eur CE%sT 1944 Aug
4183#
4184# $ zdump -v Europe/Vilnius | grep 1941
4185# Europe/Vilnius Mon Jun 23 20:59:59 1941 UTC = Mon Jun 23 23:59:59 1941 MSK isdst=0 gmtoff=10800
4186# Europe/Vilnius Mon Jun 23 21:00:00 1941 UTC = Mon Jun 23 23:00:00 1941 CEST isdst=1 gmtoff=7200
4187
4188class Europe_Vilnius_1941(tzinfo):
4189 def _utc_fold(self):
4190 return [datetime(1941, 6, 23, 21, tzinfo=self), # Mon Jun 23 21:00:00 1941 UTC
4191 datetime(1941, 6, 23, 22, tzinfo=self)] # Mon Jun 23 22:00:00 1941 UTC
4192
4193 def _loc_fold(self):
4194 return [datetime(1941, 6, 23, 23, tzinfo=self), # Mon Jun 23 23:00:00 1941 MSK / CEST
4195 datetime(1941, 6, 24, 0, tzinfo=self)] # Mon Jun 24 00:00:00 1941 CEST
4196
4197 def utcoffset(self, dt):
4198 fold_start, fold_stop = self._loc_fold()
4199 if dt < fold_start:
4200 return 3 * HOUR
4201 if dt < fold_stop:
4202 return (2 if dt.fold else 3) * HOUR
4203 # if dt >= fold_stop
4204 return 2 * HOUR
4205
4206 def dst(self, dt):
4207 fold_start, fold_stop = self._loc_fold()
4208 if dt < fold_start:
4209 return 0 * HOUR
4210 if dt < fold_stop:
4211 return (1 if dt.fold else 0) * HOUR
4212 # if dt >= fold_stop
4213 return 1 * HOUR
4214
4215 def tzname(self, dt):
4216 fold_start, fold_stop = self._loc_fold()
4217 if dt < fold_start:
4218 return 'MSK'
4219 if dt < fold_stop:
4220 return ('MSK', 'CEST')[dt.fold]
4221 # if dt >= fold_stop
4222 return 'CEST'
4223
4224 def fromutc(self, dt):
4225 assert dt.fold == 0
4226 assert dt.tzinfo is self
4227 if dt.year != 1941:
4228 raise NotImplementedError
4229 fold_start, fold_stop = self._utc_fold()
4230 if dt < fold_start:
4231 return dt + 3 * HOUR
4232 if dt < fold_stop:
4233 return (dt + 2 * HOUR).replace(fold=1)
4234 # if dt >= fold_stop
4235 return dt + 2 * HOUR
4236
4237
4238class TestLocalTimeDisambiguation(unittest.TestCase):
4239
4240 def test_vilnius_1941_fromutc(self):
4241 Vilnius = Europe_Vilnius_1941()
4242
4243 gdt = datetime(1941, 6, 23, 20, 59, 59, tzinfo=timezone.utc)
4244 ldt = gdt.astimezone(Vilnius)
4245 self.assertEqual(ldt.strftime("%c %Z%z"),
4246 'Mon Jun 23 23:59:59 1941 MSK+0300')
4247 self.assertEqual(ldt.fold, 0)
4248 self.assertFalse(ldt.dst())
4249
4250 gdt = datetime(1941, 6, 23, 21, tzinfo=timezone.utc)
4251 ldt = gdt.astimezone(Vilnius)
4252 self.assertEqual(ldt.strftime("%c %Z%z"),
4253 'Mon Jun 23 23:00:00 1941 CEST+0200')
4254 self.assertEqual(ldt.fold, 1)
4255 self.assertTrue(ldt.dst())
4256
4257 gdt = datetime(1941, 6, 23, 22, tzinfo=timezone.utc)
4258 ldt = gdt.astimezone(Vilnius)
4259 self.assertEqual(ldt.strftime("%c %Z%z"),
4260 'Tue Jun 24 00:00:00 1941 CEST+0200')
4261 self.assertEqual(ldt.fold, 0)
4262 self.assertTrue(ldt.dst())
4263
4264 def test_vilnius_1941_toutc(self):
4265 Vilnius = Europe_Vilnius_1941()
4266
4267 ldt = datetime(1941, 6, 23, 22, 59, 59, tzinfo=Vilnius)
4268 gdt = ldt.astimezone(timezone.utc)
4269 self.assertEqual(gdt.strftime("%c %Z"),
4270 'Mon Jun 23 19:59:59 1941 UTC')
4271
4272 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius)
4273 gdt = ldt.astimezone(timezone.utc)
4274 self.assertEqual(gdt.strftime("%c %Z"),
4275 'Mon Jun 23 20:59:59 1941 UTC')
4276
4277 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius, fold=1)
4278 gdt = ldt.astimezone(timezone.utc)
4279 self.assertEqual(gdt.strftime("%c %Z"),
4280 'Mon Jun 23 21:59:59 1941 UTC')
4281
4282 ldt = datetime(1941, 6, 24, 0, tzinfo=Vilnius)
4283 gdt = ldt.astimezone(timezone.utc)
4284 self.assertEqual(gdt.strftime("%c %Z"),
4285 'Mon Jun 23 22:00:00 1941 UTC')
4286
4287
4288 def test_constructors(self):
4289 t = time(0, fold=1)
4290 dt = datetime(1, 1, 1, fold=1)
4291 self.assertEqual(t.fold, 1)
4292 self.assertEqual(dt.fold, 1)
4293 with self.assertRaises(TypeError):
4294 time(0, 0, 0, 0, None, 0)
4295
4296 def test_member(self):
4297 dt = datetime(1, 1, 1, fold=1)
4298 t = dt.time()
4299 self.assertEqual(t.fold, 1)
4300 t = dt.timetz()
4301 self.assertEqual(t.fold, 1)
4302
4303 def test_replace(self):
4304 t = time(0)
4305 dt = datetime(1, 1, 1)
4306 self.assertEqual(t.replace(fold=1).fold, 1)
4307 self.assertEqual(dt.replace(fold=1).fold, 1)
4308 self.assertEqual(t.replace(fold=0).fold, 0)
4309 self.assertEqual(dt.replace(fold=0).fold, 0)
4310 # Check that replacement of other fields does not change "fold".
4311 t = t.replace(fold=1, tzinfo=Eastern)
4312 dt = dt.replace(fold=1, tzinfo=Eastern)
4313 self.assertEqual(t.replace(tzinfo=None).fold, 1)
4314 self.assertEqual(dt.replace(tzinfo=None).fold, 1)
4315 # Check that fold is a keyword-only argument
4316 with self.assertRaises(TypeError):
4317 t.replace(1, 1, 1, None, 1)
4318 with self.assertRaises(TypeError):
4319 dt.replace(1, 1, 1, 1, 1, 1, 1, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04004320
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004321 def test_comparison(self):
4322 t = time(0)
4323 dt = datetime(1, 1, 1)
4324 self.assertEqual(t, t.replace(fold=1))
4325 self.assertEqual(dt, dt.replace(fold=1))
4326
4327 def test_hash(self):
4328 t = time(0)
4329 dt = datetime(1, 1, 1)
4330 self.assertEqual(hash(t), hash(t.replace(fold=1)))
4331 self.assertEqual(hash(dt), hash(dt.replace(fold=1)))
4332
4333 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4334 def test_fromtimestamp(self):
4335 s = 1414906200
4336 dt0 = datetime.fromtimestamp(s)
4337 dt1 = datetime.fromtimestamp(s + 3600)
4338 self.assertEqual(dt0.fold, 0)
4339 self.assertEqual(dt1.fold, 1)
4340
4341 @support.run_with_tz('Australia/Lord_Howe')
4342 def test_fromtimestamp_lord_howe(self):
4343 tm = _time.localtime(1.4e9)
4344 if _time.strftime('%Z%z', tm) != 'LHST+1030':
4345 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
4346 # $ TZ=Australia/Lord_Howe date -r 1428158700
4347 # Sun Apr 5 01:45:00 LHDT 2015
4348 # $ TZ=Australia/Lord_Howe date -r 1428160500
4349 # Sun Apr 5 01:45:00 LHST 2015
4350 s = 1428158700
4351 t0 = datetime.fromtimestamp(s)
4352 t1 = datetime.fromtimestamp(s + 1800)
4353 self.assertEqual(t0, t1)
4354 self.assertEqual(t0.fold, 0)
4355 self.assertEqual(t1.fold, 1)
4356
4357
4358 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4359 def test_timestamp(self):
4360 dt0 = datetime(2014, 11, 2, 1, 30)
4361 dt1 = dt0.replace(fold=1)
4362 self.assertEqual(dt0.timestamp() + 3600,
4363 dt1.timestamp())
4364
4365 @support.run_with_tz('Australia/Lord_Howe')
4366 def test_timestamp_lord_howe(self):
4367 tm = _time.localtime(1.4e9)
4368 if _time.strftime('%Z%z', tm) != 'LHST+1030':
4369 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
4370 t = datetime(2015, 4, 5, 1, 45)
4371 s0 = t.replace(fold=0).timestamp()
4372 s1 = t.replace(fold=1).timestamp()
4373 self.assertEqual(s0 + 1800, s1)
4374
4375
4376 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4377 def test_astimezone(self):
4378 dt0 = datetime(2014, 11, 2, 1, 30)
4379 dt1 = dt0.replace(fold=1)
4380 # Convert both naive instances to aware.
4381 adt0 = dt0.astimezone()
4382 adt1 = dt1.astimezone()
4383 # Check that the first instance in DST zone and the second in STD
4384 self.assertEqual(adt0.tzname(), 'EDT')
4385 self.assertEqual(adt1.tzname(), 'EST')
4386 self.assertEqual(adt0 + HOUR, adt1)
4387 # Aware instances with fixed offset tzinfo's always have fold=0
4388 self.assertEqual(adt0.fold, 0)
4389 self.assertEqual(adt1.fold, 0)
4390
4391
4392 def test_pickle_fold(self):
4393 t = time(fold=1)
4394 dt = datetime(1, 1, 1, fold=1)
4395 for pickler, unpickler, proto in pickle_choices:
4396 for x in [t, dt]:
4397 s = pickler.dumps(x, proto)
4398 y = unpickler.loads(s)
4399 self.assertEqual(x, y)
4400 self.assertEqual((0 if proto < 4 else x.fold), y.fold)
4401
4402 def test_repr(self):
4403 t = time(fold=1)
4404 dt = datetime(1, 1, 1, fold=1)
4405 self.assertEqual(repr(t), 'datetime.time(0, 0, fold=1)')
4406 self.assertEqual(repr(dt),
4407 'datetime.datetime(1, 1, 1, 0, 0, fold=1)')
4408
4409 def test_dst(self):
4410 # Let's first establish that things work in regular times.
4411 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
4412 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
4413 self.assertEqual(dt_summer.dst(), HOUR)
4414 self.assertEqual(dt_winter.dst(), ZERO)
4415 # The disambiguation flag is ignored
4416 self.assertEqual(dt_summer.replace(fold=1).dst(), HOUR)
4417 self.assertEqual(dt_winter.replace(fold=1).dst(), ZERO)
4418
4419 # Pick local time in the fold.
4420 for minute in [0, 30, 59]:
4421 dt = datetime(2002, 10, 27, 1, minute, tzinfo=Eastern2)
4422 # With fold=0 (the default) it is in DST.
4423 self.assertEqual(dt.dst(), HOUR)
4424 # With fold=1 it is in STD.
4425 self.assertEqual(dt.replace(fold=1).dst(), ZERO)
4426
4427 # Pick local time in the gap.
4428 for minute in [0, 30, 59]:
4429 dt = datetime(2002, 4, 7, 2, minute, tzinfo=Eastern2)
4430 # With fold=0 (the default) it is in STD.
4431 self.assertEqual(dt.dst(), ZERO)
4432 # With fold=1 it is in DST.
4433 self.assertEqual(dt.replace(fold=1).dst(), HOUR)
4434
4435
4436 def test_utcoffset(self):
4437 # Let's first establish that things work in regular times.
4438 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
4439 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
4440 self.assertEqual(dt_summer.utcoffset(), -4 * HOUR)
4441 self.assertEqual(dt_winter.utcoffset(), -5 * HOUR)
4442 # The disambiguation flag is ignored
4443 self.assertEqual(dt_summer.replace(fold=1).utcoffset(), -4 * HOUR)
4444 self.assertEqual(dt_winter.replace(fold=1).utcoffset(), -5 * HOUR)
4445
4446 def test_fromutc(self):
4447 # Let's first establish that things work in regular times.
4448 u_summer = datetime(2002, 10, 27, 6, tzinfo=Eastern2) - timedelta.resolution
4449 u_winter = datetime(2002, 10, 27, 7, tzinfo=Eastern2)
4450 t_summer = Eastern2.fromutc(u_summer)
4451 t_winter = Eastern2.fromutc(u_winter)
4452 self.assertEqual(t_summer, u_summer - 4 * HOUR)
4453 self.assertEqual(t_winter, u_winter - 5 * HOUR)
4454 self.assertEqual(t_summer.fold, 0)
4455 self.assertEqual(t_winter.fold, 0)
4456
4457 # What happens in the fall-back fold?
4458 u = datetime(2002, 10, 27, 5, 30, tzinfo=Eastern2)
4459 t0 = Eastern2.fromutc(u)
4460 u += HOUR
4461 t1 = Eastern2.fromutc(u)
4462 self.assertEqual(t0, t1)
4463 self.assertEqual(t0.fold, 0)
4464 self.assertEqual(t1.fold, 1)
4465 # The tricky part is when u is in the local fold:
4466 u = datetime(2002, 10, 27, 1, 30, tzinfo=Eastern2)
4467 t = Eastern2.fromutc(u)
4468 self.assertEqual((t.day, t.hour), (26, 21))
4469 # .. or gets into the local fold after a standard time adjustment
4470 u = datetime(2002, 10, 27, 6, 30, tzinfo=Eastern2)
4471 t = Eastern2.fromutc(u)
4472 self.assertEqual((t.day, t.hour), (27, 1))
4473
4474 # What happens in the spring-forward gap?
4475 u = datetime(2002, 4, 7, 2, 0, tzinfo=Eastern2)
4476 t = Eastern2.fromutc(u)
4477 self.assertEqual((t.day, t.hour), (6, 21))
4478
4479 def test_mixed_compare_regular(self):
4480 t = datetime(2000, 1, 1, tzinfo=Eastern2)
4481 self.assertEqual(t, t.astimezone(timezone.utc))
4482 t = datetime(2000, 6, 1, tzinfo=Eastern2)
4483 self.assertEqual(t, t.astimezone(timezone.utc))
4484
4485 def test_mixed_compare_fold(self):
4486 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
4487 t_fold_utc = t_fold.astimezone(timezone.utc)
4488 self.assertNotEqual(t_fold, t_fold_utc)
4489
4490 def test_mixed_compare_gap(self):
4491 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
4492 t_gap_utc = t_gap.astimezone(timezone.utc)
4493 self.assertNotEqual(t_gap, t_gap_utc)
4494
4495 def test_hash_aware(self):
4496 t = datetime(2000, 1, 1, tzinfo=Eastern2)
4497 self.assertEqual(hash(t), hash(t.replace(fold=1)))
4498 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
4499 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
4500 self.assertEqual(hash(t_fold), hash(t_fold.replace(fold=1)))
4501 self.assertEqual(hash(t_gap), hash(t_gap.replace(fold=1)))
4502
4503SEC = timedelta(0, 1)
4504
4505def pairs(iterable):
4506 a, b = itertools.tee(iterable)
4507 next(b, None)
4508 return zip(a, b)
4509
4510class ZoneInfo(tzinfo):
4511 zoneroot = '/usr/share/zoneinfo'
4512 def __init__(self, ut, ti):
4513 """
4514
4515 :param ut: array
4516 Array of transition point timestamps
4517 :param ti: list
4518 A list of (offset, isdst, abbr) tuples
4519 :return: None
4520 """
4521 self.ut = ut
4522 self.ti = ti
4523 self.lt = self.invert(ut, ti)
4524
4525 @staticmethod
4526 def invert(ut, ti):
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04004527 lt = (array('q', ut), array('q', ut))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004528 if ut:
4529 offset = ti[0][0] // SEC
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04004530 lt[0][0] += offset
4531 lt[1][0] += offset
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004532 for i in range(1, len(ut)):
4533 lt[0][i] += ti[i-1][0] // SEC
4534 lt[1][i] += ti[i][0] // SEC
4535 return lt
4536
4537 @classmethod
4538 def fromfile(cls, fileobj):
4539 if fileobj.read(4).decode() != "TZif":
4540 raise ValueError("not a zoneinfo file")
4541 fileobj.seek(32)
4542 counts = array('i')
4543 counts.fromfile(fileobj, 3)
4544 if sys.byteorder != 'big':
4545 counts.byteswap()
4546
4547 ut = array('i')
4548 ut.fromfile(fileobj, counts[0])
4549 if sys.byteorder != 'big':
4550 ut.byteswap()
4551
4552 type_indices = array('B')
4553 type_indices.fromfile(fileobj, counts[0])
4554
4555 ttis = []
4556 for i in range(counts[1]):
4557 ttis.append(struct.unpack(">lbb", fileobj.read(6)))
4558
4559 abbrs = fileobj.read(counts[2])
4560
4561 # Convert ttis
4562 for i, (gmtoff, isdst, abbrind) in enumerate(ttis):
4563 abbr = abbrs[abbrind:abbrs.find(0, abbrind)].decode()
4564 ttis[i] = (timedelta(0, gmtoff), isdst, abbr)
4565
4566 ti = [None] * len(ut)
4567 for i, idx in enumerate(type_indices):
4568 ti[i] = ttis[idx]
4569
4570 self = cls(ut, ti)
4571
4572 return self
4573
4574 @classmethod
4575 def fromname(cls, name):
4576 path = os.path.join(cls.zoneroot, name)
4577 with open(path, 'rb') as f:
4578 return cls.fromfile(f)
4579
4580 EPOCHORDINAL = date(1970, 1, 1).toordinal()
4581
4582 def fromutc(self, dt):
4583 """datetime in UTC -> datetime in local time."""
4584
4585 if not isinstance(dt, datetime):
4586 raise TypeError("fromutc() requires a datetime argument")
4587 if dt.tzinfo is not self:
4588 raise ValueError("dt.tzinfo is not self")
4589
4590 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
4591 + dt.hour * 3600
4592 + dt.minute * 60
4593 + dt.second)
4594
4595 if timestamp < self.ut[1]:
4596 tti = self.ti[0]
4597 fold = 0
4598 else:
4599 idx = bisect.bisect_right(self.ut, timestamp)
4600 assert self.ut[idx-1] <= timestamp
4601 assert idx == len(self.ut) or timestamp < self.ut[idx]
4602 tti_prev, tti = self.ti[idx-2:idx]
4603 # Detect fold
4604 shift = tti_prev[0] - tti[0]
4605 fold = (shift > timedelta(0, timestamp - self.ut[idx-1]))
4606 dt += tti[0]
4607 if fold:
4608 return dt.replace(fold=1)
4609 else:
4610 return dt
4611
4612 def _find_ti(self, dt, i):
4613 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
4614 + dt.hour * 3600
4615 + dt.minute * 60
4616 + dt.second)
4617 lt = self.lt[dt.fold]
4618 idx = bisect.bisect_right(lt, timestamp)
4619
4620 return self.ti[max(0, idx - 1)][i]
4621
4622 def utcoffset(self, dt):
4623 return self._find_ti(dt, 0)
4624
4625 def dst(self, dt):
4626 isdst = self._find_ti(dt, 1)
4627 # XXX: We cannot accurately determine the "save" value,
4628 # so let's return 1h whenever DST is in effect. Since
4629 # we don't use dst() in fromutc(), it is unlikely that
4630 # it will be needed for anything more than bool(dst()).
4631 return ZERO if isdst else HOUR
4632
4633 def tzname(self, dt):
4634 return self._find_ti(dt, 2)
4635
4636 @classmethod
4637 def zonenames(cls, zonedir=None):
4638 if zonedir is None:
4639 zonedir = cls.zoneroot
Alexander Belopolsky1b8f26c2016-08-11 11:01:45 -04004640 zone_tab = os.path.join(zonedir, 'zone.tab')
4641 try:
4642 f = open(zone_tab)
4643 except OSError:
4644 return
4645 with f:
4646 for line in f:
4647 line = line.strip()
4648 if line and not line.startswith('#'):
4649 yield line.split()[2]
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004650
4651 @classmethod
4652 def stats(cls, start_year=1):
4653 count = gap_count = fold_count = zeros_count = 0
4654 min_gap = min_fold = timedelta.max
4655 max_gap = max_fold = ZERO
4656 min_gap_datetime = max_gap_datetime = datetime.min
4657 min_gap_zone = max_gap_zone = None
4658 min_fold_datetime = max_fold_datetime = datetime.min
4659 min_fold_zone = max_fold_zone = None
4660 stats_since = datetime(start_year, 1, 1) # Starting from 1970 eliminates a lot of noise
4661 for zonename in cls.zonenames():
4662 count += 1
4663 tz = cls.fromname(zonename)
4664 for dt, shift in tz.transitions():
4665 if dt < stats_since:
4666 continue
4667 if shift > ZERO:
4668 gap_count += 1
4669 if (shift, dt) > (max_gap, max_gap_datetime):
4670 max_gap = shift
4671 max_gap_zone = zonename
4672 max_gap_datetime = dt
4673 if (shift, datetime.max - dt) < (min_gap, datetime.max - min_gap_datetime):
4674 min_gap = shift
4675 min_gap_zone = zonename
4676 min_gap_datetime = dt
4677 elif shift < ZERO:
4678 fold_count += 1
4679 shift = -shift
4680 if (shift, dt) > (max_fold, max_fold_datetime):
4681 max_fold = shift
4682 max_fold_zone = zonename
4683 max_fold_datetime = dt
4684 if (shift, datetime.max - dt) < (min_fold, datetime.max - min_fold_datetime):
4685 min_fold = shift
4686 min_fold_zone = zonename
4687 min_fold_datetime = dt
4688 else:
4689 zeros_count += 1
4690 trans_counts = (gap_count, fold_count, zeros_count)
4691 print("Number of zones: %5d" % count)
4692 print("Number of transitions: %5d = %d (gaps) + %d (folds) + %d (zeros)" %
4693 ((sum(trans_counts),) + trans_counts))
4694 print("Min gap: %16s at %s in %s" % (min_gap, min_gap_datetime, min_gap_zone))
4695 print("Max gap: %16s at %s in %s" % (max_gap, max_gap_datetime, max_gap_zone))
4696 print("Min fold: %16s at %s in %s" % (min_fold, min_fold_datetime, min_fold_zone))
4697 print("Max fold: %16s at %s in %s" % (max_fold, max_fold_datetime, max_fold_zone))
4698
4699
4700 def transitions(self):
4701 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
4702 shift = ti[0] - prev_ti[0]
4703 yield datetime.utcfromtimestamp(t), shift
4704
4705 def nondst_folds(self):
4706 """Find all folds with the same value of isdst on both sides of the transition."""
4707 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
4708 shift = ti[0] - prev_ti[0]
4709 if shift < ZERO and ti[1] == prev_ti[1]:
4710 yield datetime.utcfromtimestamp(t), -shift, prev_ti[2], ti[2]
4711
4712 @classmethod
4713 def print_all_nondst_folds(cls, same_abbr=False, start_year=1):
4714 count = 0
4715 for zonename in cls.zonenames():
4716 tz = cls.fromname(zonename)
4717 for dt, shift, prev_abbr, abbr in tz.nondst_folds():
4718 if dt.year < start_year or same_abbr and prev_abbr != abbr:
4719 continue
4720 count += 1
4721 print("%3d) %-30s %s %10s %5s -> %s" %
4722 (count, zonename, dt, shift, prev_abbr, abbr))
4723
4724 def folds(self):
4725 for t, shift in self.transitions():
4726 if shift < ZERO:
4727 yield t, -shift
4728
4729 def gaps(self):
4730 for t, shift in self.transitions():
4731 if shift > ZERO:
4732 yield t, shift
4733
4734 def zeros(self):
4735 for t, shift in self.transitions():
4736 if not shift:
4737 yield t
4738
4739
4740class ZoneInfoTest(unittest.TestCase):
4741 zonename = 'America/New_York'
4742
4743 def setUp(self):
4744 if sys.platform == "win32":
4745 self.skipTest("Skipping zoneinfo tests on Windows")
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04004746 try:
4747 self.tz = ZoneInfo.fromname(self.zonename)
4748 except FileNotFoundError as err:
4749 self.skipTest("Skipping %s: %s" % (self.zonename, err))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004750
4751 def assertEquivDatetimes(self, a, b):
4752 self.assertEqual((a.replace(tzinfo=None), a.fold, id(a.tzinfo)),
4753 (b.replace(tzinfo=None), b.fold, id(b.tzinfo)))
4754
4755 def test_folds(self):
4756 tz = self.tz
4757 for dt, shift in tz.folds():
4758 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
4759 udt = dt + x
4760 ldt = tz.fromutc(udt.replace(tzinfo=tz))
4761 self.assertEqual(ldt.fold, 1)
4762 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
4763 self.assertEquivDatetimes(adt, ldt)
4764 utcoffset = ldt.utcoffset()
4765 self.assertEqual(ldt.replace(tzinfo=None), udt + utcoffset)
4766 # Round trip
4767 self.assertEquivDatetimes(ldt.astimezone(timezone.utc),
4768 udt.replace(tzinfo=timezone.utc))
4769
4770
4771 for x in [-timedelta.resolution, shift]:
4772 udt = dt + x
4773 udt = udt.replace(tzinfo=tz)
4774 ldt = tz.fromutc(udt)
4775 self.assertEqual(ldt.fold, 0)
4776
4777 def test_gaps(self):
4778 tz = self.tz
4779 for dt, shift in tz.gaps():
4780 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
4781 udt = dt + x
4782 udt = udt.replace(tzinfo=tz)
4783 ldt = tz.fromutc(udt)
4784 self.assertEqual(ldt.fold, 0)
4785 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
4786 self.assertEquivDatetimes(adt, ldt)
4787 utcoffset = ldt.utcoffset()
4788 self.assertEqual(ldt.replace(tzinfo=None), udt.replace(tzinfo=None) + utcoffset)
4789 # Create a local time inside the gap
4790 ldt = tz.fromutc(dt.replace(tzinfo=tz)) - shift + x
4791 self.assertLess(ldt.replace(fold=1).utcoffset(),
4792 ldt.replace(fold=0).utcoffset(),
4793 "At %s." % ldt)
4794
4795 for x in [-timedelta.resolution, shift]:
4796 udt = dt + x
4797 ldt = tz.fromutc(udt.replace(tzinfo=tz))
4798 self.assertEqual(ldt.fold, 0)
4799
4800 def test_system_transitions(self):
4801 if ('Riyadh8' in self.zonename or
4802 # From tzdata NEWS file:
4803 # The files solar87, solar88, and solar89 are no longer distributed.
4804 # They were a negative experiment - that is, a demonstration that
4805 # tz data can represent solar time only with some difficulty and error.
4806 # Their presence in the distribution caused confusion, as Riyadh
4807 # civil time was generally not solar time in those years.
4808 self.zonename.startswith('right/')):
4809 self.skipTest("Skipping %s" % self.zonename)
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04004810 tz = self.tz
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004811 TZ = os.environ.get('TZ')
4812 os.environ['TZ'] = self.zonename
4813 try:
4814 _time.tzset()
4815 for udt, shift in tz.transitions():
Alexander Belopolsky10c2dd22016-08-12 19:08:15 -04004816 if udt.year >= 2037:
4817 # System support for times around the end of 32-bit time_t
4818 # and later is flaky on many systems.
4819 break
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004820 s0 = (udt - datetime(1970, 1, 1)) // SEC
4821 ss = shift // SEC # shift seconds
4822 for x in [-40 * 3600, -20*3600, -1, 0,
4823 ss - 1, ss + 20 * 3600, ss + 40 * 3600]:
4824 s = s0 + x
4825 sdt = datetime.fromtimestamp(s)
4826 tzdt = datetime.fromtimestamp(s, tz).replace(tzinfo=None)
4827 self.assertEquivDatetimes(sdt, tzdt)
4828 s1 = sdt.timestamp()
4829 self.assertEqual(s, s1)
4830 if ss > 0: # gap
4831 # Create local time inside the gap
4832 dt = datetime.fromtimestamp(s0) - shift / 2
4833 ts0 = dt.timestamp()
4834 ts1 = dt.replace(fold=1).timestamp()
4835 self.assertEqual(ts0, s0 + ss / 2)
4836 self.assertEqual(ts1, s0 - ss / 2)
4837 finally:
4838 if TZ is None:
4839 del os.environ['TZ']
4840 else:
4841 os.environ['TZ'] = TZ
4842 _time.tzset()
4843
4844
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04004845class ZoneInfoCompleteTest(unittest.TestSuite):
4846 def __init__(self):
4847 tests = []
4848 if is_resource_enabled('tzdata'):
4849 for name in ZoneInfo.zonenames():
4850 Test = type('ZoneInfoTest[%s]' % name, (ZoneInfoTest,), {})
4851 Test.zonename = name
4852 for method in dir(Test):
4853 if method.startswith('test_'):
4854 tests.append(Test(method))
4855 super().__init__(tests)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004856
4857# Iran had a sub-minute UTC offset before 1946.
4858class IranTest(ZoneInfoTest):
Alexander Belopolsky07e2a0a2016-07-30 11:41:02 -04004859 zonename = 'Asia/Tehran'
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004860
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04004861def load_tests(loader, standard_tests, pattern):
4862 standard_tests.addTest(ZoneInfoCompleteTest())
4863 return standard_tests
4864
4865
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004866if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -05004867 unittest.main()