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