blob: 7feabced94af18b62068b8669e6836201271ca64 [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()
1996 # date 0001-01-01 00:00:00+00:00: timestamp=-62135596800
1997 self.assertEqual(self.theclass.fromtimestamp(min_ts, tz=timezone.utc),
1998 min_dt)
1999
2000 # maximum timestamp: set seconds to zero to avoid rounding issues
2001 max_dt = self.theclass.max.replace(tzinfo=timezone.utc,
2002 second=0, microsecond=0)
2003 max_ts = max_dt.timestamp()
2004 # date 9999-12-31 23:59:00+00:00: timestamp 253402300740
2005 self.assertEqual(self.theclass.fromtimestamp(max_ts, tz=timezone.utc),
2006 max_dt)
2007
2008 # number of seconds greater than 1 year: make sure that the new date
2009 # is not valid in datetime.datetime limits
2010 delta = 3600 * 24 * 400
2011
2012 # too small
2013 ts = min_ts - delta
2014 # converting a Python int to C time_t can raise a OverflowError,
2015 # especially on 32-bit platforms.
2016 with self.assertRaises((ValueError, OverflowError)):
2017 self.theclass.fromtimestamp(ts)
2018 with self.assertRaises((ValueError, OverflowError)):
2019 self.theclass.utcfromtimestamp(ts)
2020
2021 # too big
2022 ts = max_dt.timestamp() + delta
2023 with self.assertRaises((ValueError, OverflowError)):
2024 self.theclass.fromtimestamp(ts)
2025 with self.assertRaises((ValueError, OverflowError)):
2026 self.theclass.utcfromtimestamp(ts)
2027
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002028 def test_insane_fromtimestamp(self):
2029 # It's possible that some platform maps time_t to double,
2030 # and that this test will fail there. This test should
2031 # exempt such platforms (provided they return reasonable
2032 # results!).
2033 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002034 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002035 insane)
2036
2037 def test_insane_utcfromtimestamp(self):
2038 # It's possible that some platform maps time_t to double,
2039 # and that this test will fail there. This test should
2040 # exempt such platforms (provided they return reasonable
2041 # results!).
2042 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002043 self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002044 insane)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002045
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002046 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2047 def test_negative_float_fromtimestamp(self):
2048 # The result is tz-dependent; at least test that this doesn't
2049 # fail (like it did before bug 1646728 was fixed).
2050 self.theclass.fromtimestamp(-1.05)
2051
2052 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2053 def test_negative_float_utcfromtimestamp(self):
2054 d = self.theclass.utcfromtimestamp(-1.05)
2055 self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
2056
2057 def test_utcnow(self):
2058 import time
2059
2060 # Call it a success if utcnow() and utcfromtimestamp() are within
2061 # a second of each other.
2062 tolerance = timedelta(seconds=1)
2063 for dummy in range(3):
2064 from_now = self.theclass.utcnow()
2065 from_timestamp = self.theclass.utcfromtimestamp(time.time())
2066 if abs(from_timestamp - from_now) <= tolerance:
2067 break
2068 # Else try again a few times.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002069 self.assertLessEqual(abs(from_timestamp - from_now), tolerance)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002070
2071 def test_strptime(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002072 string = '2004-12-01 13:02:47.197'
2073 format = '%Y-%m-%d %H:%M:%S.%f'
2074 expected = _strptime._strptime_datetime(self.theclass, string, format)
2075 got = self.theclass.strptime(string, format)
2076 self.assertEqual(expected, got)
2077 self.assertIs(type(expected), self.theclass)
2078 self.assertIs(type(got), self.theclass)
2079
2080 strptime = self.theclass.strptime
2081 self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
2082 self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
2083 # Only local timezone and UTC are supported
2084 for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
2085 (-_time.timezone, _time.tzname[0])):
2086 if tzseconds < 0:
2087 sign = '-'
2088 seconds = -tzseconds
2089 else:
2090 sign ='+'
2091 seconds = tzseconds
2092 hours, minutes = divmod(seconds//60, 60)
2093 dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
Martin Panterfca22322015-11-16 09:22:19 +00002094 dt = strptime(dtstr, "%z %Z")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002095 self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
2096 self.assertEqual(dt.tzname(), tzname)
2097 # Can produce inconsistent datetime
2098 dtstr, fmt = "+1234 UTC", "%z %Z"
2099 dt = strptime(dtstr, fmt)
2100 self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
2101 self.assertEqual(dt.tzname(), 'UTC')
2102 # yet will roundtrip
2103 self.assertEqual(dt.strftime(fmt), dtstr)
2104
2105 # Produce naive datetime if no %z is provided
2106 self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
2107
2108 with self.assertRaises(ValueError): strptime("-2400", "%z")
2109 with self.assertRaises(ValueError): strptime("-000", "%z")
2110
2111 def test_more_timetuple(self):
2112 # This tests fields beyond those tested by the TestDate.test_timetuple.
2113 t = self.theclass(2004, 12, 31, 6, 22, 33)
2114 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
2115 self.assertEqual(t.timetuple(),
2116 (t.year, t.month, t.day,
2117 t.hour, t.minute, t.second,
2118 t.weekday(),
2119 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
2120 -1))
2121 tt = t.timetuple()
2122 self.assertEqual(tt.tm_year, t.year)
2123 self.assertEqual(tt.tm_mon, t.month)
2124 self.assertEqual(tt.tm_mday, t.day)
2125 self.assertEqual(tt.tm_hour, t.hour)
2126 self.assertEqual(tt.tm_min, t.minute)
2127 self.assertEqual(tt.tm_sec, t.second)
2128 self.assertEqual(tt.tm_wday, t.weekday())
2129 self.assertEqual(tt.tm_yday, t.toordinal() -
2130 date(t.year, 1, 1).toordinal() + 1)
2131 self.assertEqual(tt.tm_isdst, -1)
2132
2133 def test_more_strftime(self):
2134 # This tests fields beyond those tested by the TestDate.test_strftime.
2135 t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
2136 self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
2137 "12 31 04 000047 33 22 06 366")
2138
2139 def test_extract(self):
2140 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2141 self.assertEqual(dt.date(), date(2002, 3, 4))
2142 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2143
2144 def test_combine(self):
2145 d = date(2002, 3, 4)
2146 t = time(18, 45, 3, 1234)
2147 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2148 combine = self.theclass.combine
2149 dt = combine(d, t)
2150 self.assertEqual(dt, expected)
2151
2152 dt = combine(time=t, date=d)
2153 self.assertEqual(dt, expected)
2154
2155 self.assertEqual(d, dt.date())
2156 self.assertEqual(t, dt.time())
2157 self.assertEqual(dt, combine(dt.date(), dt.time()))
2158
2159 self.assertRaises(TypeError, combine) # need an arg
2160 self.assertRaises(TypeError, combine, d) # need two args
2161 self.assertRaises(TypeError, combine, t, d) # args reversed
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002162 self.assertRaises(TypeError, combine, d, t, 1) # wrong tzinfo type
2163 self.assertRaises(TypeError, combine, d, t, 1, 2) # too many args
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002164 self.assertRaises(TypeError, combine, "date", "time") # wrong types
2165 self.assertRaises(TypeError, combine, d, "time") # wrong type
2166 self.assertRaises(TypeError, combine, "date", t) # wrong type
2167
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002168 # tzinfo= argument
2169 dt = combine(d, t, timezone.utc)
2170 self.assertIs(dt.tzinfo, timezone.utc)
2171 dt = combine(d, t, tzinfo=timezone.utc)
2172 self.assertIs(dt.tzinfo, timezone.utc)
2173 t = time()
2174 dt = combine(dt, t)
2175 self.assertEqual(dt.date(), d)
2176 self.assertEqual(dt.time(), t)
2177
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002178 def test_replace(self):
2179 cls = self.theclass
2180 args = [1, 2, 3, 4, 5, 6, 7]
2181 base = cls(*args)
2182 self.assertEqual(base, base.replace())
2183
2184 i = 0
2185 for name, newval in (("year", 2),
2186 ("month", 3),
2187 ("day", 4),
2188 ("hour", 5),
2189 ("minute", 6),
2190 ("second", 7),
2191 ("microsecond", 8)):
2192 newargs = args[:]
2193 newargs[i] = newval
2194 expected = cls(*newargs)
2195 got = base.replace(**{name: newval})
2196 self.assertEqual(expected, got)
2197 i += 1
2198
2199 # Out of bounds.
2200 base = cls(2000, 2, 29)
2201 self.assertRaises(ValueError, base.replace, year=2001)
2202
2203 def test_astimezone(self):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002204 return # The rest is no longer applicable
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002205 # Pretty boring! The TZ test is more interesting here. astimezone()
2206 # simply can't be applied to a naive object.
2207 dt = self.theclass.now()
2208 f = FixedOffset(44, "")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04002209 self.assertRaises(ValueError, dt.astimezone) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002210 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
2211 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
2212 self.assertRaises(ValueError, dt.astimezone, f) # naive
2213 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
2214
2215 class Bogus(tzinfo):
2216 def utcoffset(self, dt): return None
2217 def dst(self, dt): return timedelta(0)
2218 bog = Bogus()
2219 self.assertRaises(ValueError, dt.astimezone, bog) # naive
2220 self.assertRaises(ValueError,
2221 dt.replace(tzinfo=bog).astimezone, f)
2222
2223 class AlsoBogus(tzinfo):
2224 def utcoffset(self, dt): return timedelta(0)
2225 def dst(self, dt): return None
2226 alsobog = AlsoBogus()
2227 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
2228
2229 def test_subclass_datetime(self):
2230
2231 class C(self.theclass):
2232 theAnswer = 42
2233
2234 def __new__(cls, *args, **kws):
2235 temp = kws.copy()
2236 extra = temp.pop('extra')
2237 result = self.theclass.__new__(cls, *args, **temp)
2238 result.extra = extra
2239 return result
2240
2241 def newmeth(self, start):
2242 return start + self.year + self.month + self.second
2243
2244 args = 2003, 4, 14, 12, 13, 41
2245
2246 dt1 = self.theclass(*args)
2247 dt2 = C(*args, **{'extra': 7})
2248
2249 self.assertEqual(dt2.__class__, C)
2250 self.assertEqual(dt2.theAnswer, 42)
2251 self.assertEqual(dt2.extra, 7)
2252 self.assertEqual(dt1.toordinal(), dt2.toordinal())
2253 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
2254 dt1.second - 7)
2255
2256class TestSubclassDateTime(TestDateTime):
2257 theclass = SubclassDatetime
2258 # Override tests not designed for subclass
Zachary Ware9fe6d862013-12-08 00:20:35 -06002259 @unittest.skip('not appropriate for subclasses')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002260 def test_roundtrip(self):
2261 pass
2262
2263class SubclassTime(time):
2264 sub_var = 1
2265
2266class TestTime(HarmlessMixedComparison, unittest.TestCase):
2267
2268 theclass = time
2269
2270 def test_basic_attributes(self):
2271 t = self.theclass(12, 0)
2272 self.assertEqual(t.hour, 12)
2273 self.assertEqual(t.minute, 0)
2274 self.assertEqual(t.second, 0)
2275 self.assertEqual(t.microsecond, 0)
2276
2277 def test_basic_attributes_nonzero(self):
2278 # Make sure all attributes are non-zero so bugs in
2279 # bit-shifting access show up.
2280 t = self.theclass(12, 59, 59, 8000)
2281 self.assertEqual(t.hour, 12)
2282 self.assertEqual(t.minute, 59)
2283 self.assertEqual(t.second, 59)
2284 self.assertEqual(t.microsecond, 8000)
2285
2286 def test_roundtrip(self):
2287 t = self.theclass(1, 2, 3, 4)
2288
2289 # Verify t -> string -> time identity.
2290 s = repr(t)
2291 self.assertTrue(s.startswith('datetime.'))
2292 s = s[9:]
2293 t2 = eval(s)
2294 self.assertEqual(t, t2)
2295
2296 # Verify identity via reconstructing from pieces.
2297 t2 = self.theclass(t.hour, t.minute, t.second,
2298 t.microsecond)
2299 self.assertEqual(t, t2)
2300
2301 def test_comparing(self):
2302 args = [1, 2, 3, 4]
2303 t1 = self.theclass(*args)
2304 t2 = self.theclass(*args)
2305 self.assertEqual(t1, t2)
2306 self.assertTrue(t1 <= t2)
2307 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002308 self.assertFalse(t1 != t2)
2309 self.assertFalse(t1 < t2)
2310 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002311
2312 for i in range(len(args)):
2313 newargs = args[:]
2314 newargs[i] = args[i] + 1
2315 t2 = self.theclass(*newargs) # this is larger than t1
2316 self.assertTrue(t1 < t2)
2317 self.assertTrue(t2 > t1)
2318 self.assertTrue(t1 <= t2)
2319 self.assertTrue(t2 >= t1)
2320 self.assertTrue(t1 != t2)
2321 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002322 self.assertFalse(t1 == t2)
2323 self.assertFalse(t2 == t1)
2324 self.assertFalse(t1 > t2)
2325 self.assertFalse(t2 < t1)
2326 self.assertFalse(t1 >= t2)
2327 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002328
2329 for badarg in OTHERSTUFF:
2330 self.assertEqual(t1 == badarg, False)
2331 self.assertEqual(t1 != badarg, True)
2332 self.assertEqual(badarg == t1, False)
2333 self.assertEqual(badarg != t1, True)
2334
2335 self.assertRaises(TypeError, lambda: t1 <= badarg)
2336 self.assertRaises(TypeError, lambda: t1 < badarg)
2337 self.assertRaises(TypeError, lambda: t1 > badarg)
2338 self.assertRaises(TypeError, lambda: t1 >= badarg)
2339 self.assertRaises(TypeError, lambda: badarg <= t1)
2340 self.assertRaises(TypeError, lambda: badarg < t1)
2341 self.assertRaises(TypeError, lambda: badarg > t1)
2342 self.assertRaises(TypeError, lambda: badarg >= t1)
2343
2344 def test_bad_constructor_arguments(self):
2345 # bad hours
2346 self.theclass(0, 0) # no exception
2347 self.theclass(23, 0) # no exception
2348 self.assertRaises(ValueError, self.theclass, -1, 0)
2349 self.assertRaises(ValueError, self.theclass, 24, 0)
2350 # bad minutes
2351 self.theclass(23, 0) # no exception
2352 self.theclass(23, 59) # no exception
2353 self.assertRaises(ValueError, self.theclass, 23, -1)
2354 self.assertRaises(ValueError, self.theclass, 23, 60)
2355 # bad seconds
2356 self.theclass(23, 59, 0) # no exception
2357 self.theclass(23, 59, 59) # no exception
2358 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
2359 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
2360 # bad microseconds
2361 self.theclass(23, 59, 59, 0) # no exception
2362 self.theclass(23, 59, 59, 999999) # no exception
2363 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
2364 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
2365
2366 def test_hash_equality(self):
2367 d = self.theclass(23, 30, 17)
2368 e = self.theclass(23, 30, 17)
2369 self.assertEqual(d, e)
2370 self.assertEqual(hash(d), hash(e))
2371
2372 dic = {d: 1}
2373 dic[e] = 2
2374 self.assertEqual(len(dic), 1)
2375 self.assertEqual(dic[d], 2)
2376 self.assertEqual(dic[e], 2)
2377
2378 d = self.theclass(0, 5, 17)
2379 e = self.theclass(0, 5, 17)
2380 self.assertEqual(d, e)
2381 self.assertEqual(hash(d), hash(e))
2382
2383 dic = {d: 1}
2384 dic[e] = 2
2385 self.assertEqual(len(dic), 1)
2386 self.assertEqual(dic[d], 2)
2387 self.assertEqual(dic[e], 2)
2388
2389 def test_isoformat(self):
2390 t = self.theclass(4, 5, 1, 123)
2391 self.assertEqual(t.isoformat(), "04:05:01.000123")
2392 self.assertEqual(t.isoformat(), str(t))
2393
2394 t = self.theclass()
2395 self.assertEqual(t.isoformat(), "00:00:00")
2396 self.assertEqual(t.isoformat(), str(t))
2397
2398 t = self.theclass(microsecond=1)
2399 self.assertEqual(t.isoformat(), "00:00:00.000001")
2400 self.assertEqual(t.isoformat(), str(t))
2401
2402 t = self.theclass(microsecond=10)
2403 self.assertEqual(t.isoformat(), "00:00:00.000010")
2404 self.assertEqual(t.isoformat(), str(t))
2405
2406 t = self.theclass(microsecond=100)
2407 self.assertEqual(t.isoformat(), "00:00:00.000100")
2408 self.assertEqual(t.isoformat(), str(t))
2409
2410 t = self.theclass(microsecond=1000)
2411 self.assertEqual(t.isoformat(), "00:00:00.001000")
2412 self.assertEqual(t.isoformat(), str(t))
2413
2414 t = self.theclass(microsecond=10000)
2415 self.assertEqual(t.isoformat(), "00:00:00.010000")
2416 self.assertEqual(t.isoformat(), str(t))
2417
2418 t = self.theclass(microsecond=100000)
2419 self.assertEqual(t.isoformat(), "00:00:00.100000")
2420 self.assertEqual(t.isoformat(), str(t))
2421
Alexander Belopolskya2998a62016-03-06 14:58:43 -05002422 t = self.theclass(hour=12, minute=34, second=56, microsecond=123456)
2423 self.assertEqual(t.isoformat(timespec='hours'), "12")
2424 self.assertEqual(t.isoformat(timespec='minutes'), "12:34")
2425 self.assertEqual(t.isoformat(timespec='seconds'), "12:34:56")
2426 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.123")
2427 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.123456")
2428 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56.123456")
2429 self.assertRaises(ValueError, t.isoformat, timespec='monkey')
2430
2431 t = self.theclass(hour=12, minute=34, second=56, microsecond=999500)
2432 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.999")
2433
2434 t = self.theclass(hour=12, minute=34, second=56, microsecond=0)
2435 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.000")
2436 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.000000")
2437 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56")
2438
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002439 def test_1653736(self):
2440 # verify it doesn't accept extra keyword arguments
2441 t = self.theclass(second=1)
2442 self.assertRaises(TypeError, t.isoformat, foo=3)
2443
2444 def test_strftime(self):
2445 t = self.theclass(1, 2, 3, 4)
2446 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
2447 # A naive object replaces %z and %Z with empty strings.
2448 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
2449
2450 def test_format(self):
2451 t = self.theclass(1, 2, 3, 4)
2452 self.assertEqual(t.__format__(''), str(t))
2453
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02002454 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002455 t.__format__(123)
2456
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002457 # check that a derived class's __str__() gets called
2458 class A(self.theclass):
2459 def __str__(self):
2460 return 'A'
2461 a = A(1, 2, 3, 4)
2462 self.assertEqual(a.__format__(''), 'A')
2463
2464 # check that a derived class's strftime gets called
2465 class B(self.theclass):
2466 def strftime(self, format_spec):
2467 return 'B'
2468 b = B(1, 2, 3, 4)
2469 self.assertEqual(b.__format__(''), str(t))
2470
2471 for fmt in ['%H %M %S',
2472 ]:
2473 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
2474 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
2475 self.assertEqual(b.__format__(fmt), 'B')
2476
2477 def test_str(self):
2478 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
2479 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
2480 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
2481 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
2482 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
2483
2484 def test_repr(self):
2485 name = 'datetime.' + self.theclass.__name__
2486 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
2487 "%s(1, 2, 3, 4)" % name)
2488 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
2489 "%s(10, 2, 3, 4000)" % name)
2490 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
2491 "%s(0, 2, 3, 400000)" % name)
2492 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
2493 "%s(12, 2, 3)" % name)
2494 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
2495 "%s(23, 15)" % name)
2496
2497 def test_resolution_info(self):
2498 self.assertIsInstance(self.theclass.min, self.theclass)
2499 self.assertIsInstance(self.theclass.max, self.theclass)
2500 self.assertIsInstance(self.theclass.resolution, timedelta)
2501 self.assertTrue(self.theclass.max > self.theclass.min)
2502
2503 def test_pickling(self):
2504 args = 20, 59, 16, 64**2
2505 orig = self.theclass(*args)
2506 for pickler, unpickler, proto in pickle_choices:
2507 green = pickler.dumps(orig, proto)
2508 derived = unpickler.loads(green)
2509 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02002510 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002511
2512 def test_pickling_subclass_time(self):
2513 args = 20, 59, 16, 64**2
2514 orig = SubclassTime(*args)
2515 for pickler, unpickler, proto in pickle_choices:
2516 green = pickler.dumps(orig, proto)
2517 derived = unpickler.loads(green)
2518 self.assertEqual(orig, derived)
2519
2520 def test_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002521 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002522 cls = self.theclass
2523 self.assertTrue(cls(1))
2524 self.assertTrue(cls(0, 1))
2525 self.assertTrue(cls(0, 0, 1))
2526 self.assertTrue(cls(0, 0, 0, 1))
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002527 self.assertTrue(cls(0))
2528 self.assertTrue(cls())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002529
2530 def test_replace(self):
2531 cls = self.theclass
2532 args = [1, 2, 3, 4]
2533 base = cls(*args)
2534 self.assertEqual(base, base.replace())
2535
2536 i = 0
2537 for name, newval in (("hour", 5),
2538 ("minute", 6),
2539 ("second", 7),
2540 ("microsecond", 8)):
2541 newargs = args[:]
2542 newargs[i] = newval
2543 expected = cls(*newargs)
2544 got = base.replace(**{name: newval})
2545 self.assertEqual(expected, got)
2546 i += 1
2547
2548 # Out of bounds.
2549 base = cls(1)
2550 self.assertRaises(ValueError, base.replace, hour=24)
2551 self.assertRaises(ValueError, base.replace, minute=-1)
2552 self.assertRaises(ValueError, base.replace, second=100)
2553 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2554
2555 def test_subclass_time(self):
2556
2557 class C(self.theclass):
2558 theAnswer = 42
2559
2560 def __new__(cls, *args, **kws):
2561 temp = kws.copy()
2562 extra = temp.pop('extra')
2563 result = self.theclass.__new__(cls, *args, **temp)
2564 result.extra = extra
2565 return result
2566
2567 def newmeth(self, start):
2568 return start + self.hour + self.second
2569
2570 args = 4, 5, 6
2571
2572 dt1 = self.theclass(*args)
2573 dt2 = C(*args, **{'extra': 7})
2574
2575 self.assertEqual(dt2.__class__, C)
2576 self.assertEqual(dt2.theAnswer, 42)
2577 self.assertEqual(dt2.extra, 7)
2578 self.assertEqual(dt1.isoformat(), dt2.isoformat())
2579 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2580
2581 def test_backdoor_resistance(self):
2582 # see TestDate.test_backdoor_resistance().
2583 base = '2:59.0'
2584 for hour_byte in ' ', '9', chr(24), '\xff':
2585 self.assertRaises(TypeError, self.theclass,
2586 hour_byte + base[1:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002587 # Good bytes, but bad tzinfo:
2588 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
2589 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002590
2591# A mixin for classes with a tzinfo= argument. Subclasses must define
Martin Panter46f50722016-05-26 05:35:26 +00002592# theclass as a class attribute, and theclass(1, 1, 1, tzinfo=whatever)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002593# must be legit (which is true for time and datetime).
2594class TZInfoBase:
2595
2596 def test_argument_passing(self):
2597 cls = self.theclass
2598 # A datetime passes itself on, a time passes None.
2599 class introspective(tzinfo):
2600 def tzname(self, dt): return dt and "real" or "none"
2601 def utcoffset(self, dt):
2602 return timedelta(minutes = dt and 42 or -42)
2603 dst = utcoffset
2604
2605 obj = cls(1, 2, 3, tzinfo=introspective())
2606
2607 expected = cls is time and "none" or "real"
2608 self.assertEqual(obj.tzname(), expected)
2609
2610 expected = timedelta(minutes=(cls is time and -42 or 42))
2611 self.assertEqual(obj.utcoffset(), expected)
2612 self.assertEqual(obj.dst(), expected)
2613
2614 def test_bad_tzinfo_classes(self):
2615 cls = self.theclass
2616 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
2617
2618 class NiceTry(object):
2619 def __init__(self): pass
2620 def utcoffset(self, dt): pass
2621 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
2622
2623 class BetterTry(tzinfo):
2624 def __init__(self): pass
2625 def utcoffset(self, dt): pass
2626 b = BetterTry()
2627 t = cls(1, 1, 1, tzinfo=b)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002628 self.assertIs(t.tzinfo, b)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002629
2630 def test_utc_offset_out_of_bounds(self):
2631 class Edgy(tzinfo):
2632 def __init__(self, offset):
2633 self.offset = timedelta(minutes=offset)
2634 def utcoffset(self, dt):
2635 return self.offset
2636
2637 cls = self.theclass
2638 for offset, legit in ((-1440, False),
2639 (-1439, True),
2640 (1439, True),
2641 (1440, False)):
2642 if cls is time:
2643 t = cls(1, 2, 3, tzinfo=Edgy(offset))
2644 elif cls is datetime:
2645 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
2646 else:
2647 assert 0, "impossible"
2648 if legit:
2649 aofs = abs(offset)
2650 h, m = divmod(aofs, 60)
2651 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
2652 if isinstance(t, datetime):
2653 t = t.timetz()
2654 self.assertEqual(str(t), "01:02:03" + tag)
2655 else:
2656 self.assertRaises(ValueError, str, t)
2657
2658 def test_tzinfo_classes(self):
2659 cls = self.theclass
2660 class C1(tzinfo):
2661 def utcoffset(self, dt): return None
2662 def dst(self, dt): return None
2663 def tzname(self, dt): return None
2664 for t in (cls(1, 1, 1),
2665 cls(1, 1, 1, tzinfo=None),
2666 cls(1, 1, 1, tzinfo=C1())):
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002667 self.assertIsNone(t.utcoffset())
2668 self.assertIsNone(t.dst())
2669 self.assertIsNone(t.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002670
2671 class C3(tzinfo):
2672 def utcoffset(self, dt): return timedelta(minutes=-1439)
2673 def dst(self, dt): return timedelta(minutes=1439)
2674 def tzname(self, dt): return "aname"
2675 t = cls(1, 1, 1, tzinfo=C3())
2676 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
2677 self.assertEqual(t.dst(), timedelta(minutes=1439))
2678 self.assertEqual(t.tzname(), "aname")
2679
2680 # Wrong types.
2681 class C4(tzinfo):
2682 def utcoffset(self, dt): return "aname"
2683 def dst(self, dt): return 7
2684 def tzname(self, dt): return 0
2685 t = cls(1, 1, 1, tzinfo=C4())
2686 self.assertRaises(TypeError, t.utcoffset)
2687 self.assertRaises(TypeError, t.dst)
2688 self.assertRaises(TypeError, t.tzname)
2689
2690 # Offset out of range.
2691 class C6(tzinfo):
2692 def utcoffset(self, dt): return timedelta(hours=-24)
2693 def dst(self, dt): return timedelta(hours=24)
2694 t = cls(1, 1, 1, tzinfo=C6())
2695 self.assertRaises(ValueError, t.utcoffset)
2696 self.assertRaises(ValueError, t.dst)
2697
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002698 # Not a whole number of seconds.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002699 class C7(tzinfo):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002700 def utcoffset(self, dt): return timedelta(microseconds=61)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002701 def dst(self, dt): return timedelta(microseconds=-81)
2702 t = cls(1, 1, 1, tzinfo=C7())
2703 self.assertRaises(ValueError, t.utcoffset)
2704 self.assertRaises(ValueError, t.dst)
2705
2706 def test_aware_compare(self):
2707 cls = self.theclass
2708
2709 # Ensure that utcoffset() gets ignored if the comparands have
2710 # the same tzinfo member.
2711 class OperandDependentOffset(tzinfo):
2712 def utcoffset(self, t):
2713 if t.minute < 10:
2714 # d0 and d1 equal after adjustment
2715 return timedelta(minutes=t.minute)
2716 else:
2717 # d2 off in the weeds
2718 return timedelta(minutes=59)
2719
2720 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
2721 d0 = base.replace(minute=3)
2722 d1 = base.replace(minute=9)
2723 d2 = base.replace(minute=11)
2724 for x in d0, d1, d2:
2725 for y in d0, d1, d2:
2726 for op in lt, le, gt, ge, eq, ne:
2727 got = op(x, y)
2728 expected = op(x.minute, y.minute)
2729 self.assertEqual(got, expected)
2730
2731 # However, if they're different members, uctoffset is not ignored.
2732 # Note that a time can't actually have an operand-depedent offset,
2733 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
2734 # so skip this test for time.
2735 if cls is not time:
2736 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2737 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2738 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2739 for x in d0, d1, d2:
2740 for y in d0, d1, d2:
2741 got = (x > y) - (x < y)
2742 if (x is d0 or x is d1) and (y is d0 or y is d1):
2743 expected = 0
2744 elif x is y is d2:
2745 expected = 0
2746 elif x is d2:
2747 expected = -1
2748 else:
2749 assert y is d2
2750 expected = 1
2751 self.assertEqual(got, expected)
2752
2753
2754# Testing time objects with a non-None tzinfo.
2755class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
2756 theclass = time
2757
2758 def test_empty(self):
2759 t = self.theclass()
2760 self.assertEqual(t.hour, 0)
2761 self.assertEqual(t.minute, 0)
2762 self.assertEqual(t.second, 0)
2763 self.assertEqual(t.microsecond, 0)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002764 self.assertIsNone(t.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002765
2766 def test_zones(self):
2767 est = FixedOffset(-300, "EST", 1)
2768 utc = FixedOffset(0, "UTC", -2)
2769 met = FixedOffset(60, "MET", 3)
2770 t1 = time( 7, 47, tzinfo=est)
2771 t2 = time(12, 47, tzinfo=utc)
2772 t3 = time(13, 47, tzinfo=met)
2773 t4 = time(microsecond=40)
2774 t5 = time(microsecond=40, tzinfo=utc)
2775
2776 self.assertEqual(t1.tzinfo, est)
2777 self.assertEqual(t2.tzinfo, utc)
2778 self.assertEqual(t3.tzinfo, met)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002779 self.assertIsNone(t4.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002780 self.assertEqual(t5.tzinfo, utc)
2781
2782 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2783 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2784 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002785 self.assertIsNone(t4.utcoffset())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002786 self.assertRaises(TypeError, t1.utcoffset, "no args")
2787
2788 self.assertEqual(t1.tzname(), "EST")
2789 self.assertEqual(t2.tzname(), "UTC")
2790 self.assertEqual(t3.tzname(), "MET")
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002791 self.assertIsNone(t4.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002792 self.assertRaises(TypeError, t1.tzname, "no args")
2793
2794 self.assertEqual(t1.dst(), timedelta(minutes=1))
2795 self.assertEqual(t2.dst(), timedelta(minutes=-2))
2796 self.assertEqual(t3.dst(), timedelta(minutes=3))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002797 self.assertIsNone(t4.dst())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002798 self.assertRaises(TypeError, t1.dst, "no args")
2799
2800 self.assertEqual(hash(t1), hash(t2))
2801 self.assertEqual(hash(t1), hash(t3))
2802 self.assertEqual(hash(t2), hash(t3))
2803
2804 self.assertEqual(t1, t2)
2805 self.assertEqual(t1, t3)
2806 self.assertEqual(t2, t3)
Alexander Belopolsky08313822012-06-15 20:19:47 -04002807 self.assertNotEqual(t4, t5) # mixed tz-aware & naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002808 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
2809 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
2810
2811 self.assertEqual(str(t1), "07:47:00-05:00")
2812 self.assertEqual(str(t2), "12:47:00+00:00")
2813 self.assertEqual(str(t3), "13:47:00+01:00")
2814 self.assertEqual(str(t4), "00:00:00.000040")
2815 self.assertEqual(str(t5), "00:00:00.000040+00:00")
2816
2817 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
2818 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
2819 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
2820 self.assertEqual(t4.isoformat(), "00:00:00.000040")
2821 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
2822
2823 d = 'datetime.time'
2824 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
2825 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
2826 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
2827 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
2828 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2829
2830 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2831 "07:47:00 %Z=EST %z=-0500")
2832 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2833 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2834
2835 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
2836 t1 = time(23, 59, tzinfo=yuck)
2837 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2838 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2839
2840 # Check that an invalid tzname result raises an exception.
2841 class Badtzname(tzinfo):
Alexander Belopolskye239d232010-12-08 23:31:48 +00002842 tz = 42
2843 def tzname(self, dt): return self.tz
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002844 t = time(2, 3, 4, tzinfo=Badtzname())
2845 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2846 self.assertRaises(TypeError, t.strftime, "%Z")
2847
Alexander Belopolskye239d232010-12-08 23:31:48 +00002848 # Issue #6697:
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002849 if '_Fast' in str(self):
Alexander Belopolskye239d232010-12-08 23:31:48 +00002850 Badtzname.tz = '\ud800'
2851 self.assertRaises(ValueError, t.strftime, "%Z")
2852
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002853 def test_hash_edge_cases(self):
2854 # Offsets that overflow a basic time.
2855 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2856 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2857 self.assertEqual(hash(t1), hash(t2))
2858
2859 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2860 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2861 self.assertEqual(hash(t1), hash(t2))
2862
2863 def test_pickling(self):
2864 # Try one without a tzinfo.
2865 args = 20, 59, 16, 64**2
2866 orig = self.theclass(*args)
2867 for pickler, unpickler, proto in pickle_choices:
2868 green = pickler.dumps(orig, proto)
2869 derived = unpickler.loads(green)
2870 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02002871 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002872
2873 # Try one with a tzinfo.
2874 tinfo = PicklableFixedOffset(-300, 'cookie')
2875 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
2876 for pickler, unpickler, proto in pickle_choices:
2877 green = pickler.dumps(orig, proto)
2878 derived = unpickler.loads(green)
2879 self.assertEqual(orig, derived)
2880 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
2881 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2882 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02002883 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002884
2885 def test_more_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002886 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002887 cls = self.theclass
2888
2889 t = cls(0, tzinfo=FixedOffset(-300, ""))
2890 self.assertTrue(t)
2891
2892 t = cls(5, tzinfo=FixedOffset(-300, ""))
2893 self.assertTrue(t)
2894
2895 t = cls(5, tzinfo=FixedOffset(300, ""))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002896 self.assertTrue(t)
2897
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002898 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2899 self.assertTrue(t)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002900
2901 def test_replace(self):
2902 cls = self.theclass
2903 z100 = FixedOffset(100, "+100")
2904 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2905 args = [1, 2, 3, 4, z100]
2906 base = cls(*args)
2907 self.assertEqual(base, base.replace())
2908
2909 i = 0
2910 for name, newval in (("hour", 5),
2911 ("minute", 6),
2912 ("second", 7),
2913 ("microsecond", 8),
2914 ("tzinfo", zm200)):
2915 newargs = args[:]
2916 newargs[i] = newval
2917 expected = cls(*newargs)
2918 got = base.replace(**{name: newval})
2919 self.assertEqual(expected, got)
2920 i += 1
2921
2922 # Ensure we can get rid of a tzinfo.
2923 self.assertEqual(base.tzname(), "+100")
2924 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002925 self.assertIsNone(base2.tzinfo)
2926 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002927
2928 # Ensure we can add one.
2929 base3 = base2.replace(tzinfo=z100)
2930 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002931 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002932
2933 # Out of bounds.
2934 base = cls(1)
2935 self.assertRaises(ValueError, base.replace, hour=24)
2936 self.assertRaises(ValueError, base.replace, minute=-1)
2937 self.assertRaises(ValueError, base.replace, second=100)
2938 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2939
2940 def test_mixed_compare(self):
2941 t1 = time(1, 2, 3)
2942 t2 = time(1, 2, 3)
2943 self.assertEqual(t1, t2)
2944 t2 = t2.replace(tzinfo=None)
2945 self.assertEqual(t1, t2)
2946 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2947 self.assertEqual(t1, t2)
2948 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04002949 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002950
2951 # In time w/ identical tzinfo objects, utcoffset is ignored.
2952 class Varies(tzinfo):
2953 def __init__(self):
2954 self.offset = timedelta(minutes=22)
2955 def utcoffset(self, t):
2956 self.offset += timedelta(minutes=1)
2957 return self.offset
2958
2959 v = Varies()
2960 t1 = t2.replace(tzinfo=v)
2961 t2 = t2.replace(tzinfo=v)
2962 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2963 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2964 self.assertEqual(t1, t2)
2965
2966 # But if they're not identical, it isn't ignored.
2967 t2 = t2.replace(tzinfo=Varies())
2968 self.assertTrue(t1 < t2) # t1's offset counter still going up
2969
2970 def test_subclass_timetz(self):
2971
2972 class C(self.theclass):
2973 theAnswer = 42
2974
2975 def __new__(cls, *args, **kws):
2976 temp = kws.copy()
2977 extra = temp.pop('extra')
2978 result = self.theclass.__new__(cls, *args, **temp)
2979 result.extra = extra
2980 return result
2981
2982 def newmeth(self, start):
2983 return start + self.hour + self.second
2984
2985 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2986
2987 dt1 = self.theclass(*args)
2988 dt2 = C(*args, **{'extra': 7})
2989
2990 self.assertEqual(dt2.__class__, C)
2991 self.assertEqual(dt2.theAnswer, 42)
2992 self.assertEqual(dt2.extra, 7)
2993 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2994 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2995
2996
2997# Testing datetime objects with a non-None tzinfo.
2998
2999class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
3000 theclass = datetime
3001
3002 def test_trivial(self):
3003 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
3004 self.assertEqual(dt.year, 1)
3005 self.assertEqual(dt.month, 2)
3006 self.assertEqual(dt.day, 3)
3007 self.assertEqual(dt.hour, 4)
3008 self.assertEqual(dt.minute, 5)
3009 self.assertEqual(dt.second, 6)
3010 self.assertEqual(dt.microsecond, 7)
3011 self.assertEqual(dt.tzinfo, None)
3012
3013 def test_even_more_compare(self):
3014 # The test_compare() and test_more_compare() inherited from TestDate
3015 # and TestDateTime covered non-tzinfo cases.
3016
3017 # Smallest possible after UTC adjustment.
3018 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3019 # Largest possible after UTC adjustment.
3020 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3021 tzinfo=FixedOffset(-1439, ""))
3022
3023 # Make sure those compare correctly, and w/o overflow.
3024 self.assertTrue(t1 < t2)
3025 self.assertTrue(t1 != t2)
3026 self.assertTrue(t2 > t1)
3027
3028 self.assertEqual(t1, t1)
3029 self.assertEqual(t2, t2)
3030
3031 # Equal afer adjustment.
3032 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
3033 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
3034 self.assertEqual(t1, t2)
3035
3036 # Change t1 not to subtract a minute, and t1 should be larger.
3037 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
3038 self.assertTrue(t1 > t2)
3039
3040 # Change t1 to subtract 2 minutes, and t1 should be smaller.
3041 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
3042 self.assertTrue(t1 < t2)
3043
3044 # Back to the original t1, but make seconds resolve it.
3045 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3046 second=1)
3047 self.assertTrue(t1 > t2)
3048
3049 # Likewise, but make microseconds resolve it.
3050 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3051 microsecond=1)
3052 self.assertTrue(t1 > t2)
3053
Alexander Belopolsky08313822012-06-15 20:19:47 -04003054 # Make t2 naive and it should differ.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003055 t2 = self.theclass.min
Alexander Belopolsky08313822012-06-15 20:19:47 -04003056 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003057 self.assertEqual(t2, t2)
3058
3059 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
3060 class Naive(tzinfo):
3061 def utcoffset(self, dt): return None
3062 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
Alexander Belopolsky08313822012-06-15 20:19:47 -04003063 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003064 self.assertEqual(t2, t2)
3065
3066 # OTOH, it's OK to compare two of these mixing the two ways of being
3067 # naive.
3068 t1 = self.theclass(5, 6, 7)
3069 self.assertEqual(t1, t2)
3070
3071 # Try a bogus uctoffset.
3072 class Bogus(tzinfo):
3073 def utcoffset(self, dt):
3074 return timedelta(minutes=1440) # out of bounds
3075 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
3076 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
3077 self.assertRaises(ValueError, lambda: t1 == t2)
3078
3079 def test_pickling(self):
3080 # Try one without a tzinfo.
3081 args = 6, 7, 23, 20, 59, 1, 64**2
3082 orig = self.theclass(*args)
3083 for pickler, unpickler, proto in pickle_choices:
3084 green = pickler.dumps(orig, proto)
3085 derived = unpickler.loads(green)
3086 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003087 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003088
3089 # Try one with a tzinfo.
3090 tinfo = PicklableFixedOffset(-300, 'cookie')
3091 orig = self.theclass(*args, **{'tzinfo': tinfo})
3092 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
3093 for pickler, unpickler, proto in pickle_choices:
3094 green = pickler.dumps(orig, proto)
3095 derived = unpickler.loads(green)
3096 self.assertEqual(orig, derived)
3097 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3098 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3099 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003100 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003101
3102 def test_extreme_hashes(self):
3103 # If an attempt is made to hash these via subtracting the offset
3104 # then hashing a datetime object, OverflowError results. The
3105 # Python implementation used to blow up here.
3106 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3107 hash(t)
3108 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3109 tzinfo=FixedOffset(-1439, ""))
3110 hash(t)
3111
3112 # OTOH, an OOB offset should blow up.
3113 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
3114 self.assertRaises(ValueError, hash, t)
3115
3116 def test_zones(self):
3117 est = FixedOffset(-300, "EST")
3118 utc = FixedOffset(0, "UTC")
3119 met = FixedOffset(60, "MET")
3120 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
3121 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
3122 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
3123 self.assertEqual(t1.tzinfo, est)
3124 self.assertEqual(t2.tzinfo, utc)
3125 self.assertEqual(t3.tzinfo, met)
3126 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
3127 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
3128 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
3129 self.assertEqual(t1.tzname(), "EST")
3130 self.assertEqual(t2.tzname(), "UTC")
3131 self.assertEqual(t3.tzname(), "MET")
3132 self.assertEqual(hash(t1), hash(t2))
3133 self.assertEqual(hash(t1), hash(t3))
3134 self.assertEqual(hash(t2), hash(t3))
3135 self.assertEqual(t1, t2)
3136 self.assertEqual(t1, t3)
3137 self.assertEqual(t2, t3)
3138 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
3139 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
3140 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
3141 d = 'datetime.datetime(2002, 3, 19, '
3142 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
3143 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
3144 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
3145
3146 def test_combine(self):
3147 met = FixedOffset(60, "MET")
3148 d = date(2002, 3, 4)
3149 tz = time(18, 45, 3, 1234, tzinfo=met)
3150 dt = datetime.combine(d, tz)
3151 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
3152 tzinfo=met))
3153
3154 def test_extract(self):
3155 met = FixedOffset(60, "MET")
3156 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
3157 self.assertEqual(dt.date(), date(2002, 3, 4))
3158 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
3159 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
3160
3161 def test_tz_aware_arithmetic(self):
3162 import random
3163
3164 now = self.theclass.now()
3165 tz55 = FixedOffset(-330, "west 5:30")
3166 timeaware = now.time().replace(tzinfo=tz55)
3167 nowaware = self.theclass.combine(now.date(), timeaware)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003168 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003169 self.assertEqual(nowaware.timetz(), timeaware)
3170
3171 # Can't mix aware and non-aware.
3172 self.assertRaises(TypeError, lambda: now - nowaware)
3173 self.assertRaises(TypeError, lambda: nowaware - now)
3174
3175 # And adding datetime's doesn't make sense, aware or not.
3176 self.assertRaises(TypeError, lambda: now + nowaware)
3177 self.assertRaises(TypeError, lambda: nowaware + now)
3178 self.assertRaises(TypeError, lambda: nowaware + nowaware)
3179
3180 # Subtracting should yield 0.
3181 self.assertEqual(now - now, timedelta(0))
3182 self.assertEqual(nowaware - nowaware, timedelta(0))
3183
3184 # Adding a delta should preserve tzinfo.
3185 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
3186 nowawareplus = nowaware + delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003187 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003188 nowawareplus2 = delta + nowaware
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003189 self.assertIs(nowawareplus2.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003190 self.assertEqual(nowawareplus, nowawareplus2)
3191
3192 # that - delta should be what we started with, and that - what we
3193 # started with should be delta.
3194 diff = nowawareplus - delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003195 self.assertIs(diff.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003196 self.assertEqual(nowaware, diff)
3197 self.assertRaises(TypeError, lambda: delta - nowawareplus)
3198 self.assertEqual(nowawareplus - nowaware, delta)
3199
3200 # Make up a random timezone.
3201 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
3202 # Attach it to nowawareplus.
3203 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003204 self.assertIs(nowawareplus.tzinfo, tzr)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003205 # Make sure the difference takes the timezone adjustments into account.
3206 got = nowaware - nowawareplus
3207 # Expected: (nowaware base - nowaware offset) -
3208 # (nowawareplus base - nowawareplus offset) =
3209 # (nowaware base - nowawareplus base) +
3210 # (nowawareplus offset - nowaware offset) =
3211 # -delta + nowawareplus offset - nowaware offset
3212 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
3213 self.assertEqual(got, expected)
3214
3215 # Try max possible difference.
3216 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
3217 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3218 tzinfo=FixedOffset(-1439, "max"))
3219 maxdiff = max - min
3220 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
3221 timedelta(minutes=2*1439))
3222 # Different tzinfo, but the same offset
3223 tza = timezone(HOUR, 'A')
3224 tzb = timezone(HOUR, 'B')
3225 delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
3226 self.assertEqual(delta, self.theclass.min - self.theclass.max)
3227
3228 def test_tzinfo_now(self):
3229 meth = self.theclass.now
3230 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3231 base = meth()
3232 # Try with and without naming the keyword.
3233 off42 = FixedOffset(42, "42")
3234 another = meth(off42)
3235 again = meth(tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003236 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003237 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3238 # Bad argument with and w/o naming the keyword.
3239 self.assertRaises(TypeError, meth, 16)
3240 self.assertRaises(TypeError, meth, tzinfo=16)
3241 # Bad keyword name.
3242 self.assertRaises(TypeError, meth, tinfo=off42)
3243 # Too many args.
3244 self.assertRaises(TypeError, meth, off42, off42)
3245
3246 # We don't know which time zone we're in, and don't have a tzinfo
3247 # class to represent it, so seeing whether a tz argument actually
3248 # does a conversion is tricky.
3249 utc = FixedOffset(0, "utc", 0)
3250 for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
3251 timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
3252 for dummy in range(3):
3253 now = datetime.now(weirdtz)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003254 self.assertIs(now.tzinfo, weirdtz)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003255 utcnow = datetime.utcnow().replace(tzinfo=utc)
3256 now2 = utcnow.astimezone(weirdtz)
3257 if abs(now - now2) < timedelta(seconds=30):
3258 break
3259 # Else the code is broken, or more than 30 seconds passed between
3260 # calls; assuming the latter, just try again.
3261 else:
3262 # Three strikes and we're out.
3263 self.fail("utcnow(), now(tz), or astimezone() may be broken")
3264
3265 def test_tzinfo_fromtimestamp(self):
3266 import time
3267 meth = self.theclass.fromtimestamp
3268 ts = time.time()
3269 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3270 base = meth(ts)
3271 # Try with and without naming the keyword.
3272 off42 = FixedOffset(42, "42")
3273 another = meth(ts, off42)
3274 again = meth(ts, tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003275 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003276 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3277 # Bad argument with and w/o naming the keyword.
3278 self.assertRaises(TypeError, meth, ts, 16)
3279 self.assertRaises(TypeError, meth, ts, tzinfo=16)
3280 # Bad keyword name.
3281 self.assertRaises(TypeError, meth, ts, tinfo=off42)
3282 # Too many args.
3283 self.assertRaises(TypeError, meth, ts, off42, off42)
3284 # Too few args.
3285 self.assertRaises(TypeError, meth)
3286
3287 # Try to make sure tz= actually does some conversion.
3288 timestamp = 1000000000
3289 utcdatetime = datetime.utcfromtimestamp(timestamp)
3290 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
3291 # But on some flavor of Mac, it's nowhere near that. So we can't have
3292 # any idea here what time that actually is, we can only test that
3293 # relative changes match.
3294 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
3295 tz = FixedOffset(utcoffset, "tz", 0)
3296 expected = utcdatetime + utcoffset
3297 got = datetime.fromtimestamp(timestamp, tz)
3298 self.assertEqual(expected, got.replace(tzinfo=None))
3299
3300 def test_tzinfo_utcnow(self):
3301 meth = self.theclass.utcnow
3302 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3303 base = meth()
3304 # Try with and without naming the keyword; for whatever reason,
3305 # utcnow() doesn't accept a tzinfo argument.
3306 off42 = FixedOffset(42, "42")
3307 self.assertRaises(TypeError, meth, off42)
3308 self.assertRaises(TypeError, meth, tzinfo=off42)
3309
3310 def test_tzinfo_utcfromtimestamp(self):
3311 import time
3312 meth = self.theclass.utcfromtimestamp
3313 ts = time.time()
3314 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3315 base = meth(ts)
3316 # Try with and without naming the keyword; for whatever reason,
3317 # utcfromtimestamp() doesn't accept a tzinfo argument.
3318 off42 = FixedOffset(42, "42")
3319 self.assertRaises(TypeError, meth, ts, off42)
3320 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
3321
3322 def test_tzinfo_timetuple(self):
3323 # TestDateTime tested most of this. datetime adds a twist to the
3324 # DST flag.
3325 class DST(tzinfo):
3326 def __init__(self, dstvalue):
3327 if isinstance(dstvalue, int):
3328 dstvalue = timedelta(minutes=dstvalue)
3329 self.dstvalue = dstvalue
3330 def dst(self, dt):
3331 return self.dstvalue
3332
3333 cls = self.theclass
3334 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
3335 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
3336 t = d.timetuple()
3337 self.assertEqual(1, t.tm_year)
3338 self.assertEqual(1, t.tm_mon)
3339 self.assertEqual(1, t.tm_mday)
3340 self.assertEqual(10, t.tm_hour)
3341 self.assertEqual(20, t.tm_min)
3342 self.assertEqual(30, t.tm_sec)
3343 self.assertEqual(0, t.tm_wday)
3344 self.assertEqual(1, t.tm_yday)
3345 self.assertEqual(flag, t.tm_isdst)
3346
3347 # dst() returns wrong type.
3348 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
3349
3350 # dst() at the edge.
3351 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
3352 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
3353
3354 # dst() out of range.
3355 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
3356 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
3357
3358 def test_utctimetuple(self):
3359 class DST(tzinfo):
3360 def __init__(self, dstvalue=0):
3361 if isinstance(dstvalue, int):
3362 dstvalue = timedelta(minutes=dstvalue)
3363 self.dstvalue = dstvalue
3364 def dst(self, dt):
3365 return self.dstvalue
3366
3367 cls = self.theclass
3368 # This can't work: DST didn't implement utcoffset.
3369 self.assertRaises(NotImplementedError,
3370 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
3371
3372 class UOFS(DST):
3373 def __init__(self, uofs, dofs=None):
3374 DST.__init__(self, dofs)
3375 self.uofs = timedelta(minutes=uofs)
3376 def utcoffset(self, dt):
3377 return self.uofs
3378
3379 for dstvalue in -33, 33, 0, None:
3380 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
3381 t = d.utctimetuple()
3382 self.assertEqual(d.year, t.tm_year)
3383 self.assertEqual(d.month, t.tm_mon)
3384 self.assertEqual(d.day, t.tm_mday)
3385 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
3386 self.assertEqual(13, t.tm_min)
3387 self.assertEqual(d.second, t.tm_sec)
3388 self.assertEqual(d.weekday(), t.tm_wday)
3389 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
3390 t.tm_yday)
3391 # Ensure tm_isdst is 0 regardless of what dst() says: DST
3392 # is never in effect for a UTC time.
3393 self.assertEqual(0, t.tm_isdst)
3394
3395 # For naive datetime, utctimetuple == timetuple except for isdst
3396 d = cls(1, 2, 3, 10, 20, 30, 40)
3397 t = d.utctimetuple()
3398 self.assertEqual(t[:-1], d.timetuple()[:-1])
3399 self.assertEqual(0, t.tm_isdst)
3400 # Same if utcoffset is None
3401 class NOFS(DST):
3402 def utcoffset(self, dt):
3403 return None
3404 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
3405 t = d.utctimetuple()
3406 self.assertEqual(t[:-1], d.timetuple()[:-1])
3407 self.assertEqual(0, t.tm_isdst)
3408 # Check that bad tzinfo is detected
3409 class BOFS(DST):
3410 def utcoffset(self, dt):
3411 return "EST"
3412 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
3413 self.assertRaises(TypeError, d.utctimetuple)
3414
3415 # Check that utctimetuple() is the same as
3416 # astimezone(utc).timetuple()
3417 d = cls(2010, 11, 13, 14, 15, 16, 171819)
3418 for tz in [timezone.min, timezone.utc, timezone.max]:
3419 dtz = d.replace(tzinfo=tz)
3420 self.assertEqual(dtz.utctimetuple()[:-1],
3421 dtz.astimezone(timezone.utc).timetuple()[:-1])
3422 # At the edges, UTC adjustment can produce years out-of-range
3423 # for a datetime object. Ensure that an OverflowError is
3424 # raised.
3425 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
3426 # That goes back 1 minute less than a full day.
3427 self.assertRaises(OverflowError, tiny.utctimetuple)
3428
3429 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
3430 # That goes forward 1 minute less than a full day.
3431 self.assertRaises(OverflowError, huge.utctimetuple)
3432 # More overflow cases
3433 tiny = cls.min.replace(tzinfo=timezone(MINUTE))
3434 self.assertRaises(OverflowError, tiny.utctimetuple)
3435 huge = cls.max.replace(tzinfo=timezone(-MINUTE))
3436 self.assertRaises(OverflowError, huge.utctimetuple)
3437
3438 def test_tzinfo_isoformat(self):
3439 zero = FixedOffset(0, "+00:00")
3440 plus = FixedOffset(220, "+03:40")
3441 minus = FixedOffset(-231, "-03:51")
3442 unknown = FixedOffset(None, "")
3443
3444 cls = self.theclass
3445 datestr = '0001-02-03'
3446 for ofs in None, zero, plus, minus, unknown:
3447 for us in 0, 987001:
3448 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
3449 timestr = '04:05:59' + (us and '.987001' or '')
3450 ofsstr = ofs is not None and d.tzname() or ''
3451 tailstr = timestr + ofsstr
3452 iso = d.isoformat()
3453 self.assertEqual(iso, datestr + 'T' + tailstr)
3454 self.assertEqual(iso, d.isoformat('T'))
3455 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
3456 self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
3457 self.assertEqual(str(d), datestr + ' ' + tailstr)
3458
3459 def test_replace(self):
3460 cls = self.theclass
3461 z100 = FixedOffset(100, "+100")
3462 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3463 args = [1, 2, 3, 4, 5, 6, 7, z100]
3464 base = cls(*args)
3465 self.assertEqual(base, base.replace())
3466
3467 i = 0
3468 for name, newval in (("year", 2),
3469 ("month", 3),
3470 ("day", 4),
3471 ("hour", 5),
3472 ("minute", 6),
3473 ("second", 7),
3474 ("microsecond", 8),
3475 ("tzinfo", zm200)):
3476 newargs = args[:]
3477 newargs[i] = newval
3478 expected = cls(*newargs)
3479 got = base.replace(**{name: newval})
3480 self.assertEqual(expected, got)
3481 i += 1
3482
3483 # Ensure we can get rid of a tzinfo.
3484 self.assertEqual(base.tzname(), "+100")
3485 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003486 self.assertIsNone(base2.tzinfo)
3487 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003488
3489 # Ensure we can add one.
3490 base3 = base2.replace(tzinfo=z100)
3491 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003492 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003493
3494 # Out of bounds.
3495 base = cls(2000, 2, 29)
3496 self.assertRaises(ValueError, base.replace, year=2001)
3497
3498 def test_more_astimezone(self):
3499 # The inherited test_astimezone covered some trivial and error cases.
3500 fnone = FixedOffset(None, "None")
3501 f44m = FixedOffset(44, "44")
3502 fm5h = FixedOffset(-timedelta(hours=5), "m300")
3503
3504 dt = self.theclass.now(tz=f44m)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003505 self.assertIs(dt.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003506 # Replacing with degenerate tzinfo raises an exception.
3507 self.assertRaises(ValueError, dt.astimezone, fnone)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003508 # Replacing with same tzinfo makes no change.
3509 x = dt.astimezone(dt.tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003510 self.assertIs(x.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003511 self.assertEqual(x.date(), dt.date())
3512 self.assertEqual(x.time(), dt.time())
3513
3514 # Replacing with different tzinfo does adjust.
3515 got = dt.astimezone(fm5h)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003516 self.assertIs(got.tzinfo, fm5h)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003517 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
3518 expected = dt - dt.utcoffset() # in effect, convert to UTC
3519 expected += fm5h.utcoffset(dt) # and from there to local time
3520 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
3521 self.assertEqual(got.date(), expected.date())
3522 self.assertEqual(got.time(), expected.time())
3523 self.assertEqual(got.timetz(), expected.timetz())
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003524 self.assertIs(got.tzinfo, expected.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003525 self.assertEqual(got, expected)
3526
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003527 @support.run_with_tz('UTC')
3528 def test_astimezone_default_utc(self):
3529 dt = self.theclass.now(timezone.utc)
3530 self.assertEqual(dt.astimezone(None), dt)
3531 self.assertEqual(dt.astimezone(), dt)
3532
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003533 # Note that offset in TZ variable has the opposite sign to that
3534 # produced by %z directive.
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003535 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
3536 def test_astimezone_default_eastern(self):
3537 dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
3538 local = dt.astimezone()
3539 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003540 self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003541 dt = self.theclass(2012, 11, 4, 5, 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"), "-0400 EDT")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003545
Alexander Belopolsky1dcf4f92016-03-25 15:42:59 -04003546 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
3547 def test_astimezone_default_near_fold(self):
3548 # Issue #26616.
3549 u = datetime(2015, 11, 1, 5, tzinfo=timezone.utc)
3550 t = u.astimezone()
3551 s = t.astimezone()
3552 self.assertEqual(t.tzinfo, s.tzinfo)
3553
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003554 def test_aware_subtract(self):
3555 cls = self.theclass
3556
3557 # Ensure that utcoffset() is ignored when the operands have the
3558 # same tzinfo member.
3559 class OperandDependentOffset(tzinfo):
3560 def utcoffset(self, t):
3561 if t.minute < 10:
3562 # d0 and d1 equal after adjustment
3563 return timedelta(minutes=t.minute)
3564 else:
3565 # d2 off in the weeds
3566 return timedelta(minutes=59)
3567
3568 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
3569 d0 = base.replace(minute=3)
3570 d1 = base.replace(minute=9)
3571 d2 = base.replace(minute=11)
3572 for x in d0, d1, d2:
3573 for y in d0, d1, d2:
3574 got = x - y
3575 expected = timedelta(minutes=x.minute - y.minute)
3576 self.assertEqual(got, expected)
3577
3578 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
3579 # ignored.
3580 base = cls(8, 9, 10, 11, 12, 13, 14)
3581 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3582 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3583 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3584 for x in d0, d1, d2:
3585 for y in d0, d1, d2:
3586 got = x - y
3587 if (x is d0 or x is d1) and (y is d0 or y is d1):
3588 expected = timedelta(0)
3589 elif x is y is d2:
3590 expected = timedelta(0)
3591 elif x is d2:
3592 expected = timedelta(minutes=(11-59)-0)
3593 else:
3594 assert y is d2
3595 expected = timedelta(minutes=0-(11-59))
3596 self.assertEqual(got, expected)
3597
3598 def test_mixed_compare(self):
3599 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
3600 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
3601 self.assertEqual(t1, t2)
3602 t2 = t2.replace(tzinfo=None)
3603 self.assertEqual(t1, t2)
3604 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3605 self.assertEqual(t1, t2)
3606 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04003607 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003608
3609 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
3610 class Varies(tzinfo):
3611 def __init__(self):
3612 self.offset = timedelta(minutes=22)
3613 def utcoffset(self, t):
3614 self.offset += timedelta(minutes=1)
3615 return self.offset
3616
3617 v = Varies()
3618 t1 = t2.replace(tzinfo=v)
3619 t2 = t2.replace(tzinfo=v)
3620 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3621 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3622 self.assertEqual(t1, t2)
3623
3624 # But if they're not identical, it isn't ignored.
3625 t2 = t2.replace(tzinfo=Varies())
3626 self.assertTrue(t1 < t2) # t1's offset counter still going up
3627
3628 def test_subclass_datetimetz(self):
3629
3630 class C(self.theclass):
3631 theAnswer = 42
3632
3633 def __new__(cls, *args, **kws):
3634 temp = kws.copy()
3635 extra = temp.pop('extra')
3636 result = self.theclass.__new__(cls, *args, **temp)
3637 result.extra = extra
3638 return result
3639
3640 def newmeth(self, start):
3641 return start + self.hour + self.year
3642
3643 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3644
3645 dt1 = self.theclass(*args)
3646 dt2 = C(*args, **{'extra': 7})
3647
3648 self.assertEqual(dt2.__class__, C)
3649 self.assertEqual(dt2.theAnswer, 42)
3650 self.assertEqual(dt2.extra, 7)
3651 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3652 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
3653
3654# Pain to set up DST-aware tzinfo classes.
3655
3656def first_sunday_on_or_after(dt):
3657 days_to_go = 6 - dt.weekday()
3658 if days_to_go:
3659 dt += timedelta(days_to_go)
3660 return dt
3661
3662ZERO = timedelta(0)
3663MINUTE = timedelta(minutes=1)
3664HOUR = timedelta(hours=1)
3665DAY = timedelta(days=1)
3666# In the US, DST starts at 2am (standard time) on the first Sunday in April.
3667DSTSTART = datetime(1, 4, 1, 2)
3668# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
3669# which is the first Sunday on or after Oct 25. Because we view 1:MM as
3670# being standard time on that day, there is no spelling in local time of
3671# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
3672DSTEND = datetime(1, 10, 25, 1)
3673
3674class USTimeZone(tzinfo):
3675
3676 def __init__(self, hours, reprname, stdname, dstname):
3677 self.stdoffset = timedelta(hours=hours)
3678 self.reprname = reprname
3679 self.stdname = stdname
3680 self.dstname = dstname
3681
3682 def __repr__(self):
3683 return self.reprname
3684
3685 def tzname(self, dt):
3686 if self.dst(dt):
3687 return self.dstname
3688 else:
3689 return self.stdname
3690
3691 def utcoffset(self, dt):
3692 return self.stdoffset + self.dst(dt)
3693
3694 def dst(self, dt):
3695 if dt is None or dt.tzinfo is None:
3696 # An exception instead may be sensible here, in one or more of
3697 # the cases.
3698 return ZERO
3699 assert dt.tzinfo is self
3700
3701 # Find first Sunday in April.
3702 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
3703 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
3704
3705 # Find last Sunday in October.
3706 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
3707 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
3708
3709 # Can't compare naive to aware objects, so strip the timezone from
3710 # dt first.
3711 if start <= dt.replace(tzinfo=None) < end:
3712 return HOUR
3713 else:
3714 return ZERO
3715
3716Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
3717Central = USTimeZone(-6, "Central", "CST", "CDT")
3718Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
3719Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
3720utc_real = FixedOffset(0, "UTC", 0)
3721# For better test coverage, we want another flavor of UTC that's west of
3722# the Eastern and Pacific timezones.
3723utc_fake = FixedOffset(-12*60, "UTCfake", 0)
3724
3725class TestTimezoneConversions(unittest.TestCase):
3726 # The DST switch times for 2002, in std time.
3727 dston = datetime(2002, 4, 7, 2)
3728 dstoff = datetime(2002, 10, 27, 1)
3729
3730 theclass = datetime
3731
3732 # Check a time that's inside DST.
3733 def checkinside(self, dt, tz, utc, dston, dstoff):
3734 self.assertEqual(dt.dst(), HOUR)
3735
3736 # Conversion to our own timezone is always an identity.
3737 self.assertEqual(dt.astimezone(tz), dt)
3738
3739 asutc = dt.astimezone(utc)
3740 there_and_back = asutc.astimezone(tz)
3741
3742 # Conversion to UTC and back isn't always an identity here,
3743 # because there are redundant spellings (in local time) of
3744 # UTC time when DST begins: the clock jumps from 1:59:59
3745 # to 3:00:00, and a local time of 2:MM:SS doesn't really
3746 # make sense then. The classes above treat 2:MM:SS as
3747 # daylight time then (it's "after 2am"), really an alias
3748 # for 1:MM:SS standard time. The latter form is what
3749 # conversion back from UTC produces.
3750 if dt.date() == dston.date() and dt.hour == 2:
3751 # We're in the redundant hour, and coming back from
3752 # UTC gives the 1:MM:SS standard-time spelling.
3753 self.assertEqual(there_and_back + HOUR, dt)
3754 # Although during was considered to be in daylight
3755 # time, there_and_back is not.
3756 self.assertEqual(there_and_back.dst(), ZERO)
3757 # They're the same times in UTC.
3758 self.assertEqual(there_and_back.astimezone(utc),
3759 dt.astimezone(utc))
3760 else:
3761 # We're not in the redundant hour.
3762 self.assertEqual(dt, there_and_back)
3763
3764 # Because we have a redundant spelling when DST begins, there is
Ezio Melotti3b3499b2011-03-16 11:35:38 +02003765 # (unfortunately) an hour when DST ends that can't be spelled at all in
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003766 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
3767 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
3768 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
3769 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
3770 # expressed in local time. Nevertheless, we want conversion back
3771 # from UTC to mimic the local clock's "repeat an hour" behavior.
3772 nexthour_utc = asutc + HOUR
3773 nexthour_tz = nexthour_utc.astimezone(tz)
3774 if dt.date() == dstoff.date() and dt.hour == 0:
3775 # We're in the hour before the last DST hour. The last DST hour
3776 # is ineffable. We want the conversion back to repeat 1:MM.
3777 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3778 nexthour_utc += HOUR
3779 nexthour_tz = nexthour_utc.astimezone(tz)
3780 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3781 else:
3782 self.assertEqual(nexthour_tz - dt, HOUR)
3783
3784 # Check a time that's outside DST.
3785 def checkoutside(self, dt, tz, utc):
3786 self.assertEqual(dt.dst(), ZERO)
3787
3788 # Conversion to our own timezone is always an identity.
3789 self.assertEqual(dt.astimezone(tz), dt)
3790
3791 # Converting to UTC and back is an identity too.
3792 asutc = dt.astimezone(utc)
3793 there_and_back = asutc.astimezone(tz)
3794 self.assertEqual(dt, there_and_back)
3795
3796 def convert_between_tz_and_utc(self, tz, utc):
3797 dston = self.dston.replace(tzinfo=tz)
3798 # Because 1:MM on the day DST ends is taken as being standard time,
3799 # there is no spelling in tz for the last hour of daylight time.
3800 # For purposes of the test, the last hour of DST is 0:MM, which is
3801 # taken as being daylight time (and 1:MM is taken as being standard
3802 # time).
3803 dstoff = self.dstoff.replace(tzinfo=tz)
3804 for delta in (timedelta(weeks=13),
3805 DAY,
3806 HOUR,
3807 timedelta(minutes=1),
3808 timedelta(microseconds=1)):
3809
3810 self.checkinside(dston, tz, utc, dston, dstoff)
3811 for during in dston + delta, dstoff - delta:
3812 self.checkinside(during, tz, utc, dston, dstoff)
3813
3814 self.checkoutside(dstoff, tz, utc)
3815 for outside in dston - delta, dstoff + delta:
3816 self.checkoutside(outside, tz, utc)
3817
3818 def test_easy(self):
3819 # Despite the name of this test, the endcases are excruciating.
3820 self.convert_between_tz_and_utc(Eastern, utc_real)
3821 self.convert_between_tz_and_utc(Pacific, utc_real)
3822 self.convert_between_tz_and_utc(Eastern, utc_fake)
3823 self.convert_between_tz_and_utc(Pacific, utc_fake)
3824 # The next is really dancing near the edge. It works because
3825 # Pacific and Eastern are far enough apart that their "problem
3826 # hours" don't overlap.
3827 self.convert_between_tz_and_utc(Eastern, Pacific)
3828 self.convert_between_tz_and_utc(Pacific, Eastern)
3829 # OTOH, these fail! Don't enable them. The difficulty is that
3830 # the edge case tests assume that every hour is representable in
3831 # the "utc" class. This is always true for a fixed-offset tzinfo
3832 # class (lke utc_real and utc_fake), but not for Eastern or Central.
3833 # For these adjacent DST-aware time zones, the range of time offsets
3834 # tested ends up creating hours in the one that aren't representable
3835 # in the other. For the same reason, we would see failures in the
3836 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
3837 # offset deltas in convert_between_tz_and_utc().
3838 #
3839 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
3840 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
3841
3842 def test_tricky(self):
3843 # 22:00 on day before daylight starts.
3844 fourback = self.dston - timedelta(hours=4)
3845 ninewest = FixedOffset(-9*60, "-0900", 0)
3846 fourback = fourback.replace(tzinfo=ninewest)
3847 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
3848 # 2", we should get the 3 spelling.
3849 # If we plug 22:00 the day before into Eastern, it "looks like std
3850 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
3851 # to 22:00 lands on 2:00, which makes no sense in local time (the
3852 # local clock jumps from 1 to 3). The point here is to make sure we
3853 # get the 3 spelling.
3854 expected = self.dston.replace(hour=3)
3855 got = fourback.astimezone(Eastern).replace(tzinfo=None)
3856 self.assertEqual(expected, got)
3857
3858 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
3859 # case we want the 1:00 spelling.
3860 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
3861 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
3862 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
3863 # spelling.
3864 expected = self.dston.replace(hour=1)
3865 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
3866 self.assertEqual(expected, got)
3867
3868 # Now on the day DST ends, we want "repeat an hour" behavior.
3869 # UTC 4:MM 5:MM 6:MM 7:MM checking these
3870 # EST 23:MM 0:MM 1:MM 2:MM
3871 # EDT 0:MM 1:MM 2:MM 3:MM
3872 # wall 0:MM 1:MM 1:MM 2:MM against these
3873 for utc in utc_real, utc_fake:
3874 for tz in Eastern, Pacific:
3875 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
3876 # Convert that to UTC.
3877 first_std_hour -= tz.utcoffset(None)
3878 # Adjust for possibly fake UTC.
3879 asutc = first_std_hour + utc.utcoffset(None)
3880 # First UTC hour to convert; this is 4:00 when utc=utc_real &
3881 # tz=Eastern.
3882 asutcbase = asutc.replace(tzinfo=utc)
3883 for tzhour in (0, 1, 1, 2):
3884 expectedbase = self.dstoff.replace(hour=tzhour)
3885 for minute in 0, 30, 59:
3886 expected = expectedbase.replace(minute=minute)
3887 asutc = asutcbase.replace(minute=minute)
3888 astz = asutc.astimezone(tz)
3889 self.assertEqual(astz.replace(tzinfo=None), expected)
3890 asutcbase += HOUR
3891
3892
3893 def test_bogus_dst(self):
3894 class ok(tzinfo):
3895 def utcoffset(self, dt): return HOUR
3896 def dst(self, dt): return HOUR
3897
3898 now = self.theclass.now().replace(tzinfo=utc_real)
3899 # Doesn't blow up.
3900 now.astimezone(ok())
3901
3902 # Does blow up.
3903 class notok(ok):
3904 def dst(self, dt): return None
3905 self.assertRaises(ValueError, now.astimezone, notok())
3906
3907 # Sometimes blow up. In the following, tzinfo.dst()
3908 # implementation may return None or not None depending on
3909 # whether DST is assumed to be in effect. In this situation,
3910 # a ValueError should be raised by astimezone().
3911 class tricky_notok(ok):
3912 def dst(self, dt):
3913 if dt.year == 2000:
3914 return None
3915 else:
3916 return 10*HOUR
3917 dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
3918 self.assertRaises(ValueError, dt.astimezone, tricky_notok())
3919
3920 def test_fromutc(self):
3921 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
3922 now = datetime.utcnow().replace(tzinfo=utc_real)
3923 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3924 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
3925 enow = Eastern.fromutc(now) # doesn't blow up
3926 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3927 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3928 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3929
3930 # Always converts UTC to standard time.
3931 class FauxUSTimeZone(USTimeZone):
3932 def fromutc(self, dt):
3933 return dt + self.stdoffset
3934 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
3935
3936 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
3937 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
3938 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
3939
3940 # Check around DST start.
3941 start = self.dston.replace(hour=4, tzinfo=Eastern)
3942 fstart = start.replace(tzinfo=FEastern)
3943 for wall in 23, 0, 1, 3, 4, 5:
3944 expected = start.replace(hour=wall)
3945 if wall == 23:
3946 expected -= timedelta(days=1)
3947 got = Eastern.fromutc(start)
3948 self.assertEqual(expected, got)
3949
3950 expected = fstart + FEastern.stdoffset
3951 got = FEastern.fromutc(fstart)
3952 self.assertEqual(expected, got)
3953
3954 # Ensure astimezone() calls fromutc() too.
3955 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3956 self.assertEqual(expected, got)
3957
3958 start += HOUR
3959 fstart += HOUR
3960
3961 # Check around DST end.
3962 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
3963 fstart = start.replace(tzinfo=FEastern)
3964 for wall in 0, 1, 1, 2, 3, 4:
3965 expected = start.replace(hour=wall)
3966 got = Eastern.fromutc(start)
3967 self.assertEqual(expected, got)
3968
3969 expected = fstart + FEastern.stdoffset
3970 got = FEastern.fromutc(fstart)
3971 self.assertEqual(expected, got)
3972
3973 # Ensure astimezone() calls fromutc() too.
3974 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3975 self.assertEqual(expected, got)
3976
3977 start += HOUR
3978 fstart += HOUR
3979
3980
3981#############################################################################
3982# oddballs
3983
3984class Oddballs(unittest.TestCase):
3985
3986 def test_bug_1028306(self):
3987 # Trying to compare a date to a datetime should act like a mixed-
3988 # type comparison, despite that datetime is a subclass of date.
3989 as_date = date.today()
3990 as_datetime = datetime.combine(as_date, time())
3991 self.assertTrue(as_date != as_datetime)
3992 self.assertTrue(as_datetime != as_date)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003993 self.assertFalse(as_date == as_datetime)
3994 self.assertFalse(as_datetime == as_date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003995 self.assertRaises(TypeError, lambda: as_date < as_datetime)
3996 self.assertRaises(TypeError, lambda: as_datetime < as_date)
3997 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
3998 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
3999 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
Raymond Hettinger15f44ab2016-08-30 10:47:49 -07004004 # Nevertheless, comparison should work with the base-class (date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004005 # projection if use of a date method is forced.
4006 self.assertEqual(as_date.__eq__(as_datetime), True)
4007 different_day = (as_date.day + 1) % 20 + 1
4008 as_different = as_datetime.replace(day= different_day)
4009 self.assertEqual(as_date.__eq__(as_different), False)
4010
4011 # And date should compare with other subclasses of date. If a
4012 # subclass wants to stop this, it's up to the subclass to do so.
4013 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
4014 self.assertEqual(as_date, date_sc)
4015 self.assertEqual(date_sc, as_date)
4016
4017 # Ditto for datetimes.
4018 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
4019 as_date.day, 0, 0, 0)
4020 self.assertEqual(as_datetime, datetime_sc)
4021 self.assertEqual(datetime_sc, as_datetime)
4022
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004023 def test_extra_attributes(self):
4024 for x in [date.today(),
4025 time(),
4026 datetime.utcnow(),
4027 timedelta(),
4028 tzinfo(),
4029 timezone(timedelta())]:
4030 with self.assertRaises(AttributeError):
4031 x.abc = 1
4032
4033 def test_check_arg_types(self):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004034 class Number:
4035 def __init__(self, value):
4036 self.value = value
4037 def __int__(self):
4038 return self.value
4039
4040 for xx in [decimal.Decimal(10),
4041 decimal.Decimal('10.9'),
4042 Number(10)]:
4043 self.assertEqual(datetime(10, 10, 10, 10, 10, 10, 10),
4044 datetime(xx, xx, xx, xx, xx, xx, xx))
4045
4046 with self.assertRaisesRegex(TypeError, '^an integer is required '
R David Murray44b548d2016-09-08 13:59:53 -04004047 r'\(got type str\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004048 datetime(10, 10, '10')
4049
4050 f10 = Number(10.9)
4051 with self.assertRaisesRegex(TypeError, '^__int__ returned non-int '
R David Murray44b548d2016-09-08 13:59:53 -04004052 r'\(type float\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004053 datetime(10, 10, f10)
4054
4055 class Float(float):
4056 pass
4057 s10 = Float(10.9)
4058 with self.assertRaisesRegex(TypeError, '^integer argument expected, '
4059 'got float$'):
4060 datetime(10, 10, s10)
4061
4062 with self.assertRaises(TypeError):
4063 datetime(10., 10, 10)
4064 with self.assertRaises(TypeError):
4065 datetime(10, 10., 10)
4066 with self.assertRaises(TypeError):
4067 datetime(10, 10, 10.)
4068 with self.assertRaises(TypeError):
4069 datetime(10, 10, 10, 10.)
4070 with self.assertRaises(TypeError):
4071 datetime(10, 10, 10, 10, 10.)
4072 with self.assertRaises(TypeError):
4073 datetime(10, 10, 10, 10, 10, 10.)
4074 with self.assertRaises(TypeError):
4075 datetime(10, 10, 10, 10, 10, 10, 10.)
4076
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004077#############################################################################
4078# Local Time Disambiguation
4079
4080# An experimental reimplementation of fromutc that respects the "fold" flag.
4081
4082class tzinfo2(tzinfo):
4083
4084 def fromutc(self, dt):
4085 "datetime in UTC -> datetime in local time."
4086
4087 if not isinstance(dt, datetime):
4088 raise TypeError("fromutc() requires a datetime argument")
4089 if dt.tzinfo is not self:
4090 raise ValueError("dt.tzinfo is not self")
4091 # Returned value satisfies
4092 # dt + ldt.utcoffset() = ldt
4093 off0 = dt.replace(fold=0).utcoffset()
4094 off1 = dt.replace(fold=1).utcoffset()
4095 if off0 is None or off1 is None or dt.dst() is None:
4096 raise ValueError
4097 if off0 == off1:
4098 ldt = dt + off0
4099 off1 = ldt.utcoffset()
4100 if off0 == off1:
4101 return ldt
4102 # Now, we discovered both possible offsets, so
4103 # we can just try four possible solutions:
4104 for off in [off0, off1]:
4105 ldt = dt + off
4106 if ldt.utcoffset() == off:
4107 return ldt
4108 ldt = ldt.replace(fold=1)
4109 if ldt.utcoffset() == off:
4110 return ldt
4111
4112 raise ValueError("No suitable local time found")
4113
4114# Reimplementing simplified US timezones to respect the "fold" flag:
4115
4116class USTimeZone2(tzinfo2):
4117
4118 def __init__(self, hours, reprname, stdname, dstname):
4119 self.stdoffset = timedelta(hours=hours)
4120 self.reprname = reprname
4121 self.stdname = stdname
4122 self.dstname = dstname
4123
4124 def __repr__(self):
4125 return self.reprname
4126
4127 def tzname(self, dt):
4128 if self.dst(dt):
4129 return self.dstname
4130 else:
4131 return self.stdname
4132
4133 def utcoffset(self, dt):
4134 return self.stdoffset + self.dst(dt)
4135
4136 def dst(self, dt):
4137 if dt is None or dt.tzinfo is None:
4138 # An exception instead may be sensible here, in one or more of
4139 # the cases.
4140 return ZERO
4141 assert dt.tzinfo is self
4142
4143 # Find first Sunday in April.
4144 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
4145 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
4146
4147 # Find last Sunday in October.
4148 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
4149 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
4150
4151 # Can't compare naive to aware objects, so strip the timezone from
4152 # dt first.
4153 dt = dt.replace(tzinfo=None)
4154 if start + HOUR <= dt < end:
4155 # DST is in effect.
4156 return HOUR
4157 elif end <= dt < end + HOUR:
4158 # Fold (an ambiguous hour): use dt.fold to disambiguate.
4159 return ZERO if dt.fold else HOUR
4160 elif start <= dt < start + HOUR:
4161 # Gap (a non-existent hour): reverse the fold rule.
4162 return HOUR if dt.fold else ZERO
4163 else:
4164 # DST is off.
4165 return ZERO
4166
4167Eastern2 = USTimeZone2(-5, "Eastern2", "EST", "EDT")
4168Central2 = USTimeZone2(-6, "Central2", "CST", "CDT")
4169Mountain2 = USTimeZone2(-7, "Mountain2", "MST", "MDT")
4170Pacific2 = USTimeZone2(-8, "Pacific2", "PST", "PDT")
4171
4172# Europe_Vilnius_1941 tzinfo implementation reproduces the following
4173# 1941 transition from Olson's tzdist:
4174#
4175# Zone NAME GMTOFF RULES FORMAT [UNTIL]
4176# ZoneEurope/Vilnius 1:00 - CET 1940 Aug 3
4177# 3:00 - MSK 1941 Jun 24
4178# 1:00 C-Eur CE%sT 1944 Aug
4179#
4180# $ zdump -v Europe/Vilnius | grep 1941
4181# Europe/Vilnius Mon Jun 23 20:59:59 1941 UTC = Mon Jun 23 23:59:59 1941 MSK isdst=0 gmtoff=10800
4182# Europe/Vilnius Mon Jun 23 21:00:00 1941 UTC = Mon Jun 23 23:00:00 1941 CEST isdst=1 gmtoff=7200
4183
4184class Europe_Vilnius_1941(tzinfo):
4185 def _utc_fold(self):
4186 return [datetime(1941, 6, 23, 21, tzinfo=self), # Mon Jun 23 21:00:00 1941 UTC
4187 datetime(1941, 6, 23, 22, tzinfo=self)] # Mon Jun 23 22:00:00 1941 UTC
4188
4189 def _loc_fold(self):
4190 return [datetime(1941, 6, 23, 23, tzinfo=self), # Mon Jun 23 23:00:00 1941 MSK / CEST
4191 datetime(1941, 6, 24, 0, tzinfo=self)] # Mon Jun 24 00:00:00 1941 CEST
4192
4193 def utcoffset(self, dt):
4194 fold_start, fold_stop = self._loc_fold()
4195 if dt < fold_start:
4196 return 3 * HOUR
4197 if dt < fold_stop:
4198 return (2 if dt.fold else 3) * HOUR
4199 # if dt >= fold_stop
4200 return 2 * HOUR
4201
4202 def dst(self, dt):
4203 fold_start, fold_stop = self._loc_fold()
4204 if dt < fold_start:
4205 return 0 * HOUR
4206 if dt < fold_stop:
4207 return (1 if dt.fold else 0) * HOUR
4208 # if dt >= fold_stop
4209 return 1 * HOUR
4210
4211 def tzname(self, dt):
4212 fold_start, fold_stop = self._loc_fold()
4213 if dt < fold_start:
4214 return 'MSK'
4215 if dt < fold_stop:
4216 return ('MSK', 'CEST')[dt.fold]
4217 # if dt >= fold_stop
4218 return 'CEST'
4219
4220 def fromutc(self, dt):
4221 assert dt.fold == 0
4222 assert dt.tzinfo is self
4223 if dt.year != 1941:
4224 raise NotImplementedError
4225 fold_start, fold_stop = self._utc_fold()
4226 if dt < fold_start:
4227 return dt + 3 * HOUR
4228 if dt < fold_stop:
4229 return (dt + 2 * HOUR).replace(fold=1)
4230 # if dt >= fold_stop
4231 return dt + 2 * HOUR
4232
4233
4234class TestLocalTimeDisambiguation(unittest.TestCase):
4235
4236 def test_vilnius_1941_fromutc(self):
4237 Vilnius = Europe_Vilnius_1941()
4238
4239 gdt = datetime(1941, 6, 23, 20, 59, 59, tzinfo=timezone.utc)
4240 ldt = gdt.astimezone(Vilnius)
4241 self.assertEqual(ldt.strftime("%c %Z%z"),
4242 'Mon Jun 23 23:59:59 1941 MSK+0300')
4243 self.assertEqual(ldt.fold, 0)
4244 self.assertFalse(ldt.dst())
4245
4246 gdt = datetime(1941, 6, 23, 21, tzinfo=timezone.utc)
4247 ldt = gdt.astimezone(Vilnius)
4248 self.assertEqual(ldt.strftime("%c %Z%z"),
4249 'Mon Jun 23 23:00:00 1941 CEST+0200')
4250 self.assertEqual(ldt.fold, 1)
4251 self.assertTrue(ldt.dst())
4252
4253 gdt = datetime(1941, 6, 23, 22, tzinfo=timezone.utc)
4254 ldt = gdt.astimezone(Vilnius)
4255 self.assertEqual(ldt.strftime("%c %Z%z"),
4256 'Tue Jun 24 00:00:00 1941 CEST+0200')
4257 self.assertEqual(ldt.fold, 0)
4258 self.assertTrue(ldt.dst())
4259
4260 def test_vilnius_1941_toutc(self):
4261 Vilnius = Europe_Vilnius_1941()
4262
4263 ldt = datetime(1941, 6, 23, 22, 59, 59, tzinfo=Vilnius)
4264 gdt = ldt.astimezone(timezone.utc)
4265 self.assertEqual(gdt.strftime("%c %Z"),
4266 'Mon Jun 23 19:59:59 1941 UTC')
4267
4268 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius)
4269 gdt = ldt.astimezone(timezone.utc)
4270 self.assertEqual(gdt.strftime("%c %Z"),
4271 'Mon Jun 23 20:59:59 1941 UTC')
4272
4273 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius, fold=1)
4274 gdt = ldt.astimezone(timezone.utc)
4275 self.assertEqual(gdt.strftime("%c %Z"),
4276 'Mon Jun 23 21:59:59 1941 UTC')
4277
4278 ldt = datetime(1941, 6, 24, 0, tzinfo=Vilnius)
4279 gdt = ldt.astimezone(timezone.utc)
4280 self.assertEqual(gdt.strftime("%c %Z"),
4281 'Mon Jun 23 22:00:00 1941 UTC')
4282
4283
4284 def test_constructors(self):
4285 t = time(0, fold=1)
4286 dt = datetime(1, 1, 1, fold=1)
4287 self.assertEqual(t.fold, 1)
4288 self.assertEqual(dt.fold, 1)
4289 with self.assertRaises(TypeError):
4290 time(0, 0, 0, 0, None, 0)
4291
4292 def test_member(self):
4293 dt = datetime(1, 1, 1, fold=1)
4294 t = dt.time()
4295 self.assertEqual(t.fold, 1)
4296 t = dt.timetz()
4297 self.assertEqual(t.fold, 1)
4298
4299 def test_replace(self):
4300 t = time(0)
4301 dt = datetime(1, 1, 1)
4302 self.assertEqual(t.replace(fold=1).fold, 1)
4303 self.assertEqual(dt.replace(fold=1).fold, 1)
4304 self.assertEqual(t.replace(fold=0).fold, 0)
4305 self.assertEqual(dt.replace(fold=0).fold, 0)
4306 # Check that replacement of other fields does not change "fold".
4307 t = t.replace(fold=1, tzinfo=Eastern)
4308 dt = dt.replace(fold=1, tzinfo=Eastern)
4309 self.assertEqual(t.replace(tzinfo=None).fold, 1)
4310 self.assertEqual(dt.replace(tzinfo=None).fold, 1)
4311 # Check that fold is a keyword-only argument
4312 with self.assertRaises(TypeError):
4313 t.replace(1, 1, 1, None, 1)
4314 with self.assertRaises(TypeError):
4315 dt.replace(1, 1, 1, 1, 1, 1, 1, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04004316
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004317 def test_comparison(self):
4318 t = time(0)
4319 dt = datetime(1, 1, 1)
4320 self.assertEqual(t, t.replace(fold=1))
4321 self.assertEqual(dt, dt.replace(fold=1))
4322
4323 def test_hash(self):
4324 t = time(0)
4325 dt = datetime(1, 1, 1)
4326 self.assertEqual(hash(t), hash(t.replace(fold=1)))
4327 self.assertEqual(hash(dt), hash(dt.replace(fold=1)))
4328
4329 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4330 def test_fromtimestamp(self):
4331 s = 1414906200
4332 dt0 = datetime.fromtimestamp(s)
4333 dt1 = datetime.fromtimestamp(s + 3600)
4334 self.assertEqual(dt0.fold, 0)
4335 self.assertEqual(dt1.fold, 1)
4336
4337 @support.run_with_tz('Australia/Lord_Howe')
4338 def test_fromtimestamp_lord_howe(self):
4339 tm = _time.localtime(1.4e9)
4340 if _time.strftime('%Z%z', tm) != 'LHST+1030':
4341 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
4342 # $ TZ=Australia/Lord_Howe date -r 1428158700
4343 # Sun Apr 5 01:45:00 LHDT 2015
4344 # $ TZ=Australia/Lord_Howe date -r 1428160500
4345 # Sun Apr 5 01:45:00 LHST 2015
4346 s = 1428158700
4347 t0 = datetime.fromtimestamp(s)
4348 t1 = datetime.fromtimestamp(s + 1800)
4349 self.assertEqual(t0, t1)
4350 self.assertEqual(t0.fold, 0)
4351 self.assertEqual(t1.fold, 1)
4352
4353
4354 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4355 def test_timestamp(self):
4356 dt0 = datetime(2014, 11, 2, 1, 30)
4357 dt1 = dt0.replace(fold=1)
4358 self.assertEqual(dt0.timestamp() + 3600,
4359 dt1.timestamp())
4360
4361 @support.run_with_tz('Australia/Lord_Howe')
4362 def test_timestamp_lord_howe(self):
4363 tm = _time.localtime(1.4e9)
4364 if _time.strftime('%Z%z', tm) != 'LHST+1030':
4365 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
4366 t = datetime(2015, 4, 5, 1, 45)
4367 s0 = t.replace(fold=0).timestamp()
4368 s1 = t.replace(fold=1).timestamp()
4369 self.assertEqual(s0 + 1800, s1)
4370
4371
4372 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4373 def test_astimezone(self):
4374 dt0 = datetime(2014, 11, 2, 1, 30)
4375 dt1 = dt0.replace(fold=1)
4376 # Convert both naive instances to aware.
4377 adt0 = dt0.astimezone()
4378 adt1 = dt1.astimezone()
4379 # Check that the first instance in DST zone and the second in STD
4380 self.assertEqual(adt0.tzname(), 'EDT')
4381 self.assertEqual(adt1.tzname(), 'EST')
4382 self.assertEqual(adt0 + HOUR, adt1)
4383 # Aware instances with fixed offset tzinfo's always have fold=0
4384 self.assertEqual(adt0.fold, 0)
4385 self.assertEqual(adt1.fold, 0)
4386
4387
4388 def test_pickle_fold(self):
4389 t = time(fold=1)
4390 dt = datetime(1, 1, 1, fold=1)
4391 for pickler, unpickler, proto in pickle_choices:
4392 for x in [t, dt]:
4393 s = pickler.dumps(x, proto)
4394 y = unpickler.loads(s)
4395 self.assertEqual(x, y)
4396 self.assertEqual((0 if proto < 4 else x.fold), y.fold)
4397
4398 def test_repr(self):
4399 t = time(fold=1)
4400 dt = datetime(1, 1, 1, fold=1)
4401 self.assertEqual(repr(t), 'datetime.time(0, 0, fold=1)')
4402 self.assertEqual(repr(dt),
4403 'datetime.datetime(1, 1, 1, 0, 0, fold=1)')
4404
4405 def test_dst(self):
4406 # Let's first establish that things work in regular times.
4407 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
4408 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
4409 self.assertEqual(dt_summer.dst(), HOUR)
4410 self.assertEqual(dt_winter.dst(), ZERO)
4411 # The disambiguation flag is ignored
4412 self.assertEqual(dt_summer.replace(fold=1).dst(), HOUR)
4413 self.assertEqual(dt_winter.replace(fold=1).dst(), ZERO)
4414
4415 # Pick local time in the fold.
4416 for minute in [0, 30, 59]:
4417 dt = datetime(2002, 10, 27, 1, minute, tzinfo=Eastern2)
4418 # With fold=0 (the default) it is in DST.
4419 self.assertEqual(dt.dst(), HOUR)
4420 # With fold=1 it is in STD.
4421 self.assertEqual(dt.replace(fold=1).dst(), ZERO)
4422
4423 # Pick local time in the gap.
4424 for minute in [0, 30, 59]:
4425 dt = datetime(2002, 4, 7, 2, minute, tzinfo=Eastern2)
4426 # With fold=0 (the default) it is in STD.
4427 self.assertEqual(dt.dst(), ZERO)
4428 # With fold=1 it is in DST.
4429 self.assertEqual(dt.replace(fold=1).dst(), HOUR)
4430
4431
4432 def test_utcoffset(self):
4433 # Let's first establish that things work in regular times.
4434 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
4435 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
4436 self.assertEqual(dt_summer.utcoffset(), -4 * HOUR)
4437 self.assertEqual(dt_winter.utcoffset(), -5 * HOUR)
4438 # The disambiguation flag is ignored
4439 self.assertEqual(dt_summer.replace(fold=1).utcoffset(), -4 * HOUR)
4440 self.assertEqual(dt_winter.replace(fold=1).utcoffset(), -5 * HOUR)
4441
4442 def test_fromutc(self):
4443 # Let's first establish that things work in regular times.
4444 u_summer = datetime(2002, 10, 27, 6, tzinfo=Eastern2) - timedelta.resolution
4445 u_winter = datetime(2002, 10, 27, 7, tzinfo=Eastern2)
4446 t_summer = Eastern2.fromutc(u_summer)
4447 t_winter = Eastern2.fromutc(u_winter)
4448 self.assertEqual(t_summer, u_summer - 4 * HOUR)
4449 self.assertEqual(t_winter, u_winter - 5 * HOUR)
4450 self.assertEqual(t_summer.fold, 0)
4451 self.assertEqual(t_winter.fold, 0)
4452
4453 # What happens in the fall-back fold?
4454 u = datetime(2002, 10, 27, 5, 30, tzinfo=Eastern2)
4455 t0 = Eastern2.fromutc(u)
4456 u += HOUR
4457 t1 = Eastern2.fromutc(u)
4458 self.assertEqual(t0, t1)
4459 self.assertEqual(t0.fold, 0)
4460 self.assertEqual(t1.fold, 1)
4461 # The tricky part is when u is in the local fold:
4462 u = datetime(2002, 10, 27, 1, 30, tzinfo=Eastern2)
4463 t = Eastern2.fromutc(u)
4464 self.assertEqual((t.day, t.hour), (26, 21))
4465 # .. or gets into the local fold after a standard time adjustment
4466 u = datetime(2002, 10, 27, 6, 30, tzinfo=Eastern2)
4467 t = Eastern2.fromutc(u)
4468 self.assertEqual((t.day, t.hour), (27, 1))
4469
4470 # What happens in the spring-forward gap?
4471 u = datetime(2002, 4, 7, 2, 0, tzinfo=Eastern2)
4472 t = Eastern2.fromutc(u)
4473 self.assertEqual((t.day, t.hour), (6, 21))
4474
4475 def test_mixed_compare_regular(self):
4476 t = datetime(2000, 1, 1, tzinfo=Eastern2)
4477 self.assertEqual(t, t.astimezone(timezone.utc))
4478 t = datetime(2000, 6, 1, tzinfo=Eastern2)
4479 self.assertEqual(t, t.astimezone(timezone.utc))
4480
4481 def test_mixed_compare_fold(self):
4482 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
4483 t_fold_utc = t_fold.astimezone(timezone.utc)
4484 self.assertNotEqual(t_fold, t_fold_utc)
4485
4486 def test_mixed_compare_gap(self):
4487 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
4488 t_gap_utc = t_gap.astimezone(timezone.utc)
4489 self.assertNotEqual(t_gap, t_gap_utc)
4490
4491 def test_hash_aware(self):
4492 t = datetime(2000, 1, 1, tzinfo=Eastern2)
4493 self.assertEqual(hash(t), hash(t.replace(fold=1)))
4494 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
4495 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
4496 self.assertEqual(hash(t_fold), hash(t_fold.replace(fold=1)))
4497 self.assertEqual(hash(t_gap), hash(t_gap.replace(fold=1)))
4498
4499SEC = timedelta(0, 1)
4500
4501def pairs(iterable):
4502 a, b = itertools.tee(iterable)
4503 next(b, None)
4504 return zip(a, b)
4505
4506class ZoneInfo(tzinfo):
4507 zoneroot = '/usr/share/zoneinfo'
4508 def __init__(self, ut, ti):
4509 """
4510
4511 :param ut: array
4512 Array of transition point timestamps
4513 :param ti: list
4514 A list of (offset, isdst, abbr) tuples
4515 :return: None
4516 """
4517 self.ut = ut
4518 self.ti = ti
4519 self.lt = self.invert(ut, ti)
4520
4521 @staticmethod
4522 def invert(ut, ti):
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04004523 lt = (array('q', ut), array('q', ut))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004524 if ut:
4525 offset = ti[0][0] // SEC
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04004526 lt[0][0] += offset
4527 lt[1][0] += offset
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004528 for i in range(1, len(ut)):
4529 lt[0][i] += ti[i-1][0] // SEC
4530 lt[1][i] += ti[i][0] // SEC
4531 return lt
4532
4533 @classmethod
4534 def fromfile(cls, fileobj):
4535 if fileobj.read(4).decode() != "TZif":
4536 raise ValueError("not a zoneinfo file")
4537 fileobj.seek(32)
4538 counts = array('i')
4539 counts.fromfile(fileobj, 3)
4540 if sys.byteorder != 'big':
4541 counts.byteswap()
4542
4543 ut = array('i')
4544 ut.fromfile(fileobj, counts[0])
4545 if sys.byteorder != 'big':
4546 ut.byteswap()
4547
4548 type_indices = array('B')
4549 type_indices.fromfile(fileobj, counts[0])
4550
4551 ttis = []
4552 for i in range(counts[1]):
4553 ttis.append(struct.unpack(">lbb", fileobj.read(6)))
4554
4555 abbrs = fileobj.read(counts[2])
4556
4557 # Convert ttis
4558 for i, (gmtoff, isdst, abbrind) in enumerate(ttis):
4559 abbr = abbrs[abbrind:abbrs.find(0, abbrind)].decode()
4560 ttis[i] = (timedelta(0, gmtoff), isdst, abbr)
4561
4562 ti = [None] * len(ut)
4563 for i, idx in enumerate(type_indices):
4564 ti[i] = ttis[idx]
4565
4566 self = cls(ut, ti)
4567
4568 return self
4569
4570 @classmethod
4571 def fromname(cls, name):
4572 path = os.path.join(cls.zoneroot, name)
4573 with open(path, 'rb') as f:
4574 return cls.fromfile(f)
4575
4576 EPOCHORDINAL = date(1970, 1, 1).toordinal()
4577
4578 def fromutc(self, dt):
4579 """datetime in UTC -> datetime in local time."""
4580
4581 if not isinstance(dt, datetime):
4582 raise TypeError("fromutc() requires a datetime argument")
4583 if dt.tzinfo is not self:
4584 raise ValueError("dt.tzinfo is not self")
4585
4586 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
4587 + dt.hour * 3600
4588 + dt.minute * 60
4589 + dt.second)
4590
4591 if timestamp < self.ut[1]:
4592 tti = self.ti[0]
4593 fold = 0
4594 else:
4595 idx = bisect.bisect_right(self.ut, timestamp)
4596 assert self.ut[idx-1] <= timestamp
4597 assert idx == len(self.ut) or timestamp < self.ut[idx]
4598 tti_prev, tti = self.ti[idx-2:idx]
4599 # Detect fold
4600 shift = tti_prev[0] - tti[0]
4601 fold = (shift > timedelta(0, timestamp - self.ut[idx-1]))
4602 dt += tti[0]
4603 if fold:
4604 return dt.replace(fold=1)
4605 else:
4606 return dt
4607
4608 def _find_ti(self, dt, i):
4609 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
4610 + dt.hour * 3600
4611 + dt.minute * 60
4612 + dt.second)
4613 lt = self.lt[dt.fold]
4614 idx = bisect.bisect_right(lt, timestamp)
4615
4616 return self.ti[max(0, idx - 1)][i]
4617
4618 def utcoffset(self, dt):
4619 return self._find_ti(dt, 0)
4620
4621 def dst(self, dt):
4622 isdst = self._find_ti(dt, 1)
4623 # XXX: We cannot accurately determine the "save" value,
4624 # so let's return 1h whenever DST is in effect. Since
4625 # we don't use dst() in fromutc(), it is unlikely that
4626 # it will be needed for anything more than bool(dst()).
4627 return ZERO if isdst else HOUR
4628
4629 def tzname(self, dt):
4630 return self._find_ti(dt, 2)
4631
4632 @classmethod
4633 def zonenames(cls, zonedir=None):
4634 if zonedir is None:
4635 zonedir = cls.zoneroot
Alexander Belopolsky1b8f26c2016-08-11 11:01:45 -04004636 zone_tab = os.path.join(zonedir, 'zone.tab')
4637 try:
4638 f = open(zone_tab)
4639 except OSError:
4640 return
4641 with f:
4642 for line in f:
4643 line = line.strip()
4644 if line and not line.startswith('#'):
4645 yield line.split()[2]
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004646
4647 @classmethod
4648 def stats(cls, start_year=1):
4649 count = gap_count = fold_count = zeros_count = 0
4650 min_gap = min_fold = timedelta.max
4651 max_gap = max_fold = ZERO
4652 min_gap_datetime = max_gap_datetime = datetime.min
4653 min_gap_zone = max_gap_zone = None
4654 min_fold_datetime = max_fold_datetime = datetime.min
4655 min_fold_zone = max_fold_zone = None
4656 stats_since = datetime(start_year, 1, 1) # Starting from 1970 eliminates a lot of noise
4657 for zonename in cls.zonenames():
4658 count += 1
4659 tz = cls.fromname(zonename)
4660 for dt, shift in tz.transitions():
4661 if dt < stats_since:
4662 continue
4663 if shift > ZERO:
4664 gap_count += 1
4665 if (shift, dt) > (max_gap, max_gap_datetime):
4666 max_gap = shift
4667 max_gap_zone = zonename
4668 max_gap_datetime = dt
4669 if (shift, datetime.max - dt) < (min_gap, datetime.max - min_gap_datetime):
4670 min_gap = shift
4671 min_gap_zone = zonename
4672 min_gap_datetime = dt
4673 elif shift < ZERO:
4674 fold_count += 1
4675 shift = -shift
4676 if (shift, dt) > (max_fold, max_fold_datetime):
4677 max_fold = shift
4678 max_fold_zone = zonename
4679 max_fold_datetime = dt
4680 if (shift, datetime.max - dt) < (min_fold, datetime.max - min_fold_datetime):
4681 min_fold = shift
4682 min_fold_zone = zonename
4683 min_fold_datetime = dt
4684 else:
4685 zeros_count += 1
4686 trans_counts = (gap_count, fold_count, zeros_count)
4687 print("Number of zones: %5d" % count)
4688 print("Number of transitions: %5d = %d (gaps) + %d (folds) + %d (zeros)" %
4689 ((sum(trans_counts),) + trans_counts))
4690 print("Min gap: %16s at %s in %s" % (min_gap, min_gap_datetime, min_gap_zone))
4691 print("Max gap: %16s at %s in %s" % (max_gap, max_gap_datetime, max_gap_zone))
4692 print("Min fold: %16s at %s in %s" % (min_fold, min_fold_datetime, min_fold_zone))
4693 print("Max fold: %16s at %s in %s" % (max_fold, max_fold_datetime, max_fold_zone))
4694
4695
4696 def transitions(self):
4697 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
4698 shift = ti[0] - prev_ti[0]
4699 yield datetime.utcfromtimestamp(t), shift
4700
4701 def nondst_folds(self):
4702 """Find all folds with the same value of isdst on both sides of the transition."""
4703 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
4704 shift = ti[0] - prev_ti[0]
4705 if shift < ZERO and ti[1] == prev_ti[1]:
4706 yield datetime.utcfromtimestamp(t), -shift, prev_ti[2], ti[2]
4707
4708 @classmethod
4709 def print_all_nondst_folds(cls, same_abbr=False, start_year=1):
4710 count = 0
4711 for zonename in cls.zonenames():
4712 tz = cls.fromname(zonename)
4713 for dt, shift, prev_abbr, abbr in tz.nondst_folds():
4714 if dt.year < start_year or same_abbr and prev_abbr != abbr:
4715 continue
4716 count += 1
4717 print("%3d) %-30s %s %10s %5s -> %s" %
4718 (count, zonename, dt, shift, prev_abbr, abbr))
4719
4720 def folds(self):
4721 for t, shift in self.transitions():
4722 if shift < ZERO:
4723 yield t, -shift
4724
4725 def gaps(self):
4726 for t, shift in self.transitions():
4727 if shift > ZERO:
4728 yield t, shift
4729
4730 def zeros(self):
4731 for t, shift in self.transitions():
4732 if not shift:
4733 yield t
4734
4735
4736class ZoneInfoTest(unittest.TestCase):
4737 zonename = 'America/New_York'
4738
4739 def setUp(self):
4740 if sys.platform == "win32":
4741 self.skipTest("Skipping zoneinfo tests on Windows")
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04004742 try:
4743 self.tz = ZoneInfo.fromname(self.zonename)
4744 except FileNotFoundError as err:
4745 self.skipTest("Skipping %s: %s" % (self.zonename, err))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004746
4747 def assertEquivDatetimes(self, a, b):
4748 self.assertEqual((a.replace(tzinfo=None), a.fold, id(a.tzinfo)),
4749 (b.replace(tzinfo=None), b.fold, id(b.tzinfo)))
4750
4751 def test_folds(self):
4752 tz = self.tz
4753 for dt, shift in tz.folds():
4754 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
4755 udt = dt + x
4756 ldt = tz.fromutc(udt.replace(tzinfo=tz))
4757 self.assertEqual(ldt.fold, 1)
4758 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
4759 self.assertEquivDatetimes(adt, ldt)
4760 utcoffset = ldt.utcoffset()
4761 self.assertEqual(ldt.replace(tzinfo=None), udt + utcoffset)
4762 # Round trip
4763 self.assertEquivDatetimes(ldt.astimezone(timezone.utc),
4764 udt.replace(tzinfo=timezone.utc))
4765
4766
4767 for x in [-timedelta.resolution, shift]:
4768 udt = dt + x
4769 udt = udt.replace(tzinfo=tz)
4770 ldt = tz.fromutc(udt)
4771 self.assertEqual(ldt.fold, 0)
4772
4773 def test_gaps(self):
4774 tz = self.tz
4775 for dt, shift in tz.gaps():
4776 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
4777 udt = dt + x
4778 udt = udt.replace(tzinfo=tz)
4779 ldt = tz.fromutc(udt)
4780 self.assertEqual(ldt.fold, 0)
4781 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
4782 self.assertEquivDatetimes(adt, ldt)
4783 utcoffset = ldt.utcoffset()
4784 self.assertEqual(ldt.replace(tzinfo=None), udt.replace(tzinfo=None) + utcoffset)
4785 # Create a local time inside the gap
4786 ldt = tz.fromutc(dt.replace(tzinfo=tz)) - shift + x
4787 self.assertLess(ldt.replace(fold=1).utcoffset(),
4788 ldt.replace(fold=0).utcoffset(),
4789 "At %s." % ldt)
4790
4791 for x in [-timedelta.resolution, shift]:
4792 udt = dt + x
4793 ldt = tz.fromutc(udt.replace(tzinfo=tz))
4794 self.assertEqual(ldt.fold, 0)
4795
4796 def test_system_transitions(self):
4797 if ('Riyadh8' in self.zonename or
4798 # From tzdata NEWS file:
4799 # The files solar87, solar88, and solar89 are no longer distributed.
4800 # They were a negative experiment - that is, a demonstration that
4801 # tz data can represent solar time only with some difficulty and error.
4802 # Their presence in the distribution caused confusion, as Riyadh
4803 # civil time was generally not solar time in those years.
4804 self.zonename.startswith('right/')):
4805 self.skipTest("Skipping %s" % self.zonename)
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04004806 tz = self.tz
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004807 TZ = os.environ.get('TZ')
4808 os.environ['TZ'] = self.zonename
4809 try:
4810 _time.tzset()
4811 for udt, shift in tz.transitions():
Alexander Belopolsky10c2dd22016-08-12 19:08:15 -04004812 if udt.year >= 2037:
4813 # System support for times around the end of 32-bit time_t
4814 # and later is flaky on many systems.
4815 break
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004816 s0 = (udt - datetime(1970, 1, 1)) // SEC
4817 ss = shift // SEC # shift seconds
4818 for x in [-40 * 3600, -20*3600, -1, 0,
4819 ss - 1, ss + 20 * 3600, ss + 40 * 3600]:
4820 s = s0 + x
4821 sdt = datetime.fromtimestamp(s)
4822 tzdt = datetime.fromtimestamp(s, tz).replace(tzinfo=None)
4823 self.assertEquivDatetimes(sdt, tzdt)
4824 s1 = sdt.timestamp()
4825 self.assertEqual(s, s1)
4826 if ss > 0: # gap
4827 # Create local time inside the gap
4828 dt = datetime.fromtimestamp(s0) - shift / 2
4829 ts0 = dt.timestamp()
4830 ts1 = dt.replace(fold=1).timestamp()
4831 self.assertEqual(ts0, s0 + ss / 2)
4832 self.assertEqual(ts1, s0 - ss / 2)
4833 finally:
4834 if TZ is None:
4835 del os.environ['TZ']
4836 else:
4837 os.environ['TZ'] = TZ
4838 _time.tzset()
4839
4840
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04004841class ZoneInfoCompleteTest(unittest.TestSuite):
4842 def __init__(self):
4843 tests = []
4844 if is_resource_enabled('tzdata'):
4845 for name in ZoneInfo.zonenames():
4846 Test = type('ZoneInfoTest[%s]' % name, (ZoneInfoTest,), {})
4847 Test.zonename = name
4848 for method in dir(Test):
4849 if method.startswith('test_'):
4850 tests.append(Test(method))
4851 super().__init__(tests)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004852
4853# Iran had a sub-minute UTC offset before 1946.
4854class IranTest(ZoneInfoTest):
Alexander Belopolsky07e2a0a2016-07-30 11:41:02 -04004855 zonename = 'Asia/Tehran'
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004856
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04004857def load_tests(loader, standard_tests, pattern):
4858 standard_tests.addTest(ZoneInfoCompleteTest())
4859 return standard_tests
4860
4861
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004862if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -05004863 unittest.main()