blob: 988c6f722e21b0f858d0a46e839ddd013c1fcee8 [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)
1332
1333 def test_compare(self):
1334 t1 = self.theclass(2, 3, 4)
1335 t2 = self.theclass(2, 3, 4)
1336 self.assertEqual(t1, t2)
1337 self.assertTrue(t1 <= t2)
1338 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001339 self.assertFalse(t1 != t2)
1340 self.assertFalse(t1 < t2)
1341 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001342
1343 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
1344 t2 = self.theclass(*args) # this is larger than t1
1345 self.assertTrue(t1 < t2)
1346 self.assertTrue(t2 > t1)
1347 self.assertTrue(t1 <= t2)
1348 self.assertTrue(t2 >= t1)
1349 self.assertTrue(t1 != t2)
1350 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001351 self.assertFalse(t1 == t2)
1352 self.assertFalse(t2 == t1)
1353 self.assertFalse(t1 > t2)
1354 self.assertFalse(t2 < t1)
1355 self.assertFalse(t1 >= t2)
1356 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001357
1358 for badarg in OTHERSTUFF:
1359 self.assertEqual(t1 == badarg, False)
1360 self.assertEqual(t1 != badarg, True)
1361 self.assertEqual(badarg == t1, False)
1362 self.assertEqual(badarg != t1, True)
1363
1364 self.assertRaises(TypeError, lambda: t1 < badarg)
1365 self.assertRaises(TypeError, lambda: t1 > badarg)
1366 self.assertRaises(TypeError, lambda: t1 >= badarg)
1367 self.assertRaises(TypeError, lambda: badarg <= t1)
1368 self.assertRaises(TypeError, lambda: badarg < t1)
1369 self.assertRaises(TypeError, lambda: badarg > t1)
1370 self.assertRaises(TypeError, lambda: badarg >= t1)
1371
1372 def test_mixed_compare(self):
1373 our = self.theclass(2000, 4, 5)
1374
1375 # Our class can be compared for equality to other classes
1376 self.assertEqual(our == 1, False)
1377 self.assertEqual(1 == our, False)
1378 self.assertEqual(our != 1, True)
1379 self.assertEqual(1 != our, True)
1380
1381 # But the ordering is undefined
1382 self.assertRaises(TypeError, lambda: our < 1)
1383 self.assertRaises(TypeError, lambda: 1 < our)
1384
1385 # Repeat those tests with a different class
1386
1387 class SomeClass:
1388 pass
1389
1390 their = SomeClass()
1391 self.assertEqual(our == their, False)
1392 self.assertEqual(their == our, False)
1393 self.assertEqual(our != their, True)
1394 self.assertEqual(their != our, True)
1395 self.assertRaises(TypeError, lambda: our < their)
1396 self.assertRaises(TypeError, lambda: their < our)
1397
1398 # However, if the other class explicitly defines ordering
1399 # relative to our class, it is allowed to do so
1400
1401 class LargerThanAnything:
1402 def __lt__(self, other):
1403 return False
1404 def __le__(self, other):
1405 return isinstance(other, LargerThanAnything)
1406 def __eq__(self, other):
1407 return isinstance(other, LargerThanAnything)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001408 def __gt__(self, other):
1409 return not isinstance(other, LargerThanAnything)
1410 def __ge__(self, other):
1411 return True
1412
1413 their = LargerThanAnything()
1414 self.assertEqual(our == their, False)
1415 self.assertEqual(their == our, False)
1416 self.assertEqual(our != their, True)
1417 self.assertEqual(their != our, True)
1418 self.assertEqual(our < their, True)
1419 self.assertEqual(their < our, False)
1420
1421 def test_bool(self):
1422 # All dates are considered true.
1423 self.assertTrue(self.theclass.min)
1424 self.assertTrue(self.theclass.max)
1425
Alexander Belopolsky89da3492011-05-02 13:14:24 -04001426 def test_strftime_y2k(self):
1427 for y in (1, 49, 70, 99, 100, 999, 1000, 1970):
Florent Xicluna49ce0682011-11-01 12:56:14 +01001428 d = self.theclass(y, 1, 1)
1429 # Issue 13305: For years < 1000, the value is not always
1430 # padded to 4 digits across platforms. The C standard
1431 # assumes year >= 1900, so it does not specify the number
1432 # of digits.
1433 if d.strftime("%Y") != '%04d' % y:
1434 # Year 42 returns '42', not padded
1435 self.assertEqual(d.strftime("%Y"), '%d' % y)
Victor Stinner15a83e82016-03-12 08:16:48 +01001436 # '0042' is obtained anyway
1437 self.assertEqual(d.strftime("%4Y"), '%04d' % y)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001438
1439 def test_replace(self):
1440 cls = self.theclass
1441 args = [1, 2, 3]
1442 base = cls(*args)
1443 self.assertEqual(base, base.replace())
1444
1445 i = 0
1446 for name, newval in (("year", 2),
1447 ("month", 3),
1448 ("day", 4)):
1449 newargs = args[:]
1450 newargs[i] = newval
1451 expected = cls(*newargs)
1452 got = base.replace(**{name: newval})
1453 self.assertEqual(expected, got)
1454 i += 1
1455
1456 # Out of bounds.
1457 base = cls(2000, 2, 29)
1458 self.assertRaises(ValueError, base.replace, year=2001)
1459
1460 def test_subclass_date(self):
1461
1462 class C(self.theclass):
1463 theAnswer = 42
1464
1465 def __new__(cls, *args, **kws):
1466 temp = kws.copy()
1467 extra = temp.pop('extra')
1468 result = self.theclass.__new__(cls, *args, **temp)
1469 result.extra = extra
1470 return result
1471
1472 def newmeth(self, start):
1473 return start + self.year + self.month
1474
1475 args = 2003, 4, 14
1476
1477 dt1 = self.theclass(*args)
1478 dt2 = C(*args, **{'extra': 7})
1479
1480 self.assertEqual(dt2.__class__, C)
1481 self.assertEqual(dt2.theAnswer, 42)
1482 self.assertEqual(dt2.extra, 7)
1483 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1484 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1485
1486 def test_pickling_subclass_date(self):
1487
1488 args = 6, 7, 23
1489 orig = SubclassDate(*args)
1490 for pickler, unpickler, proto in pickle_choices:
1491 green = pickler.dumps(orig, proto)
1492 derived = unpickler.loads(green)
1493 self.assertEqual(orig, derived)
1494
1495 def test_backdoor_resistance(self):
1496 # For fast unpickling, the constructor accepts a pickle byte string.
1497 # This is a low-overhead backdoor. A user can (by intent or
1498 # mistake) pass a string directly, which (if it's the right length)
1499 # will get treated like a pickle, and bypass the normal sanity
1500 # checks in the constructor. This can create insane objects.
1501 # The constructor doesn't want to burn the time to validate all
1502 # fields, but does check the month field. This stops, e.g.,
1503 # datetime.datetime('1995-03-25') from yielding an insane object.
1504 base = b'1995-03-25'
1505 if not issubclass(self.theclass, datetime):
1506 base = base[:4]
1507 for month_byte in b'9', b'\0', b'\r', b'\xff':
1508 self.assertRaises(TypeError, self.theclass,
1509 base[:2] + month_byte + base[3:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001510 if issubclass(self.theclass, datetime):
1511 # Good bytes, but bad tzinfo:
1512 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
1513 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001514
1515 for ord_byte in range(1, 13):
1516 # This shouldn't blow up because of the month byte alone. If
1517 # the implementation changes to do more-careful checking, it may
1518 # blow up because other fields are insane.
1519 self.theclass(base[:2] + bytes([ord_byte]) + base[3:])
1520
1521#############################################################################
1522# datetime tests
1523
1524class SubclassDatetime(datetime):
1525 sub_var = 1
1526
1527class TestDateTime(TestDate):
1528
1529 theclass = datetime
1530
1531 def test_basic_attributes(self):
1532 dt = self.theclass(2002, 3, 1, 12, 0)
1533 self.assertEqual(dt.year, 2002)
1534 self.assertEqual(dt.month, 3)
1535 self.assertEqual(dt.day, 1)
1536 self.assertEqual(dt.hour, 12)
1537 self.assertEqual(dt.minute, 0)
1538 self.assertEqual(dt.second, 0)
1539 self.assertEqual(dt.microsecond, 0)
1540
1541 def test_basic_attributes_nonzero(self):
1542 # Make sure all attributes are non-zero so bugs in
1543 # bit-shifting access show up.
1544 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1545 self.assertEqual(dt.year, 2002)
1546 self.assertEqual(dt.month, 3)
1547 self.assertEqual(dt.day, 1)
1548 self.assertEqual(dt.hour, 12)
1549 self.assertEqual(dt.minute, 59)
1550 self.assertEqual(dt.second, 59)
1551 self.assertEqual(dt.microsecond, 8000)
1552
1553 def test_roundtrip(self):
1554 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1555 self.theclass.now()):
1556 # Verify dt -> string -> datetime identity.
1557 s = repr(dt)
1558 self.assertTrue(s.startswith('datetime.'))
1559 s = s[9:]
1560 dt2 = eval(s)
1561 self.assertEqual(dt, dt2)
1562
1563 # Verify identity via reconstructing from pieces.
1564 dt2 = self.theclass(dt.year, dt.month, dt.day,
1565 dt.hour, dt.minute, dt.second,
1566 dt.microsecond)
1567 self.assertEqual(dt, dt2)
1568
1569 def test_isoformat(self):
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001570 t = self.theclass(1, 2, 3, 4, 5, 1, 123)
1571 self.assertEqual(t.isoformat(), "0001-02-03T04:05:01.000123")
1572 self.assertEqual(t.isoformat('T'), "0001-02-03T04:05:01.000123")
1573 self.assertEqual(t.isoformat(' '), "0001-02-03 04:05:01.000123")
1574 self.assertEqual(t.isoformat('\x00'), "0001-02-03\x0004:05:01.000123")
1575 self.assertEqual(t.isoformat(timespec='hours'), "0001-02-03T04")
1576 self.assertEqual(t.isoformat(timespec='minutes'), "0001-02-03T04:05")
1577 self.assertEqual(t.isoformat(timespec='seconds'), "0001-02-03T04:05:01")
1578 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1579 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000123")
1580 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01.000123")
1581 self.assertEqual(t.isoformat(sep=' ', timespec='minutes'), "0001-02-03 04:05")
1582 self.assertRaises(ValueError, t.isoformat, timespec='foo')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001583 # str is ISO format with the separator forced to a blank.
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001584 self.assertEqual(str(t), "0001-02-03 04:05:01.000123")
1585
1586 t = self.theclass(1, 2, 3, 4, 5, 1, 999500, tzinfo=timezone.utc)
1587 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999+00:00")
1588
1589 t = self.theclass(1, 2, 3, 4, 5, 1, 999500)
1590 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999")
1591
1592 t = self.theclass(1, 2, 3, 4, 5, 1)
1593 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01")
1594 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1595 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000000")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001596
1597 t = self.theclass(2, 3, 2)
1598 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1599 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1600 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1601 # str is ISO format with the separator forced to a blank.
1602 self.assertEqual(str(t), "0002-03-02 00:00:00")
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001603 # ISO format with timezone
1604 tz = FixedOffset(timedelta(seconds=16), 'XXX')
1605 t = self.theclass(2, 3, 2, tzinfo=tz)
1606 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00+00:00:16")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001607
1608 def test_format(self):
1609 dt = self.theclass(2007, 9, 10, 4, 5, 1, 123)
1610 self.assertEqual(dt.__format__(''), str(dt))
1611
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02001612 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001613 dt.__format__(123)
1614
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001615 # check that a derived class's __str__() gets called
1616 class A(self.theclass):
1617 def __str__(self):
1618 return 'A'
1619 a = A(2007, 9, 10, 4, 5, 1, 123)
1620 self.assertEqual(a.__format__(''), 'A')
1621
1622 # check that a derived class's strftime gets called
1623 class B(self.theclass):
1624 def strftime(self, format_spec):
1625 return 'B'
1626 b = B(2007, 9, 10, 4, 5, 1, 123)
1627 self.assertEqual(b.__format__(''), str(dt))
1628
1629 for fmt in ["m:%m d:%d y:%y",
1630 "m:%m d:%d y:%y H:%H M:%M S:%S",
1631 "%z %Z",
1632 ]:
1633 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1634 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1635 self.assertEqual(b.__format__(fmt), 'B')
1636
1637 def test_more_ctime(self):
1638 # Test fields that TestDate doesn't touch.
1639 import time
1640
1641 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1642 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1643 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1644 # out. The difference is that t.ctime() produces " 2" for the day,
1645 # but platform ctime() produces "02" for the day. According to
1646 # C99, t.ctime() is correct here.
1647 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1648
1649 # So test a case where that difference doesn't matter.
1650 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1651 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1652
1653 def test_tz_independent_comparing(self):
1654 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1655 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1656 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1657 self.assertEqual(dt1, dt3)
1658 self.assertTrue(dt2 > dt3)
1659
1660 # Make sure comparison doesn't forget microseconds, and isn't done
1661 # via comparing a float timestamp (an IEEE double doesn't have enough
1662 # precision to span microsecond resolution across years 1 thru 9999,
1663 # so comparing via timestamp necessarily calls some distinct values
1664 # equal).
1665 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1666 us = timedelta(microseconds=1)
1667 dt2 = dt1 + us
1668 self.assertEqual(dt2 - dt1, us)
1669 self.assertTrue(dt1 < dt2)
1670
1671 def test_strftime_with_bad_tzname_replace(self):
1672 # verify ok if tzinfo.tzname().replace() returns a non-string
1673 class MyTzInfo(FixedOffset):
1674 def tzname(self, dt):
1675 class MyStr(str):
1676 def replace(self, *args):
1677 return None
1678 return MyStr('name')
1679 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
1680 self.assertRaises(TypeError, t.strftime, '%Z')
1681
1682 def test_bad_constructor_arguments(self):
1683 # bad years
1684 self.theclass(MINYEAR, 1, 1) # no exception
1685 self.theclass(MAXYEAR, 1, 1) # no exception
1686 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1687 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1688 # bad months
1689 self.theclass(2000, 1, 1) # no exception
1690 self.theclass(2000, 12, 1) # no exception
1691 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1692 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1693 # bad days
1694 self.theclass(2000, 2, 29) # no exception
1695 self.theclass(2004, 2, 29) # no exception
1696 self.theclass(2400, 2, 29) # no exception
1697 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1698 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1699 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1700 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1701 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1702 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1703 # bad hours
1704 self.theclass(2000, 1, 31, 0) # no exception
1705 self.theclass(2000, 1, 31, 23) # no exception
1706 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1707 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1708 # bad minutes
1709 self.theclass(2000, 1, 31, 23, 0) # no exception
1710 self.theclass(2000, 1, 31, 23, 59) # no exception
1711 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1712 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1713 # bad seconds
1714 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1715 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1716 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1717 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1718 # bad microseconds
1719 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1720 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1721 self.assertRaises(ValueError, self.theclass,
1722 2000, 1, 31, 23, 59, 59, -1)
1723 self.assertRaises(ValueError, self.theclass,
1724 2000, 1, 31, 23, 59, 59,
1725 1000000)
Alexander Belopolsky47649ab2016-08-08 17:05:40 -04001726 # bad fold
1727 self.assertRaises(ValueError, self.theclass,
1728 2000, 1, 31, fold=-1)
1729 self.assertRaises(ValueError, self.theclass,
1730 2000, 1, 31, fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001731 # Positional fold:
1732 self.assertRaises(TypeError, self.theclass,
1733 2000, 1, 31, 23, 59, 59, 0, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04001734
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001735 def test_hash_equality(self):
1736 d = self.theclass(2000, 12, 31, 23, 30, 17)
1737 e = self.theclass(2000, 12, 31, 23, 30, 17)
1738 self.assertEqual(d, e)
1739 self.assertEqual(hash(d), hash(e))
1740
1741 dic = {d: 1}
1742 dic[e] = 2
1743 self.assertEqual(len(dic), 1)
1744 self.assertEqual(dic[d], 2)
1745 self.assertEqual(dic[e], 2)
1746
1747 d = self.theclass(2001, 1, 1, 0, 5, 17)
1748 e = self.theclass(2001, 1, 1, 0, 5, 17)
1749 self.assertEqual(d, e)
1750 self.assertEqual(hash(d), hash(e))
1751
1752 dic = {d: 1}
1753 dic[e] = 2
1754 self.assertEqual(len(dic), 1)
1755 self.assertEqual(dic[d], 2)
1756 self.assertEqual(dic[e], 2)
1757
1758 def test_computations(self):
1759 a = self.theclass(2002, 1, 31)
1760 b = self.theclass(1956, 1, 31)
1761 diff = a-b
1762 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1763 self.assertEqual(diff.seconds, 0)
1764 self.assertEqual(diff.microseconds, 0)
1765 a = self.theclass(2002, 3, 2, 17, 6)
1766 millisec = timedelta(0, 0, 1000)
1767 hour = timedelta(0, 3600)
1768 day = timedelta(1)
1769 week = timedelta(7)
1770 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1771 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1772 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1773 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1774 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1775 self.assertEqual(a - hour, a + -hour)
1776 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1777 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1778 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1779 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1780 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1781 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1782 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1783 self.assertEqual((a + week) - a, week)
1784 self.assertEqual((a + day) - a, day)
1785 self.assertEqual((a + hour) - a, hour)
1786 self.assertEqual((a + millisec) - a, millisec)
1787 self.assertEqual((a - week) - a, -week)
1788 self.assertEqual((a - day) - a, -day)
1789 self.assertEqual((a - hour) - a, -hour)
1790 self.assertEqual((a - millisec) - a, -millisec)
1791 self.assertEqual(a - (a + week), -week)
1792 self.assertEqual(a - (a + day), -day)
1793 self.assertEqual(a - (a + hour), -hour)
1794 self.assertEqual(a - (a + millisec), -millisec)
1795 self.assertEqual(a - (a - week), week)
1796 self.assertEqual(a - (a - day), day)
1797 self.assertEqual(a - (a - hour), hour)
1798 self.assertEqual(a - (a - millisec), millisec)
1799 self.assertEqual(a + (week + day + hour + millisec),
1800 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1801 self.assertEqual(a + (week + day + hour + millisec),
1802 (((a + week) + day) + hour) + millisec)
1803 self.assertEqual(a - (week + day + hour + millisec),
1804 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1805 self.assertEqual(a - (week + day + hour + millisec),
1806 (((a - week) - day) - hour) - millisec)
1807 # Add/sub ints or floats should be illegal
1808 for i in 1, 1.0:
1809 self.assertRaises(TypeError, lambda: a+i)
1810 self.assertRaises(TypeError, lambda: a-i)
1811 self.assertRaises(TypeError, lambda: i+a)
1812 self.assertRaises(TypeError, lambda: i-a)
1813
1814 # delta - datetime is senseless.
1815 self.assertRaises(TypeError, lambda: day - a)
1816 # mixing datetime and (delta or datetime) via * or // is senseless
1817 self.assertRaises(TypeError, lambda: day * a)
1818 self.assertRaises(TypeError, lambda: a * day)
1819 self.assertRaises(TypeError, lambda: day // a)
1820 self.assertRaises(TypeError, lambda: a // day)
1821 self.assertRaises(TypeError, lambda: a * a)
1822 self.assertRaises(TypeError, lambda: a // a)
1823 # datetime + datetime is senseless
1824 self.assertRaises(TypeError, lambda: a + a)
1825
1826 def test_pickling(self):
1827 args = 6, 7, 23, 20, 59, 1, 64**2
1828 orig = self.theclass(*args)
1829 for pickler, unpickler, proto in pickle_choices:
1830 green = pickler.dumps(orig, proto)
1831 derived = unpickler.loads(green)
1832 self.assertEqual(orig, derived)
1833
1834 def test_more_pickling(self):
1835 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
Serhiy Storchakabad12572014-12-15 14:03:42 +02001836 for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1837 s = pickle.dumps(a, proto)
1838 b = pickle.loads(s)
1839 self.assertEqual(b.year, 2003)
1840 self.assertEqual(b.month, 2)
1841 self.assertEqual(b.day, 7)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001842
1843 def test_pickling_subclass_datetime(self):
1844 args = 6, 7, 23, 20, 59, 1, 64**2
1845 orig = SubclassDatetime(*args)
1846 for pickler, unpickler, proto in pickle_choices:
1847 green = pickler.dumps(orig, proto)
1848 derived = unpickler.loads(green)
1849 self.assertEqual(orig, derived)
1850
1851 def test_more_compare(self):
1852 # The test_compare() inherited from TestDate covers the error cases.
1853 # We just want to test lexicographic ordering on the members datetime
1854 # has that date lacks.
1855 args = [2000, 11, 29, 20, 58, 16, 999998]
1856 t1 = self.theclass(*args)
1857 t2 = self.theclass(*args)
1858 self.assertEqual(t1, t2)
1859 self.assertTrue(t1 <= t2)
1860 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001861 self.assertFalse(t1 != t2)
1862 self.assertFalse(t1 < t2)
1863 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001864
1865 for i in range(len(args)):
1866 newargs = args[:]
1867 newargs[i] = args[i] + 1
1868 t2 = self.theclass(*newargs) # this is larger than t1
1869 self.assertTrue(t1 < t2)
1870 self.assertTrue(t2 > t1)
1871 self.assertTrue(t1 <= t2)
1872 self.assertTrue(t2 >= t1)
1873 self.assertTrue(t1 != t2)
1874 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001875 self.assertFalse(t1 == t2)
1876 self.assertFalse(t2 == t1)
1877 self.assertFalse(t1 > t2)
1878 self.assertFalse(t2 < t1)
1879 self.assertFalse(t1 >= t2)
1880 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001881
1882
1883 # A helper for timestamp constructor tests.
1884 def verify_field_equality(self, expected, got):
1885 self.assertEqual(expected.tm_year, got.year)
1886 self.assertEqual(expected.tm_mon, got.month)
1887 self.assertEqual(expected.tm_mday, got.day)
1888 self.assertEqual(expected.tm_hour, got.hour)
1889 self.assertEqual(expected.tm_min, got.minute)
1890 self.assertEqual(expected.tm_sec, got.second)
1891
1892 def test_fromtimestamp(self):
1893 import time
1894
1895 ts = time.time()
1896 expected = time.localtime(ts)
1897 got = self.theclass.fromtimestamp(ts)
1898 self.verify_field_equality(expected, got)
1899
1900 def test_utcfromtimestamp(self):
1901 import time
1902
1903 ts = time.time()
1904 expected = time.gmtime(ts)
1905 got = self.theclass.utcfromtimestamp(ts)
1906 self.verify_field_equality(expected, got)
1907
Alexander Belopolskya4415142012-06-08 12:33:09 -04001908 # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in
1909 # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0).
1910 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
1911 def test_timestamp_naive(self):
1912 t = self.theclass(1970, 1, 1)
1913 self.assertEqual(t.timestamp(), 18000.0)
1914 t = self.theclass(1970, 1, 1, 1, 2, 3, 4)
1915 self.assertEqual(t.timestamp(),
1916 18000.0 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001917 # Missing hour
1918 t0 = self.theclass(2012, 3, 11, 2, 30)
1919 t1 = t0.replace(fold=1)
1920 self.assertEqual(self.theclass.fromtimestamp(t1.timestamp()),
1921 t0 - timedelta(hours=1))
1922 self.assertEqual(self.theclass.fromtimestamp(t0.timestamp()),
1923 t1 + timedelta(hours=1))
Alexander Belopolskya4415142012-06-08 12:33:09 -04001924 # Ambiguous hour defaults to DST
1925 t = self.theclass(2012, 11, 4, 1, 30)
1926 self.assertEqual(self.theclass.fromtimestamp(t.timestamp()), t)
1927
1928 # Timestamp may raise an overflow error on some platforms
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001929 # XXX: Do we care to support the first and last year?
1930 for t in [self.theclass(2,1,1), self.theclass(9998,12,12)]:
Alexander Belopolskya4415142012-06-08 12:33:09 -04001931 try:
1932 s = t.timestamp()
1933 except OverflowError:
1934 pass
1935 else:
1936 self.assertEqual(self.theclass.fromtimestamp(s), t)
1937
1938 def test_timestamp_aware(self):
1939 t = self.theclass(1970, 1, 1, tzinfo=timezone.utc)
1940 self.assertEqual(t.timestamp(), 0.0)
1941 t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone.utc)
1942 self.assertEqual(t.timestamp(),
1943 3600 + 2*60 + 3 + 4*1e-6)
1944 t = self.theclass(1970, 1, 1, 1, 2, 3, 4,
1945 tzinfo=timezone(timedelta(hours=-5), 'EST'))
1946 self.assertEqual(t.timestamp(),
1947 18000 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001948
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001949 @support.run_with_tz('MSK-03') # Something east of Greenwich
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001950 def test_microsecond_rounding(self):
Victor Stinner5ebfe422015-09-18 14:52:15 +02001951 for fts in [self.theclass.fromtimestamp,
1952 self.theclass.utcfromtimestamp]:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001953 zero = fts(0)
1954 self.assertEqual(zero.second, 0)
1955 self.assertEqual(zero.microsecond, 0)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02001956 one = fts(1e-6)
Victor Stinner8050ca92012-03-14 00:17:05 +01001957 try:
1958 minus_one = fts(-1e-6)
1959 except OSError:
1960 # localtime(-1) and gmtime(-1) is not supported on Windows
1961 pass
1962 else:
1963 self.assertEqual(minus_one.second, 59)
1964 self.assertEqual(minus_one.microsecond, 999999)
Victor Stinner5d272cc2012-03-13 13:35:55 +01001965
Victor Stinner8050ca92012-03-14 00:17:05 +01001966 t = fts(-1e-8)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02001967 self.assertEqual(t, zero)
Victor Stinner8050ca92012-03-14 00:17:05 +01001968 t = fts(-9e-7)
1969 self.assertEqual(t, minus_one)
1970 t = fts(-1e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02001971 self.assertEqual(t, zero)
Victor Stinner8820a352015-09-05 10:50:20 +02001972 t = fts(-1/2**7)
1973 self.assertEqual(t.second, 59)
Victor Stinner7667f582015-09-09 01:02:23 +02001974 self.assertEqual(t.microsecond, 992188)
Victor Stinner5d272cc2012-03-13 13:35:55 +01001975
1976 t = fts(1e-7)
1977 self.assertEqual(t, zero)
1978 t = fts(9e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02001979 self.assertEqual(t, one)
Victor Stinner5d272cc2012-03-13 13:35:55 +01001980 t = fts(0.99999949)
1981 self.assertEqual(t.second, 0)
1982 self.assertEqual(t.microsecond, 999999)
1983 t = fts(0.9999999)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02001984 self.assertEqual(t.second, 1)
1985 self.assertEqual(t.microsecond, 0)
Victor Stinneradfefa52015-09-04 23:57:25 +02001986 t = fts(1/2**7)
1987 self.assertEqual(t.second, 0)
Victor Stinner7667f582015-09-09 01:02:23 +02001988 self.assertEqual(t.microsecond, 7812)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001989
1990 def test_insane_fromtimestamp(self):
1991 # It's possible that some platform maps time_t to double,
1992 # and that this test will fail there. This test should
1993 # exempt such platforms (provided they return reasonable
1994 # results!).
1995 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001996 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001997 insane)
1998
1999 def test_insane_utcfromtimestamp(self):
2000 # It's possible that some platform maps time_t to double,
2001 # and that this test will fail there. This test should
2002 # exempt such platforms (provided they return reasonable
2003 # results!).
2004 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002005 self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002006 insane)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002007
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002008 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2009 def test_negative_float_fromtimestamp(self):
2010 # The result is tz-dependent; at least test that this doesn't
2011 # fail (like it did before bug 1646728 was fixed).
2012 self.theclass.fromtimestamp(-1.05)
2013
2014 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2015 def test_negative_float_utcfromtimestamp(self):
2016 d = self.theclass.utcfromtimestamp(-1.05)
2017 self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
2018
2019 def test_utcnow(self):
2020 import time
2021
2022 # Call it a success if utcnow() and utcfromtimestamp() are within
2023 # a second of each other.
2024 tolerance = timedelta(seconds=1)
2025 for dummy in range(3):
2026 from_now = self.theclass.utcnow()
2027 from_timestamp = self.theclass.utcfromtimestamp(time.time())
2028 if abs(from_timestamp - from_now) <= tolerance:
2029 break
2030 # Else try again a few times.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002031 self.assertLessEqual(abs(from_timestamp - from_now), tolerance)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002032
2033 def test_strptime(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002034 string = '2004-12-01 13:02:47.197'
2035 format = '%Y-%m-%d %H:%M:%S.%f'
2036 expected = _strptime._strptime_datetime(self.theclass, string, format)
2037 got = self.theclass.strptime(string, format)
2038 self.assertEqual(expected, got)
2039 self.assertIs(type(expected), self.theclass)
2040 self.assertIs(type(got), self.theclass)
2041
2042 strptime = self.theclass.strptime
2043 self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
2044 self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
2045 # Only local timezone and UTC are supported
2046 for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
2047 (-_time.timezone, _time.tzname[0])):
2048 if tzseconds < 0:
2049 sign = '-'
2050 seconds = -tzseconds
2051 else:
2052 sign ='+'
2053 seconds = tzseconds
2054 hours, minutes = divmod(seconds//60, 60)
2055 dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
Martin Panterfca22322015-11-16 09:22:19 +00002056 dt = strptime(dtstr, "%z %Z")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002057 self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
2058 self.assertEqual(dt.tzname(), tzname)
2059 # Can produce inconsistent datetime
2060 dtstr, fmt = "+1234 UTC", "%z %Z"
2061 dt = strptime(dtstr, fmt)
2062 self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
2063 self.assertEqual(dt.tzname(), 'UTC')
2064 # yet will roundtrip
2065 self.assertEqual(dt.strftime(fmt), dtstr)
2066
2067 # Produce naive datetime if no %z is provided
2068 self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
2069
2070 with self.assertRaises(ValueError): strptime("-2400", "%z")
2071 with self.assertRaises(ValueError): strptime("-000", "%z")
2072
2073 def test_more_timetuple(self):
2074 # This tests fields beyond those tested by the TestDate.test_timetuple.
2075 t = self.theclass(2004, 12, 31, 6, 22, 33)
2076 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
2077 self.assertEqual(t.timetuple(),
2078 (t.year, t.month, t.day,
2079 t.hour, t.minute, t.second,
2080 t.weekday(),
2081 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
2082 -1))
2083 tt = t.timetuple()
2084 self.assertEqual(tt.tm_year, t.year)
2085 self.assertEqual(tt.tm_mon, t.month)
2086 self.assertEqual(tt.tm_mday, t.day)
2087 self.assertEqual(tt.tm_hour, t.hour)
2088 self.assertEqual(tt.tm_min, t.minute)
2089 self.assertEqual(tt.tm_sec, t.second)
2090 self.assertEqual(tt.tm_wday, t.weekday())
2091 self.assertEqual(tt.tm_yday, t.toordinal() -
2092 date(t.year, 1, 1).toordinal() + 1)
2093 self.assertEqual(tt.tm_isdst, -1)
2094
2095 def test_more_strftime(self):
2096 # This tests fields beyond those tested by the TestDate.test_strftime.
2097 t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
2098 self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
2099 "12 31 04 000047 33 22 06 366")
2100
2101 def test_extract(self):
2102 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2103 self.assertEqual(dt.date(), date(2002, 3, 4))
2104 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2105
2106 def test_combine(self):
2107 d = date(2002, 3, 4)
2108 t = time(18, 45, 3, 1234)
2109 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2110 combine = self.theclass.combine
2111 dt = combine(d, t)
2112 self.assertEqual(dt, expected)
2113
2114 dt = combine(time=t, date=d)
2115 self.assertEqual(dt, expected)
2116
2117 self.assertEqual(d, dt.date())
2118 self.assertEqual(t, dt.time())
2119 self.assertEqual(dt, combine(dt.date(), dt.time()))
2120
2121 self.assertRaises(TypeError, combine) # need an arg
2122 self.assertRaises(TypeError, combine, d) # need two args
2123 self.assertRaises(TypeError, combine, t, d) # args reversed
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002124 self.assertRaises(TypeError, combine, d, t, 1) # wrong tzinfo type
2125 self.assertRaises(TypeError, combine, d, t, 1, 2) # too many args
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002126 self.assertRaises(TypeError, combine, "date", "time") # wrong types
2127 self.assertRaises(TypeError, combine, d, "time") # wrong type
2128 self.assertRaises(TypeError, combine, "date", t) # wrong type
2129
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002130 # tzinfo= argument
2131 dt = combine(d, t, timezone.utc)
2132 self.assertIs(dt.tzinfo, timezone.utc)
2133 dt = combine(d, t, tzinfo=timezone.utc)
2134 self.assertIs(dt.tzinfo, timezone.utc)
2135 t = time()
2136 dt = combine(dt, t)
2137 self.assertEqual(dt.date(), d)
2138 self.assertEqual(dt.time(), t)
2139
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002140 def test_replace(self):
2141 cls = self.theclass
2142 args = [1, 2, 3, 4, 5, 6, 7]
2143 base = cls(*args)
2144 self.assertEqual(base, base.replace())
2145
2146 i = 0
2147 for name, newval in (("year", 2),
2148 ("month", 3),
2149 ("day", 4),
2150 ("hour", 5),
2151 ("minute", 6),
2152 ("second", 7),
2153 ("microsecond", 8)):
2154 newargs = args[:]
2155 newargs[i] = newval
2156 expected = cls(*newargs)
2157 got = base.replace(**{name: newval})
2158 self.assertEqual(expected, got)
2159 i += 1
2160
2161 # Out of bounds.
2162 base = cls(2000, 2, 29)
2163 self.assertRaises(ValueError, base.replace, year=2001)
2164
2165 def test_astimezone(self):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002166 return # The rest is no longer applicable
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002167 # Pretty boring! The TZ test is more interesting here. astimezone()
2168 # simply can't be applied to a naive object.
2169 dt = self.theclass.now()
2170 f = FixedOffset(44, "")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04002171 self.assertRaises(ValueError, dt.astimezone) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002172 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
2173 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
2174 self.assertRaises(ValueError, dt.astimezone, f) # naive
2175 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
2176
2177 class Bogus(tzinfo):
2178 def utcoffset(self, dt): return None
2179 def dst(self, dt): return timedelta(0)
2180 bog = Bogus()
2181 self.assertRaises(ValueError, dt.astimezone, bog) # naive
2182 self.assertRaises(ValueError,
2183 dt.replace(tzinfo=bog).astimezone, f)
2184
2185 class AlsoBogus(tzinfo):
2186 def utcoffset(self, dt): return timedelta(0)
2187 def dst(self, dt): return None
2188 alsobog = AlsoBogus()
2189 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
2190
2191 def test_subclass_datetime(self):
2192
2193 class C(self.theclass):
2194 theAnswer = 42
2195
2196 def __new__(cls, *args, **kws):
2197 temp = kws.copy()
2198 extra = temp.pop('extra')
2199 result = self.theclass.__new__(cls, *args, **temp)
2200 result.extra = extra
2201 return result
2202
2203 def newmeth(self, start):
2204 return start + self.year + self.month + self.second
2205
2206 args = 2003, 4, 14, 12, 13, 41
2207
2208 dt1 = self.theclass(*args)
2209 dt2 = C(*args, **{'extra': 7})
2210
2211 self.assertEqual(dt2.__class__, C)
2212 self.assertEqual(dt2.theAnswer, 42)
2213 self.assertEqual(dt2.extra, 7)
2214 self.assertEqual(dt1.toordinal(), dt2.toordinal())
2215 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
2216 dt1.second - 7)
2217
2218class TestSubclassDateTime(TestDateTime):
2219 theclass = SubclassDatetime
2220 # Override tests not designed for subclass
Zachary Ware9fe6d862013-12-08 00:20:35 -06002221 @unittest.skip('not appropriate for subclasses')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002222 def test_roundtrip(self):
2223 pass
2224
2225class SubclassTime(time):
2226 sub_var = 1
2227
2228class TestTime(HarmlessMixedComparison, unittest.TestCase):
2229
2230 theclass = time
2231
2232 def test_basic_attributes(self):
2233 t = self.theclass(12, 0)
2234 self.assertEqual(t.hour, 12)
2235 self.assertEqual(t.minute, 0)
2236 self.assertEqual(t.second, 0)
2237 self.assertEqual(t.microsecond, 0)
2238
2239 def test_basic_attributes_nonzero(self):
2240 # Make sure all attributes are non-zero so bugs in
2241 # bit-shifting access show up.
2242 t = self.theclass(12, 59, 59, 8000)
2243 self.assertEqual(t.hour, 12)
2244 self.assertEqual(t.minute, 59)
2245 self.assertEqual(t.second, 59)
2246 self.assertEqual(t.microsecond, 8000)
2247
2248 def test_roundtrip(self):
2249 t = self.theclass(1, 2, 3, 4)
2250
2251 # Verify t -> string -> time identity.
2252 s = repr(t)
2253 self.assertTrue(s.startswith('datetime.'))
2254 s = s[9:]
2255 t2 = eval(s)
2256 self.assertEqual(t, t2)
2257
2258 # Verify identity via reconstructing from pieces.
2259 t2 = self.theclass(t.hour, t.minute, t.second,
2260 t.microsecond)
2261 self.assertEqual(t, t2)
2262
2263 def test_comparing(self):
2264 args = [1, 2, 3, 4]
2265 t1 = self.theclass(*args)
2266 t2 = self.theclass(*args)
2267 self.assertEqual(t1, t2)
2268 self.assertTrue(t1 <= t2)
2269 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002270 self.assertFalse(t1 != t2)
2271 self.assertFalse(t1 < t2)
2272 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002273
2274 for i in range(len(args)):
2275 newargs = args[:]
2276 newargs[i] = args[i] + 1
2277 t2 = self.theclass(*newargs) # this is larger than t1
2278 self.assertTrue(t1 < t2)
2279 self.assertTrue(t2 > t1)
2280 self.assertTrue(t1 <= t2)
2281 self.assertTrue(t2 >= t1)
2282 self.assertTrue(t1 != t2)
2283 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002284 self.assertFalse(t1 == t2)
2285 self.assertFalse(t2 == t1)
2286 self.assertFalse(t1 > t2)
2287 self.assertFalse(t2 < t1)
2288 self.assertFalse(t1 >= t2)
2289 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002290
2291 for badarg in OTHERSTUFF:
2292 self.assertEqual(t1 == badarg, False)
2293 self.assertEqual(t1 != badarg, True)
2294 self.assertEqual(badarg == t1, False)
2295 self.assertEqual(badarg != t1, True)
2296
2297 self.assertRaises(TypeError, lambda: t1 <= badarg)
2298 self.assertRaises(TypeError, lambda: t1 < badarg)
2299 self.assertRaises(TypeError, lambda: t1 > badarg)
2300 self.assertRaises(TypeError, lambda: t1 >= badarg)
2301 self.assertRaises(TypeError, lambda: badarg <= t1)
2302 self.assertRaises(TypeError, lambda: badarg < t1)
2303 self.assertRaises(TypeError, lambda: badarg > t1)
2304 self.assertRaises(TypeError, lambda: badarg >= t1)
2305
2306 def test_bad_constructor_arguments(self):
2307 # bad hours
2308 self.theclass(0, 0) # no exception
2309 self.theclass(23, 0) # no exception
2310 self.assertRaises(ValueError, self.theclass, -1, 0)
2311 self.assertRaises(ValueError, self.theclass, 24, 0)
2312 # bad minutes
2313 self.theclass(23, 0) # no exception
2314 self.theclass(23, 59) # no exception
2315 self.assertRaises(ValueError, self.theclass, 23, -1)
2316 self.assertRaises(ValueError, self.theclass, 23, 60)
2317 # bad seconds
2318 self.theclass(23, 59, 0) # no exception
2319 self.theclass(23, 59, 59) # no exception
2320 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
2321 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
2322 # bad microseconds
2323 self.theclass(23, 59, 59, 0) # no exception
2324 self.theclass(23, 59, 59, 999999) # no exception
2325 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
2326 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
2327
2328 def test_hash_equality(self):
2329 d = self.theclass(23, 30, 17)
2330 e = self.theclass(23, 30, 17)
2331 self.assertEqual(d, e)
2332 self.assertEqual(hash(d), hash(e))
2333
2334 dic = {d: 1}
2335 dic[e] = 2
2336 self.assertEqual(len(dic), 1)
2337 self.assertEqual(dic[d], 2)
2338 self.assertEqual(dic[e], 2)
2339
2340 d = self.theclass(0, 5, 17)
2341 e = self.theclass(0, 5, 17)
2342 self.assertEqual(d, e)
2343 self.assertEqual(hash(d), hash(e))
2344
2345 dic = {d: 1}
2346 dic[e] = 2
2347 self.assertEqual(len(dic), 1)
2348 self.assertEqual(dic[d], 2)
2349 self.assertEqual(dic[e], 2)
2350
2351 def test_isoformat(self):
2352 t = self.theclass(4, 5, 1, 123)
2353 self.assertEqual(t.isoformat(), "04:05:01.000123")
2354 self.assertEqual(t.isoformat(), str(t))
2355
2356 t = self.theclass()
2357 self.assertEqual(t.isoformat(), "00:00:00")
2358 self.assertEqual(t.isoformat(), str(t))
2359
2360 t = self.theclass(microsecond=1)
2361 self.assertEqual(t.isoformat(), "00:00:00.000001")
2362 self.assertEqual(t.isoformat(), str(t))
2363
2364 t = self.theclass(microsecond=10)
2365 self.assertEqual(t.isoformat(), "00:00:00.000010")
2366 self.assertEqual(t.isoformat(), str(t))
2367
2368 t = self.theclass(microsecond=100)
2369 self.assertEqual(t.isoformat(), "00:00:00.000100")
2370 self.assertEqual(t.isoformat(), str(t))
2371
2372 t = self.theclass(microsecond=1000)
2373 self.assertEqual(t.isoformat(), "00:00:00.001000")
2374 self.assertEqual(t.isoformat(), str(t))
2375
2376 t = self.theclass(microsecond=10000)
2377 self.assertEqual(t.isoformat(), "00:00:00.010000")
2378 self.assertEqual(t.isoformat(), str(t))
2379
2380 t = self.theclass(microsecond=100000)
2381 self.assertEqual(t.isoformat(), "00:00:00.100000")
2382 self.assertEqual(t.isoformat(), str(t))
2383
Alexander Belopolskya2998a62016-03-06 14:58:43 -05002384 t = self.theclass(hour=12, minute=34, second=56, microsecond=123456)
2385 self.assertEqual(t.isoformat(timespec='hours'), "12")
2386 self.assertEqual(t.isoformat(timespec='minutes'), "12:34")
2387 self.assertEqual(t.isoformat(timespec='seconds'), "12:34:56")
2388 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.123")
2389 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.123456")
2390 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56.123456")
2391 self.assertRaises(ValueError, t.isoformat, timespec='monkey')
2392
2393 t = self.theclass(hour=12, minute=34, second=56, microsecond=999500)
2394 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.999")
2395
2396 t = self.theclass(hour=12, minute=34, second=56, microsecond=0)
2397 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.000")
2398 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.000000")
2399 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56")
2400
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002401 def test_1653736(self):
2402 # verify it doesn't accept extra keyword arguments
2403 t = self.theclass(second=1)
2404 self.assertRaises(TypeError, t.isoformat, foo=3)
2405
2406 def test_strftime(self):
2407 t = self.theclass(1, 2, 3, 4)
2408 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
2409 # A naive object replaces %z and %Z with empty strings.
2410 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
2411
2412 def test_format(self):
2413 t = self.theclass(1, 2, 3, 4)
2414 self.assertEqual(t.__format__(''), str(t))
2415
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02002416 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002417 t.__format__(123)
2418
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002419 # check that a derived class's __str__() gets called
2420 class A(self.theclass):
2421 def __str__(self):
2422 return 'A'
2423 a = A(1, 2, 3, 4)
2424 self.assertEqual(a.__format__(''), 'A')
2425
2426 # check that a derived class's strftime gets called
2427 class B(self.theclass):
2428 def strftime(self, format_spec):
2429 return 'B'
2430 b = B(1, 2, 3, 4)
2431 self.assertEqual(b.__format__(''), str(t))
2432
2433 for fmt in ['%H %M %S',
2434 ]:
2435 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
2436 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
2437 self.assertEqual(b.__format__(fmt), 'B')
2438
2439 def test_str(self):
2440 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
2441 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
2442 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
2443 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
2444 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
2445
2446 def test_repr(self):
2447 name = 'datetime.' + self.theclass.__name__
2448 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
2449 "%s(1, 2, 3, 4)" % name)
2450 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
2451 "%s(10, 2, 3, 4000)" % name)
2452 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
2453 "%s(0, 2, 3, 400000)" % name)
2454 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
2455 "%s(12, 2, 3)" % name)
2456 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
2457 "%s(23, 15)" % name)
2458
2459 def test_resolution_info(self):
2460 self.assertIsInstance(self.theclass.min, self.theclass)
2461 self.assertIsInstance(self.theclass.max, self.theclass)
2462 self.assertIsInstance(self.theclass.resolution, timedelta)
2463 self.assertTrue(self.theclass.max > self.theclass.min)
2464
2465 def test_pickling(self):
2466 args = 20, 59, 16, 64**2
2467 orig = self.theclass(*args)
2468 for pickler, unpickler, proto in pickle_choices:
2469 green = pickler.dumps(orig, proto)
2470 derived = unpickler.loads(green)
2471 self.assertEqual(orig, derived)
2472
2473 def test_pickling_subclass_time(self):
2474 args = 20, 59, 16, 64**2
2475 orig = SubclassTime(*args)
2476 for pickler, unpickler, proto in pickle_choices:
2477 green = pickler.dumps(orig, proto)
2478 derived = unpickler.loads(green)
2479 self.assertEqual(orig, derived)
2480
2481 def test_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002482 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002483 cls = self.theclass
2484 self.assertTrue(cls(1))
2485 self.assertTrue(cls(0, 1))
2486 self.assertTrue(cls(0, 0, 1))
2487 self.assertTrue(cls(0, 0, 0, 1))
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002488 self.assertTrue(cls(0))
2489 self.assertTrue(cls())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002490
2491 def test_replace(self):
2492 cls = self.theclass
2493 args = [1, 2, 3, 4]
2494 base = cls(*args)
2495 self.assertEqual(base, base.replace())
2496
2497 i = 0
2498 for name, newval in (("hour", 5),
2499 ("minute", 6),
2500 ("second", 7),
2501 ("microsecond", 8)):
2502 newargs = args[:]
2503 newargs[i] = newval
2504 expected = cls(*newargs)
2505 got = base.replace(**{name: newval})
2506 self.assertEqual(expected, got)
2507 i += 1
2508
2509 # Out of bounds.
2510 base = cls(1)
2511 self.assertRaises(ValueError, base.replace, hour=24)
2512 self.assertRaises(ValueError, base.replace, minute=-1)
2513 self.assertRaises(ValueError, base.replace, second=100)
2514 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2515
2516 def test_subclass_time(self):
2517
2518 class C(self.theclass):
2519 theAnswer = 42
2520
2521 def __new__(cls, *args, **kws):
2522 temp = kws.copy()
2523 extra = temp.pop('extra')
2524 result = self.theclass.__new__(cls, *args, **temp)
2525 result.extra = extra
2526 return result
2527
2528 def newmeth(self, start):
2529 return start + self.hour + self.second
2530
2531 args = 4, 5, 6
2532
2533 dt1 = self.theclass(*args)
2534 dt2 = C(*args, **{'extra': 7})
2535
2536 self.assertEqual(dt2.__class__, C)
2537 self.assertEqual(dt2.theAnswer, 42)
2538 self.assertEqual(dt2.extra, 7)
2539 self.assertEqual(dt1.isoformat(), dt2.isoformat())
2540 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2541
2542 def test_backdoor_resistance(self):
2543 # see TestDate.test_backdoor_resistance().
2544 base = '2:59.0'
2545 for hour_byte in ' ', '9', chr(24), '\xff':
2546 self.assertRaises(TypeError, self.theclass,
2547 hour_byte + base[1:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002548 # Good bytes, but bad tzinfo:
2549 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
2550 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002551
2552# A mixin for classes with a tzinfo= argument. Subclasses must define
Martin Panter46f50722016-05-26 05:35:26 +00002553# theclass as a class attribute, and theclass(1, 1, 1, tzinfo=whatever)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002554# must be legit (which is true for time and datetime).
2555class TZInfoBase:
2556
2557 def test_argument_passing(self):
2558 cls = self.theclass
2559 # A datetime passes itself on, a time passes None.
2560 class introspective(tzinfo):
2561 def tzname(self, dt): return dt and "real" or "none"
2562 def utcoffset(self, dt):
2563 return timedelta(minutes = dt and 42 or -42)
2564 dst = utcoffset
2565
2566 obj = cls(1, 2, 3, tzinfo=introspective())
2567
2568 expected = cls is time and "none" or "real"
2569 self.assertEqual(obj.tzname(), expected)
2570
2571 expected = timedelta(minutes=(cls is time and -42 or 42))
2572 self.assertEqual(obj.utcoffset(), expected)
2573 self.assertEqual(obj.dst(), expected)
2574
2575 def test_bad_tzinfo_classes(self):
2576 cls = self.theclass
2577 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
2578
2579 class NiceTry(object):
2580 def __init__(self): pass
2581 def utcoffset(self, dt): pass
2582 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
2583
2584 class BetterTry(tzinfo):
2585 def __init__(self): pass
2586 def utcoffset(self, dt): pass
2587 b = BetterTry()
2588 t = cls(1, 1, 1, tzinfo=b)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002589 self.assertIs(t.tzinfo, b)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002590
2591 def test_utc_offset_out_of_bounds(self):
2592 class Edgy(tzinfo):
2593 def __init__(self, offset):
2594 self.offset = timedelta(minutes=offset)
2595 def utcoffset(self, dt):
2596 return self.offset
2597
2598 cls = self.theclass
2599 for offset, legit in ((-1440, False),
2600 (-1439, True),
2601 (1439, True),
2602 (1440, False)):
2603 if cls is time:
2604 t = cls(1, 2, 3, tzinfo=Edgy(offset))
2605 elif cls is datetime:
2606 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
2607 else:
2608 assert 0, "impossible"
2609 if legit:
2610 aofs = abs(offset)
2611 h, m = divmod(aofs, 60)
2612 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
2613 if isinstance(t, datetime):
2614 t = t.timetz()
2615 self.assertEqual(str(t), "01:02:03" + tag)
2616 else:
2617 self.assertRaises(ValueError, str, t)
2618
2619 def test_tzinfo_classes(self):
2620 cls = self.theclass
2621 class C1(tzinfo):
2622 def utcoffset(self, dt): return None
2623 def dst(self, dt): return None
2624 def tzname(self, dt): return None
2625 for t in (cls(1, 1, 1),
2626 cls(1, 1, 1, tzinfo=None),
2627 cls(1, 1, 1, tzinfo=C1())):
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002628 self.assertIsNone(t.utcoffset())
2629 self.assertIsNone(t.dst())
2630 self.assertIsNone(t.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002631
2632 class C3(tzinfo):
2633 def utcoffset(self, dt): return timedelta(minutes=-1439)
2634 def dst(self, dt): return timedelta(minutes=1439)
2635 def tzname(self, dt): return "aname"
2636 t = cls(1, 1, 1, tzinfo=C3())
2637 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
2638 self.assertEqual(t.dst(), timedelta(minutes=1439))
2639 self.assertEqual(t.tzname(), "aname")
2640
2641 # Wrong types.
2642 class C4(tzinfo):
2643 def utcoffset(self, dt): return "aname"
2644 def dst(self, dt): return 7
2645 def tzname(self, dt): return 0
2646 t = cls(1, 1, 1, tzinfo=C4())
2647 self.assertRaises(TypeError, t.utcoffset)
2648 self.assertRaises(TypeError, t.dst)
2649 self.assertRaises(TypeError, t.tzname)
2650
2651 # Offset out of range.
2652 class C6(tzinfo):
2653 def utcoffset(self, dt): return timedelta(hours=-24)
2654 def dst(self, dt): return timedelta(hours=24)
2655 t = cls(1, 1, 1, tzinfo=C6())
2656 self.assertRaises(ValueError, t.utcoffset)
2657 self.assertRaises(ValueError, t.dst)
2658
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002659 # Not a whole number of seconds.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002660 class C7(tzinfo):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002661 def utcoffset(self, dt): return timedelta(microseconds=61)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002662 def dst(self, dt): return timedelta(microseconds=-81)
2663 t = cls(1, 1, 1, tzinfo=C7())
2664 self.assertRaises(ValueError, t.utcoffset)
2665 self.assertRaises(ValueError, t.dst)
2666
2667 def test_aware_compare(self):
2668 cls = self.theclass
2669
2670 # Ensure that utcoffset() gets ignored if the comparands have
2671 # the same tzinfo member.
2672 class OperandDependentOffset(tzinfo):
2673 def utcoffset(self, t):
2674 if t.minute < 10:
2675 # d0 and d1 equal after adjustment
2676 return timedelta(minutes=t.minute)
2677 else:
2678 # d2 off in the weeds
2679 return timedelta(minutes=59)
2680
2681 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
2682 d0 = base.replace(minute=3)
2683 d1 = base.replace(minute=9)
2684 d2 = base.replace(minute=11)
2685 for x in d0, d1, d2:
2686 for y in d0, d1, d2:
2687 for op in lt, le, gt, ge, eq, ne:
2688 got = op(x, y)
2689 expected = op(x.minute, y.minute)
2690 self.assertEqual(got, expected)
2691
2692 # However, if they're different members, uctoffset is not ignored.
2693 # Note that a time can't actually have an operand-depedent offset,
2694 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
2695 # so skip this test for time.
2696 if cls is not time:
2697 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2698 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2699 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2700 for x in d0, d1, d2:
2701 for y in d0, d1, d2:
2702 got = (x > y) - (x < y)
2703 if (x is d0 or x is d1) and (y is d0 or y is d1):
2704 expected = 0
2705 elif x is y is d2:
2706 expected = 0
2707 elif x is d2:
2708 expected = -1
2709 else:
2710 assert y is d2
2711 expected = 1
2712 self.assertEqual(got, expected)
2713
2714
2715# Testing time objects with a non-None tzinfo.
2716class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
2717 theclass = time
2718
2719 def test_empty(self):
2720 t = self.theclass()
2721 self.assertEqual(t.hour, 0)
2722 self.assertEqual(t.minute, 0)
2723 self.assertEqual(t.second, 0)
2724 self.assertEqual(t.microsecond, 0)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002725 self.assertIsNone(t.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002726
2727 def test_zones(self):
2728 est = FixedOffset(-300, "EST", 1)
2729 utc = FixedOffset(0, "UTC", -2)
2730 met = FixedOffset(60, "MET", 3)
2731 t1 = time( 7, 47, tzinfo=est)
2732 t2 = time(12, 47, tzinfo=utc)
2733 t3 = time(13, 47, tzinfo=met)
2734 t4 = time(microsecond=40)
2735 t5 = time(microsecond=40, tzinfo=utc)
2736
2737 self.assertEqual(t1.tzinfo, est)
2738 self.assertEqual(t2.tzinfo, utc)
2739 self.assertEqual(t3.tzinfo, met)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002740 self.assertIsNone(t4.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002741 self.assertEqual(t5.tzinfo, utc)
2742
2743 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2744 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2745 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002746 self.assertIsNone(t4.utcoffset())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002747 self.assertRaises(TypeError, t1.utcoffset, "no args")
2748
2749 self.assertEqual(t1.tzname(), "EST")
2750 self.assertEqual(t2.tzname(), "UTC")
2751 self.assertEqual(t3.tzname(), "MET")
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002752 self.assertIsNone(t4.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002753 self.assertRaises(TypeError, t1.tzname, "no args")
2754
2755 self.assertEqual(t1.dst(), timedelta(minutes=1))
2756 self.assertEqual(t2.dst(), timedelta(minutes=-2))
2757 self.assertEqual(t3.dst(), timedelta(minutes=3))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002758 self.assertIsNone(t4.dst())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002759 self.assertRaises(TypeError, t1.dst, "no args")
2760
2761 self.assertEqual(hash(t1), hash(t2))
2762 self.assertEqual(hash(t1), hash(t3))
2763 self.assertEqual(hash(t2), hash(t3))
2764
2765 self.assertEqual(t1, t2)
2766 self.assertEqual(t1, t3)
2767 self.assertEqual(t2, t3)
Alexander Belopolsky08313822012-06-15 20:19:47 -04002768 self.assertNotEqual(t4, t5) # mixed tz-aware & naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002769 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
2770 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
2771
2772 self.assertEqual(str(t1), "07:47:00-05:00")
2773 self.assertEqual(str(t2), "12:47:00+00:00")
2774 self.assertEqual(str(t3), "13:47:00+01:00")
2775 self.assertEqual(str(t4), "00:00:00.000040")
2776 self.assertEqual(str(t5), "00:00:00.000040+00:00")
2777
2778 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
2779 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
2780 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
2781 self.assertEqual(t4.isoformat(), "00:00:00.000040")
2782 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
2783
2784 d = 'datetime.time'
2785 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
2786 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
2787 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
2788 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
2789 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2790
2791 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2792 "07:47:00 %Z=EST %z=-0500")
2793 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2794 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2795
2796 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
2797 t1 = time(23, 59, tzinfo=yuck)
2798 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2799 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2800
2801 # Check that an invalid tzname result raises an exception.
2802 class Badtzname(tzinfo):
Alexander Belopolskye239d232010-12-08 23:31:48 +00002803 tz = 42
2804 def tzname(self, dt): return self.tz
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002805 t = time(2, 3, 4, tzinfo=Badtzname())
2806 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2807 self.assertRaises(TypeError, t.strftime, "%Z")
2808
Alexander Belopolskye239d232010-12-08 23:31:48 +00002809 # Issue #6697:
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002810 if '_Fast' in str(self):
Alexander Belopolskye239d232010-12-08 23:31:48 +00002811 Badtzname.tz = '\ud800'
2812 self.assertRaises(ValueError, t.strftime, "%Z")
2813
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002814 def test_hash_edge_cases(self):
2815 # Offsets that overflow a basic time.
2816 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2817 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2818 self.assertEqual(hash(t1), hash(t2))
2819
2820 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2821 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2822 self.assertEqual(hash(t1), hash(t2))
2823
2824 def test_pickling(self):
2825 # Try one without a tzinfo.
2826 args = 20, 59, 16, 64**2
2827 orig = self.theclass(*args)
2828 for pickler, unpickler, proto in pickle_choices:
2829 green = pickler.dumps(orig, proto)
2830 derived = unpickler.loads(green)
2831 self.assertEqual(orig, derived)
2832
2833 # Try one with a tzinfo.
2834 tinfo = PicklableFixedOffset(-300, 'cookie')
2835 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
2836 for pickler, unpickler, proto in pickle_choices:
2837 green = pickler.dumps(orig, proto)
2838 derived = unpickler.loads(green)
2839 self.assertEqual(orig, derived)
2840 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
2841 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2842 self.assertEqual(derived.tzname(), 'cookie')
2843
2844 def test_more_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002845 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002846 cls = self.theclass
2847
2848 t = cls(0, tzinfo=FixedOffset(-300, ""))
2849 self.assertTrue(t)
2850
2851 t = cls(5, tzinfo=FixedOffset(-300, ""))
2852 self.assertTrue(t)
2853
2854 t = cls(5, tzinfo=FixedOffset(300, ""))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002855 self.assertTrue(t)
2856
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002857 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2858 self.assertTrue(t)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002859
2860 def test_replace(self):
2861 cls = self.theclass
2862 z100 = FixedOffset(100, "+100")
2863 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2864 args = [1, 2, 3, 4, z100]
2865 base = cls(*args)
2866 self.assertEqual(base, base.replace())
2867
2868 i = 0
2869 for name, newval in (("hour", 5),
2870 ("minute", 6),
2871 ("second", 7),
2872 ("microsecond", 8),
2873 ("tzinfo", zm200)):
2874 newargs = args[:]
2875 newargs[i] = newval
2876 expected = cls(*newargs)
2877 got = base.replace(**{name: newval})
2878 self.assertEqual(expected, got)
2879 i += 1
2880
2881 # Ensure we can get rid of a tzinfo.
2882 self.assertEqual(base.tzname(), "+100")
2883 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002884 self.assertIsNone(base2.tzinfo)
2885 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002886
2887 # Ensure we can add one.
2888 base3 = base2.replace(tzinfo=z100)
2889 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002890 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002891
2892 # Out of bounds.
2893 base = cls(1)
2894 self.assertRaises(ValueError, base.replace, hour=24)
2895 self.assertRaises(ValueError, base.replace, minute=-1)
2896 self.assertRaises(ValueError, base.replace, second=100)
2897 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2898
2899 def test_mixed_compare(self):
2900 t1 = time(1, 2, 3)
2901 t2 = time(1, 2, 3)
2902 self.assertEqual(t1, t2)
2903 t2 = t2.replace(tzinfo=None)
2904 self.assertEqual(t1, t2)
2905 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2906 self.assertEqual(t1, t2)
2907 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04002908 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002909
2910 # In time w/ identical tzinfo objects, utcoffset is ignored.
2911 class Varies(tzinfo):
2912 def __init__(self):
2913 self.offset = timedelta(minutes=22)
2914 def utcoffset(self, t):
2915 self.offset += timedelta(minutes=1)
2916 return self.offset
2917
2918 v = Varies()
2919 t1 = t2.replace(tzinfo=v)
2920 t2 = t2.replace(tzinfo=v)
2921 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2922 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2923 self.assertEqual(t1, t2)
2924
2925 # But if they're not identical, it isn't ignored.
2926 t2 = t2.replace(tzinfo=Varies())
2927 self.assertTrue(t1 < t2) # t1's offset counter still going up
2928
2929 def test_subclass_timetz(self):
2930
2931 class C(self.theclass):
2932 theAnswer = 42
2933
2934 def __new__(cls, *args, **kws):
2935 temp = kws.copy()
2936 extra = temp.pop('extra')
2937 result = self.theclass.__new__(cls, *args, **temp)
2938 result.extra = extra
2939 return result
2940
2941 def newmeth(self, start):
2942 return start + self.hour + self.second
2943
2944 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2945
2946 dt1 = self.theclass(*args)
2947 dt2 = C(*args, **{'extra': 7})
2948
2949 self.assertEqual(dt2.__class__, C)
2950 self.assertEqual(dt2.theAnswer, 42)
2951 self.assertEqual(dt2.extra, 7)
2952 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2953 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2954
2955
2956# Testing datetime objects with a non-None tzinfo.
2957
2958class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
2959 theclass = datetime
2960
2961 def test_trivial(self):
2962 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2963 self.assertEqual(dt.year, 1)
2964 self.assertEqual(dt.month, 2)
2965 self.assertEqual(dt.day, 3)
2966 self.assertEqual(dt.hour, 4)
2967 self.assertEqual(dt.minute, 5)
2968 self.assertEqual(dt.second, 6)
2969 self.assertEqual(dt.microsecond, 7)
2970 self.assertEqual(dt.tzinfo, None)
2971
2972 def test_even_more_compare(self):
2973 # The test_compare() and test_more_compare() inherited from TestDate
2974 # and TestDateTime covered non-tzinfo cases.
2975
2976 # Smallest possible after UTC adjustment.
2977 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2978 # Largest possible after UTC adjustment.
2979 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2980 tzinfo=FixedOffset(-1439, ""))
2981
2982 # Make sure those compare correctly, and w/o overflow.
2983 self.assertTrue(t1 < t2)
2984 self.assertTrue(t1 != t2)
2985 self.assertTrue(t2 > t1)
2986
2987 self.assertEqual(t1, t1)
2988 self.assertEqual(t2, t2)
2989
2990 # Equal afer adjustment.
2991 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2992 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2993 self.assertEqual(t1, t2)
2994
2995 # Change t1 not to subtract a minute, and t1 should be larger.
2996 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2997 self.assertTrue(t1 > t2)
2998
2999 # Change t1 to subtract 2 minutes, and t1 should be smaller.
3000 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
3001 self.assertTrue(t1 < t2)
3002
3003 # Back to the original t1, but make seconds resolve it.
3004 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3005 second=1)
3006 self.assertTrue(t1 > t2)
3007
3008 # Likewise, but make microseconds resolve it.
3009 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3010 microsecond=1)
3011 self.assertTrue(t1 > t2)
3012
Alexander Belopolsky08313822012-06-15 20:19:47 -04003013 # Make t2 naive and it should differ.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003014 t2 = self.theclass.min
Alexander Belopolsky08313822012-06-15 20:19:47 -04003015 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003016 self.assertEqual(t2, t2)
3017
3018 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
3019 class Naive(tzinfo):
3020 def utcoffset(self, dt): return None
3021 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
Alexander Belopolsky08313822012-06-15 20:19:47 -04003022 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003023 self.assertEqual(t2, t2)
3024
3025 # OTOH, it's OK to compare two of these mixing the two ways of being
3026 # naive.
3027 t1 = self.theclass(5, 6, 7)
3028 self.assertEqual(t1, t2)
3029
3030 # Try a bogus uctoffset.
3031 class Bogus(tzinfo):
3032 def utcoffset(self, dt):
3033 return timedelta(minutes=1440) # out of bounds
3034 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
3035 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
3036 self.assertRaises(ValueError, lambda: t1 == t2)
3037
3038 def test_pickling(self):
3039 # Try one without a tzinfo.
3040 args = 6, 7, 23, 20, 59, 1, 64**2
3041 orig = self.theclass(*args)
3042 for pickler, unpickler, proto in pickle_choices:
3043 green = pickler.dumps(orig, proto)
3044 derived = unpickler.loads(green)
3045 self.assertEqual(orig, derived)
3046
3047 # Try one with a tzinfo.
3048 tinfo = PicklableFixedOffset(-300, 'cookie')
3049 orig = self.theclass(*args, **{'tzinfo': tinfo})
3050 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
3051 for pickler, unpickler, proto in pickle_choices:
3052 green = pickler.dumps(orig, proto)
3053 derived = unpickler.loads(green)
3054 self.assertEqual(orig, derived)
3055 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3056 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3057 self.assertEqual(derived.tzname(), 'cookie')
3058
3059 def test_extreme_hashes(self):
3060 # If an attempt is made to hash these via subtracting the offset
3061 # then hashing a datetime object, OverflowError results. The
3062 # Python implementation used to blow up here.
3063 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3064 hash(t)
3065 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3066 tzinfo=FixedOffset(-1439, ""))
3067 hash(t)
3068
3069 # OTOH, an OOB offset should blow up.
3070 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
3071 self.assertRaises(ValueError, hash, t)
3072
3073 def test_zones(self):
3074 est = FixedOffset(-300, "EST")
3075 utc = FixedOffset(0, "UTC")
3076 met = FixedOffset(60, "MET")
3077 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
3078 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
3079 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
3080 self.assertEqual(t1.tzinfo, est)
3081 self.assertEqual(t2.tzinfo, utc)
3082 self.assertEqual(t3.tzinfo, met)
3083 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
3084 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
3085 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
3086 self.assertEqual(t1.tzname(), "EST")
3087 self.assertEqual(t2.tzname(), "UTC")
3088 self.assertEqual(t3.tzname(), "MET")
3089 self.assertEqual(hash(t1), hash(t2))
3090 self.assertEqual(hash(t1), hash(t3))
3091 self.assertEqual(hash(t2), hash(t3))
3092 self.assertEqual(t1, t2)
3093 self.assertEqual(t1, t3)
3094 self.assertEqual(t2, t3)
3095 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
3096 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
3097 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
3098 d = 'datetime.datetime(2002, 3, 19, '
3099 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
3100 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
3101 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
3102
3103 def test_combine(self):
3104 met = FixedOffset(60, "MET")
3105 d = date(2002, 3, 4)
3106 tz = time(18, 45, 3, 1234, tzinfo=met)
3107 dt = datetime.combine(d, tz)
3108 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
3109 tzinfo=met))
3110
3111 def test_extract(self):
3112 met = FixedOffset(60, "MET")
3113 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
3114 self.assertEqual(dt.date(), date(2002, 3, 4))
3115 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
3116 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
3117
3118 def test_tz_aware_arithmetic(self):
3119 import random
3120
3121 now = self.theclass.now()
3122 tz55 = FixedOffset(-330, "west 5:30")
3123 timeaware = now.time().replace(tzinfo=tz55)
3124 nowaware = self.theclass.combine(now.date(), timeaware)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003125 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003126 self.assertEqual(nowaware.timetz(), timeaware)
3127
3128 # Can't mix aware and non-aware.
3129 self.assertRaises(TypeError, lambda: now - nowaware)
3130 self.assertRaises(TypeError, lambda: nowaware - now)
3131
3132 # And adding datetime's doesn't make sense, aware or not.
3133 self.assertRaises(TypeError, lambda: now + nowaware)
3134 self.assertRaises(TypeError, lambda: nowaware + now)
3135 self.assertRaises(TypeError, lambda: nowaware + nowaware)
3136
3137 # Subtracting should yield 0.
3138 self.assertEqual(now - now, timedelta(0))
3139 self.assertEqual(nowaware - nowaware, timedelta(0))
3140
3141 # Adding a delta should preserve tzinfo.
3142 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
3143 nowawareplus = nowaware + delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003144 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003145 nowawareplus2 = delta + nowaware
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003146 self.assertIs(nowawareplus2.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003147 self.assertEqual(nowawareplus, nowawareplus2)
3148
3149 # that - delta should be what we started with, and that - what we
3150 # started with should be delta.
3151 diff = nowawareplus - delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003152 self.assertIs(diff.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003153 self.assertEqual(nowaware, diff)
3154 self.assertRaises(TypeError, lambda: delta - nowawareplus)
3155 self.assertEqual(nowawareplus - nowaware, delta)
3156
3157 # Make up a random timezone.
3158 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
3159 # Attach it to nowawareplus.
3160 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003161 self.assertIs(nowawareplus.tzinfo, tzr)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003162 # Make sure the difference takes the timezone adjustments into account.
3163 got = nowaware - nowawareplus
3164 # Expected: (nowaware base - nowaware offset) -
3165 # (nowawareplus base - nowawareplus offset) =
3166 # (nowaware base - nowawareplus base) +
3167 # (nowawareplus offset - nowaware offset) =
3168 # -delta + nowawareplus offset - nowaware offset
3169 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
3170 self.assertEqual(got, expected)
3171
3172 # Try max possible difference.
3173 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
3174 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3175 tzinfo=FixedOffset(-1439, "max"))
3176 maxdiff = max - min
3177 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
3178 timedelta(minutes=2*1439))
3179 # Different tzinfo, but the same offset
3180 tza = timezone(HOUR, 'A')
3181 tzb = timezone(HOUR, 'B')
3182 delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
3183 self.assertEqual(delta, self.theclass.min - self.theclass.max)
3184
3185 def test_tzinfo_now(self):
3186 meth = self.theclass.now
3187 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3188 base = meth()
3189 # Try with and without naming the keyword.
3190 off42 = FixedOffset(42, "42")
3191 another = meth(off42)
3192 again = meth(tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003193 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003194 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3195 # Bad argument with and w/o naming the keyword.
3196 self.assertRaises(TypeError, meth, 16)
3197 self.assertRaises(TypeError, meth, tzinfo=16)
3198 # Bad keyword name.
3199 self.assertRaises(TypeError, meth, tinfo=off42)
3200 # Too many args.
3201 self.assertRaises(TypeError, meth, off42, off42)
3202
3203 # We don't know which time zone we're in, and don't have a tzinfo
3204 # class to represent it, so seeing whether a tz argument actually
3205 # does a conversion is tricky.
3206 utc = FixedOffset(0, "utc", 0)
3207 for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
3208 timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
3209 for dummy in range(3):
3210 now = datetime.now(weirdtz)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003211 self.assertIs(now.tzinfo, weirdtz)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003212 utcnow = datetime.utcnow().replace(tzinfo=utc)
3213 now2 = utcnow.astimezone(weirdtz)
3214 if abs(now - now2) < timedelta(seconds=30):
3215 break
3216 # Else the code is broken, or more than 30 seconds passed between
3217 # calls; assuming the latter, just try again.
3218 else:
3219 # Three strikes and we're out.
3220 self.fail("utcnow(), now(tz), or astimezone() may be broken")
3221
3222 def test_tzinfo_fromtimestamp(self):
3223 import time
3224 meth = self.theclass.fromtimestamp
3225 ts = time.time()
3226 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3227 base = meth(ts)
3228 # Try with and without naming the keyword.
3229 off42 = FixedOffset(42, "42")
3230 another = meth(ts, off42)
3231 again = meth(ts, tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003232 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003233 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3234 # Bad argument with and w/o naming the keyword.
3235 self.assertRaises(TypeError, meth, ts, 16)
3236 self.assertRaises(TypeError, meth, ts, tzinfo=16)
3237 # Bad keyword name.
3238 self.assertRaises(TypeError, meth, ts, tinfo=off42)
3239 # Too many args.
3240 self.assertRaises(TypeError, meth, ts, off42, off42)
3241 # Too few args.
3242 self.assertRaises(TypeError, meth)
3243
3244 # Try to make sure tz= actually does some conversion.
3245 timestamp = 1000000000
3246 utcdatetime = datetime.utcfromtimestamp(timestamp)
3247 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
3248 # But on some flavor of Mac, it's nowhere near that. So we can't have
3249 # any idea here what time that actually is, we can only test that
3250 # relative changes match.
3251 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
3252 tz = FixedOffset(utcoffset, "tz", 0)
3253 expected = utcdatetime + utcoffset
3254 got = datetime.fromtimestamp(timestamp, tz)
3255 self.assertEqual(expected, got.replace(tzinfo=None))
3256
3257 def test_tzinfo_utcnow(self):
3258 meth = self.theclass.utcnow
3259 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3260 base = meth()
3261 # Try with and without naming the keyword; for whatever reason,
3262 # utcnow() doesn't accept a tzinfo argument.
3263 off42 = FixedOffset(42, "42")
3264 self.assertRaises(TypeError, meth, off42)
3265 self.assertRaises(TypeError, meth, tzinfo=off42)
3266
3267 def test_tzinfo_utcfromtimestamp(self):
3268 import time
3269 meth = self.theclass.utcfromtimestamp
3270 ts = time.time()
3271 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3272 base = meth(ts)
3273 # Try with and without naming the keyword; for whatever reason,
3274 # utcfromtimestamp() doesn't accept a tzinfo argument.
3275 off42 = FixedOffset(42, "42")
3276 self.assertRaises(TypeError, meth, ts, off42)
3277 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
3278
3279 def test_tzinfo_timetuple(self):
3280 # TestDateTime tested most of this. datetime adds a twist to the
3281 # DST flag.
3282 class DST(tzinfo):
3283 def __init__(self, dstvalue):
3284 if isinstance(dstvalue, int):
3285 dstvalue = timedelta(minutes=dstvalue)
3286 self.dstvalue = dstvalue
3287 def dst(self, dt):
3288 return self.dstvalue
3289
3290 cls = self.theclass
3291 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
3292 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
3293 t = d.timetuple()
3294 self.assertEqual(1, t.tm_year)
3295 self.assertEqual(1, t.tm_mon)
3296 self.assertEqual(1, t.tm_mday)
3297 self.assertEqual(10, t.tm_hour)
3298 self.assertEqual(20, t.tm_min)
3299 self.assertEqual(30, t.tm_sec)
3300 self.assertEqual(0, t.tm_wday)
3301 self.assertEqual(1, t.tm_yday)
3302 self.assertEqual(flag, t.tm_isdst)
3303
3304 # dst() returns wrong type.
3305 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
3306
3307 # dst() at the edge.
3308 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
3309 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
3310
3311 # dst() out of range.
3312 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
3313 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
3314
3315 def test_utctimetuple(self):
3316 class DST(tzinfo):
3317 def __init__(self, dstvalue=0):
3318 if isinstance(dstvalue, int):
3319 dstvalue = timedelta(minutes=dstvalue)
3320 self.dstvalue = dstvalue
3321 def dst(self, dt):
3322 return self.dstvalue
3323
3324 cls = self.theclass
3325 # This can't work: DST didn't implement utcoffset.
3326 self.assertRaises(NotImplementedError,
3327 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
3328
3329 class UOFS(DST):
3330 def __init__(self, uofs, dofs=None):
3331 DST.__init__(self, dofs)
3332 self.uofs = timedelta(minutes=uofs)
3333 def utcoffset(self, dt):
3334 return self.uofs
3335
3336 for dstvalue in -33, 33, 0, None:
3337 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
3338 t = d.utctimetuple()
3339 self.assertEqual(d.year, t.tm_year)
3340 self.assertEqual(d.month, t.tm_mon)
3341 self.assertEqual(d.day, t.tm_mday)
3342 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
3343 self.assertEqual(13, t.tm_min)
3344 self.assertEqual(d.second, t.tm_sec)
3345 self.assertEqual(d.weekday(), t.tm_wday)
3346 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
3347 t.tm_yday)
3348 # Ensure tm_isdst is 0 regardless of what dst() says: DST
3349 # is never in effect for a UTC time.
3350 self.assertEqual(0, t.tm_isdst)
3351
3352 # For naive datetime, utctimetuple == timetuple except for isdst
3353 d = cls(1, 2, 3, 10, 20, 30, 40)
3354 t = d.utctimetuple()
3355 self.assertEqual(t[:-1], d.timetuple()[:-1])
3356 self.assertEqual(0, t.tm_isdst)
3357 # Same if utcoffset is None
3358 class NOFS(DST):
3359 def utcoffset(self, dt):
3360 return None
3361 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
3362 t = d.utctimetuple()
3363 self.assertEqual(t[:-1], d.timetuple()[:-1])
3364 self.assertEqual(0, t.tm_isdst)
3365 # Check that bad tzinfo is detected
3366 class BOFS(DST):
3367 def utcoffset(self, dt):
3368 return "EST"
3369 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
3370 self.assertRaises(TypeError, d.utctimetuple)
3371
3372 # Check that utctimetuple() is the same as
3373 # astimezone(utc).timetuple()
3374 d = cls(2010, 11, 13, 14, 15, 16, 171819)
3375 for tz in [timezone.min, timezone.utc, timezone.max]:
3376 dtz = d.replace(tzinfo=tz)
3377 self.assertEqual(dtz.utctimetuple()[:-1],
3378 dtz.astimezone(timezone.utc).timetuple()[:-1])
3379 # At the edges, UTC adjustment can produce years out-of-range
3380 # for a datetime object. Ensure that an OverflowError is
3381 # raised.
3382 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
3383 # That goes back 1 minute less than a full day.
3384 self.assertRaises(OverflowError, tiny.utctimetuple)
3385
3386 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
3387 # That goes forward 1 minute less than a full day.
3388 self.assertRaises(OverflowError, huge.utctimetuple)
3389 # More overflow cases
3390 tiny = cls.min.replace(tzinfo=timezone(MINUTE))
3391 self.assertRaises(OverflowError, tiny.utctimetuple)
3392 huge = cls.max.replace(tzinfo=timezone(-MINUTE))
3393 self.assertRaises(OverflowError, huge.utctimetuple)
3394
3395 def test_tzinfo_isoformat(self):
3396 zero = FixedOffset(0, "+00:00")
3397 plus = FixedOffset(220, "+03:40")
3398 minus = FixedOffset(-231, "-03:51")
3399 unknown = FixedOffset(None, "")
3400
3401 cls = self.theclass
3402 datestr = '0001-02-03'
3403 for ofs in None, zero, plus, minus, unknown:
3404 for us in 0, 987001:
3405 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
3406 timestr = '04:05:59' + (us and '.987001' or '')
3407 ofsstr = ofs is not None and d.tzname() or ''
3408 tailstr = timestr + ofsstr
3409 iso = d.isoformat()
3410 self.assertEqual(iso, datestr + 'T' + tailstr)
3411 self.assertEqual(iso, d.isoformat('T'))
3412 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
3413 self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
3414 self.assertEqual(str(d), datestr + ' ' + tailstr)
3415
3416 def test_replace(self):
3417 cls = self.theclass
3418 z100 = FixedOffset(100, "+100")
3419 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3420 args = [1, 2, 3, 4, 5, 6, 7, z100]
3421 base = cls(*args)
3422 self.assertEqual(base, base.replace())
3423
3424 i = 0
3425 for name, newval in (("year", 2),
3426 ("month", 3),
3427 ("day", 4),
3428 ("hour", 5),
3429 ("minute", 6),
3430 ("second", 7),
3431 ("microsecond", 8),
3432 ("tzinfo", zm200)):
3433 newargs = args[:]
3434 newargs[i] = newval
3435 expected = cls(*newargs)
3436 got = base.replace(**{name: newval})
3437 self.assertEqual(expected, got)
3438 i += 1
3439
3440 # Ensure we can get rid of a tzinfo.
3441 self.assertEqual(base.tzname(), "+100")
3442 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003443 self.assertIsNone(base2.tzinfo)
3444 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003445
3446 # Ensure we can add one.
3447 base3 = base2.replace(tzinfo=z100)
3448 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003449 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003450
3451 # Out of bounds.
3452 base = cls(2000, 2, 29)
3453 self.assertRaises(ValueError, base.replace, year=2001)
3454
3455 def test_more_astimezone(self):
3456 # The inherited test_astimezone covered some trivial and error cases.
3457 fnone = FixedOffset(None, "None")
3458 f44m = FixedOffset(44, "44")
3459 fm5h = FixedOffset(-timedelta(hours=5), "m300")
3460
3461 dt = self.theclass.now(tz=f44m)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003462 self.assertIs(dt.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003463 # Replacing with degenerate tzinfo raises an exception.
3464 self.assertRaises(ValueError, dt.astimezone, fnone)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003465 # Replacing with same tzinfo makes no change.
3466 x = dt.astimezone(dt.tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003467 self.assertIs(x.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003468 self.assertEqual(x.date(), dt.date())
3469 self.assertEqual(x.time(), dt.time())
3470
3471 # Replacing with different tzinfo does adjust.
3472 got = dt.astimezone(fm5h)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003473 self.assertIs(got.tzinfo, fm5h)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003474 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
3475 expected = dt - dt.utcoffset() # in effect, convert to UTC
3476 expected += fm5h.utcoffset(dt) # and from there to local time
3477 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
3478 self.assertEqual(got.date(), expected.date())
3479 self.assertEqual(got.time(), expected.time())
3480 self.assertEqual(got.timetz(), expected.timetz())
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003481 self.assertIs(got.tzinfo, expected.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003482 self.assertEqual(got, expected)
3483
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003484 @support.run_with_tz('UTC')
3485 def test_astimezone_default_utc(self):
3486 dt = self.theclass.now(timezone.utc)
3487 self.assertEqual(dt.astimezone(None), dt)
3488 self.assertEqual(dt.astimezone(), dt)
3489
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003490 # Note that offset in TZ variable has the opposite sign to that
3491 # produced by %z directive.
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003492 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
3493 def test_astimezone_default_eastern(self):
3494 dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
3495 local = dt.astimezone()
3496 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003497 self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003498 dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
3499 local = dt.astimezone()
3500 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003501 self.assertEqual(local.strftime("%z %Z"), "-0400 EDT")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003502
Alexander Belopolsky1dcf4f92016-03-25 15:42:59 -04003503 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
3504 def test_astimezone_default_near_fold(self):
3505 # Issue #26616.
3506 u = datetime(2015, 11, 1, 5, tzinfo=timezone.utc)
3507 t = u.astimezone()
3508 s = t.astimezone()
3509 self.assertEqual(t.tzinfo, s.tzinfo)
3510
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003511 def test_aware_subtract(self):
3512 cls = self.theclass
3513
3514 # Ensure that utcoffset() is ignored when the operands have the
3515 # same tzinfo member.
3516 class OperandDependentOffset(tzinfo):
3517 def utcoffset(self, t):
3518 if t.minute < 10:
3519 # d0 and d1 equal after adjustment
3520 return timedelta(minutes=t.minute)
3521 else:
3522 # d2 off in the weeds
3523 return timedelta(minutes=59)
3524
3525 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
3526 d0 = base.replace(minute=3)
3527 d1 = base.replace(minute=9)
3528 d2 = base.replace(minute=11)
3529 for x in d0, d1, d2:
3530 for y in d0, d1, d2:
3531 got = x - y
3532 expected = timedelta(minutes=x.minute - y.minute)
3533 self.assertEqual(got, expected)
3534
3535 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
3536 # ignored.
3537 base = cls(8, 9, 10, 11, 12, 13, 14)
3538 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3539 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3540 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3541 for x in d0, d1, d2:
3542 for y in d0, d1, d2:
3543 got = x - y
3544 if (x is d0 or x is d1) and (y is d0 or y is d1):
3545 expected = timedelta(0)
3546 elif x is y is d2:
3547 expected = timedelta(0)
3548 elif x is d2:
3549 expected = timedelta(minutes=(11-59)-0)
3550 else:
3551 assert y is d2
3552 expected = timedelta(minutes=0-(11-59))
3553 self.assertEqual(got, expected)
3554
3555 def test_mixed_compare(self):
3556 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
3557 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
3558 self.assertEqual(t1, t2)
3559 t2 = t2.replace(tzinfo=None)
3560 self.assertEqual(t1, t2)
3561 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3562 self.assertEqual(t1, t2)
3563 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04003564 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003565
3566 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
3567 class Varies(tzinfo):
3568 def __init__(self):
3569 self.offset = timedelta(minutes=22)
3570 def utcoffset(self, t):
3571 self.offset += timedelta(minutes=1)
3572 return self.offset
3573
3574 v = Varies()
3575 t1 = t2.replace(tzinfo=v)
3576 t2 = t2.replace(tzinfo=v)
3577 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3578 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3579 self.assertEqual(t1, t2)
3580
3581 # But if they're not identical, it isn't ignored.
3582 t2 = t2.replace(tzinfo=Varies())
3583 self.assertTrue(t1 < t2) # t1's offset counter still going up
3584
3585 def test_subclass_datetimetz(self):
3586
3587 class C(self.theclass):
3588 theAnswer = 42
3589
3590 def __new__(cls, *args, **kws):
3591 temp = kws.copy()
3592 extra = temp.pop('extra')
3593 result = self.theclass.__new__(cls, *args, **temp)
3594 result.extra = extra
3595 return result
3596
3597 def newmeth(self, start):
3598 return start + self.hour + self.year
3599
3600 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3601
3602 dt1 = self.theclass(*args)
3603 dt2 = C(*args, **{'extra': 7})
3604
3605 self.assertEqual(dt2.__class__, C)
3606 self.assertEqual(dt2.theAnswer, 42)
3607 self.assertEqual(dt2.extra, 7)
3608 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3609 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
3610
3611# Pain to set up DST-aware tzinfo classes.
3612
3613def first_sunday_on_or_after(dt):
3614 days_to_go = 6 - dt.weekday()
3615 if days_to_go:
3616 dt += timedelta(days_to_go)
3617 return dt
3618
3619ZERO = timedelta(0)
3620MINUTE = timedelta(minutes=1)
3621HOUR = timedelta(hours=1)
3622DAY = timedelta(days=1)
3623# In the US, DST starts at 2am (standard time) on the first Sunday in April.
3624DSTSTART = datetime(1, 4, 1, 2)
3625# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
3626# which is the first Sunday on or after Oct 25. Because we view 1:MM as
3627# being standard time on that day, there is no spelling in local time of
3628# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
3629DSTEND = datetime(1, 10, 25, 1)
3630
3631class USTimeZone(tzinfo):
3632
3633 def __init__(self, hours, reprname, stdname, dstname):
3634 self.stdoffset = timedelta(hours=hours)
3635 self.reprname = reprname
3636 self.stdname = stdname
3637 self.dstname = dstname
3638
3639 def __repr__(self):
3640 return self.reprname
3641
3642 def tzname(self, dt):
3643 if self.dst(dt):
3644 return self.dstname
3645 else:
3646 return self.stdname
3647
3648 def utcoffset(self, dt):
3649 return self.stdoffset + self.dst(dt)
3650
3651 def dst(self, dt):
3652 if dt is None or dt.tzinfo is None:
3653 # An exception instead may be sensible here, in one or more of
3654 # the cases.
3655 return ZERO
3656 assert dt.tzinfo is self
3657
3658 # Find first Sunday in April.
3659 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
3660 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
3661
3662 # Find last Sunday in October.
3663 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
3664 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
3665
3666 # Can't compare naive to aware objects, so strip the timezone from
3667 # dt first.
3668 if start <= dt.replace(tzinfo=None) < end:
3669 return HOUR
3670 else:
3671 return ZERO
3672
3673Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
3674Central = USTimeZone(-6, "Central", "CST", "CDT")
3675Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
3676Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
3677utc_real = FixedOffset(0, "UTC", 0)
3678# For better test coverage, we want another flavor of UTC that's west of
3679# the Eastern and Pacific timezones.
3680utc_fake = FixedOffset(-12*60, "UTCfake", 0)
3681
3682class TestTimezoneConversions(unittest.TestCase):
3683 # The DST switch times for 2002, in std time.
3684 dston = datetime(2002, 4, 7, 2)
3685 dstoff = datetime(2002, 10, 27, 1)
3686
3687 theclass = datetime
3688
3689 # Check a time that's inside DST.
3690 def checkinside(self, dt, tz, utc, dston, dstoff):
3691 self.assertEqual(dt.dst(), HOUR)
3692
3693 # Conversion to our own timezone is always an identity.
3694 self.assertEqual(dt.astimezone(tz), dt)
3695
3696 asutc = dt.astimezone(utc)
3697 there_and_back = asutc.astimezone(tz)
3698
3699 # Conversion to UTC and back isn't always an identity here,
3700 # because there are redundant spellings (in local time) of
3701 # UTC time when DST begins: the clock jumps from 1:59:59
3702 # to 3:00:00, and a local time of 2:MM:SS doesn't really
3703 # make sense then. The classes above treat 2:MM:SS as
3704 # daylight time then (it's "after 2am"), really an alias
3705 # for 1:MM:SS standard time. The latter form is what
3706 # conversion back from UTC produces.
3707 if dt.date() == dston.date() and dt.hour == 2:
3708 # We're in the redundant hour, and coming back from
3709 # UTC gives the 1:MM:SS standard-time spelling.
3710 self.assertEqual(there_and_back + HOUR, dt)
3711 # Although during was considered to be in daylight
3712 # time, there_and_back is not.
3713 self.assertEqual(there_and_back.dst(), ZERO)
3714 # They're the same times in UTC.
3715 self.assertEqual(there_and_back.astimezone(utc),
3716 dt.astimezone(utc))
3717 else:
3718 # We're not in the redundant hour.
3719 self.assertEqual(dt, there_and_back)
3720
3721 # Because we have a redundant spelling when DST begins, there is
Ezio Melotti3b3499b2011-03-16 11:35:38 +02003722 # (unfortunately) an hour when DST ends that can't be spelled at all in
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003723 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
3724 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
3725 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
3726 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
3727 # expressed in local time. Nevertheless, we want conversion back
3728 # from UTC to mimic the local clock's "repeat an hour" behavior.
3729 nexthour_utc = asutc + HOUR
3730 nexthour_tz = nexthour_utc.astimezone(tz)
3731 if dt.date() == dstoff.date() and dt.hour == 0:
3732 # We're in the hour before the last DST hour. The last DST hour
3733 # is ineffable. We want the conversion back to repeat 1:MM.
3734 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3735 nexthour_utc += HOUR
3736 nexthour_tz = nexthour_utc.astimezone(tz)
3737 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3738 else:
3739 self.assertEqual(nexthour_tz - dt, HOUR)
3740
3741 # Check a time that's outside DST.
3742 def checkoutside(self, dt, tz, utc):
3743 self.assertEqual(dt.dst(), ZERO)
3744
3745 # Conversion to our own timezone is always an identity.
3746 self.assertEqual(dt.astimezone(tz), dt)
3747
3748 # Converting to UTC and back is an identity too.
3749 asutc = dt.astimezone(utc)
3750 there_and_back = asutc.astimezone(tz)
3751 self.assertEqual(dt, there_and_back)
3752
3753 def convert_between_tz_and_utc(self, tz, utc):
3754 dston = self.dston.replace(tzinfo=tz)
3755 # Because 1:MM on the day DST ends is taken as being standard time,
3756 # there is no spelling in tz for the last hour of daylight time.
3757 # For purposes of the test, the last hour of DST is 0:MM, which is
3758 # taken as being daylight time (and 1:MM is taken as being standard
3759 # time).
3760 dstoff = self.dstoff.replace(tzinfo=tz)
3761 for delta in (timedelta(weeks=13),
3762 DAY,
3763 HOUR,
3764 timedelta(minutes=1),
3765 timedelta(microseconds=1)):
3766
3767 self.checkinside(dston, tz, utc, dston, dstoff)
3768 for during in dston + delta, dstoff - delta:
3769 self.checkinside(during, tz, utc, dston, dstoff)
3770
3771 self.checkoutside(dstoff, tz, utc)
3772 for outside in dston - delta, dstoff + delta:
3773 self.checkoutside(outside, tz, utc)
3774
3775 def test_easy(self):
3776 # Despite the name of this test, the endcases are excruciating.
3777 self.convert_between_tz_and_utc(Eastern, utc_real)
3778 self.convert_between_tz_and_utc(Pacific, utc_real)
3779 self.convert_between_tz_and_utc(Eastern, utc_fake)
3780 self.convert_between_tz_and_utc(Pacific, utc_fake)
3781 # The next is really dancing near the edge. It works because
3782 # Pacific and Eastern are far enough apart that their "problem
3783 # hours" don't overlap.
3784 self.convert_between_tz_and_utc(Eastern, Pacific)
3785 self.convert_between_tz_and_utc(Pacific, Eastern)
3786 # OTOH, these fail! Don't enable them. The difficulty is that
3787 # the edge case tests assume that every hour is representable in
3788 # the "utc" class. This is always true for a fixed-offset tzinfo
3789 # class (lke utc_real and utc_fake), but not for Eastern or Central.
3790 # For these adjacent DST-aware time zones, the range of time offsets
3791 # tested ends up creating hours in the one that aren't representable
3792 # in the other. For the same reason, we would see failures in the
3793 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
3794 # offset deltas in convert_between_tz_and_utc().
3795 #
3796 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
3797 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
3798
3799 def test_tricky(self):
3800 # 22:00 on day before daylight starts.
3801 fourback = self.dston - timedelta(hours=4)
3802 ninewest = FixedOffset(-9*60, "-0900", 0)
3803 fourback = fourback.replace(tzinfo=ninewest)
3804 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
3805 # 2", we should get the 3 spelling.
3806 # If we plug 22:00 the day before into Eastern, it "looks like std
3807 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
3808 # to 22:00 lands on 2:00, which makes no sense in local time (the
3809 # local clock jumps from 1 to 3). The point here is to make sure we
3810 # get the 3 spelling.
3811 expected = self.dston.replace(hour=3)
3812 got = fourback.astimezone(Eastern).replace(tzinfo=None)
3813 self.assertEqual(expected, got)
3814
3815 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
3816 # case we want the 1:00 spelling.
3817 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
3818 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
3819 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
3820 # spelling.
3821 expected = self.dston.replace(hour=1)
3822 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
3823 self.assertEqual(expected, got)
3824
3825 # Now on the day DST ends, we want "repeat an hour" behavior.
3826 # UTC 4:MM 5:MM 6:MM 7:MM checking these
3827 # EST 23:MM 0:MM 1:MM 2:MM
3828 # EDT 0:MM 1:MM 2:MM 3:MM
3829 # wall 0:MM 1:MM 1:MM 2:MM against these
3830 for utc in utc_real, utc_fake:
3831 for tz in Eastern, Pacific:
3832 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
3833 # Convert that to UTC.
3834 first_std_hour -= tz.utcoffset(None)
3835 # Adjust for possibly fake UTC.
3836 asutc = first_std_hour + utc.utcoffset(None)
3837 # First UTC hour to convert; this is 4:00 when utc=utc_real &
3838 # tz=Eastern.
3839 asutcbase = asutc.replace(tzinfo=utc)
3840 for tzhour in (0, 1, 1, 2):
3841 expectedbase = self.dstoff.replace(hour=tzhour)
3842 for minute in 0, 30, 59:
3843 expected = expectedbase.replace(minute=minute)
3844 asutc = asutcbase.replace(minute=minute)
3845 astz = asutc.astimezone(tz)
3846 self.assertEqual(astz.replace(tzinfo=None), expected)
3847 asutcbase += HOUR
3848
3849
3850 def test_bogus_dst(self):
3851 class ok(tzinfo):
3852 def utcoffset(self, dt): return HOUR
3853 def dst(self, dt): return HOUR
3854
3855 now = self.theclass.now().replace(tzinfo=utc_real)
3856 # Doesn't blow up.
3857 now.astimezone(ok())
3858
3859 # Does blow up.
3860 class notok(ok):
3861 def dst(self, dt): return None
3862 self.assertRaises(ValueError, now.astimezone, notok())
3863
3864 # Sometimes blow up. In the following, tzinfo.dst()
3865 # implementation may return None or not None depending on
3866 # whether DST is assumed to be in effect. In this situation,
3867 # a ValueError should be raised by astimezone().
3868 class tricky_notok(ok):
3869 def dst(self, dt):
3870 if dt.year == 2000:
3871 return None
3872 else:
3873 return 10*HOUR
3874 dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
3875 self.assertRaises(ValueError, dt.astimezone, tricky_notok())
3876
3877 def test_fromutc(self):
3878 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
3879 now = datetime.utcnow().replace(tzinfo=utc_real)
3880 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3881 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
3882 enow = Eastern.fromutc(now) # doesn't blow up
3883 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3884 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3885 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3886
3887 # Always converts UTC to standard time.
3888 class FauxUSTimeZone(USTimeZone):
3889 def fromutc(self, dt):
3890 return dt + self.stdoffset
3891 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
3892
3893 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
3894 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
3895 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
3896
3897 # Check around DST start.
3898 start = self.dston.replace(hour=4, tzinfo=Eastern)
3899 fstart = start.replace(tzinfo=FEastern)
3900 for wall in 23, 0, 1, 3, 4, 5:
3901 expected = start.replace(hour=wall)
3902 if wall == 23:
3903 expected -= timedelta(days=1)
3904 got = Eastern.fromutc(start)
3905 self.assertEqual(expected, got)
3906
3907 expected = fstart + FEastern.stdoffset
3908 got = FEastern.fromutc(fstart)
3909 self.assertEqual(expected, got)
3910
3911 # Ensure astimezone() calls fromutc() too.
3912 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3913 self.assertEqual(expected, got)
3914
3915 start += HOUR
3916 fstart += HOUR
3917
3918 # Check around DST end.
3919 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
3920 fstart = start.replace(tzinfo=FEastern)
3921 for wall in 0, 1, 1, 2, 3, 4:
3922 expected = start.replace(hour=wall)
3923 got = Eastern.fromutc(start)
3924 self.assertEqual(expected, got)
3925
3926 expected = fstart + FEastern.stdoffset
3927 got = FEastern.fromutc(fstart)
3928 self.assertEqual(expected, got)
3929
3930 # Ensure astimezone() calls fromutc() too.
3931 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3932 self.assertEqual(expected, got)
3933
3934 start += HOUR
3935 fstart += HOUR
3936
3937
3938#############################################################################
3939# oddballs
3940
3941class Oddballs(unittest.TestCase):
3942
3943 def test_bug_1028306(self):
3944 # Trying to compare a date to a datetime should act like a mixed-
3945 # type comparison, despite that datetime is a subclass of date.
3946 as_date = date.today()
3947 as_datetime = datetime.combine(as_date, time())
3948 self.assertTrue(as_date != as_datetime)
3949 self.assertTrue(as_datetime != as_date)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003950 self.assertFalse(as_date == as_datetime)
3951 self.assertFalse(as_datetime == as_date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003952 self.assertRaises(TypeError, lambda: as_date < as_datetime)
3953 self.assertRaises(TypeError, lambda: as_datetime < as_date)
3954 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
3955 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
3956 self.assertRaises(TypeError, lambda: as_date > as_datetime)
3957 self.assertRaises(TypeError, lambda: as_datetime > as_date)
3958 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
3959 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
3960
Raymond Hettinger15f44ab2016-08-30 10:47:49 -07003961 # Nevertheless, comparison should work with the base-class (date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003962 # projection if use of a date method is forced.
3963 self.assertEqual(as_date.__eq__(as_datetime), True)
3964 different_day = (as_date.day + 1) % 20 + 1
3965 as_different = as_datetime.replace(day= different_day)
3966 self.assertEqual(as_date.__eq__(as_different), False)
3967
3968 # And date should compare with other subclasses of date. If a
3969 # subclass wants to stop this, it's up to the subclass to do so.
3970 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
3971 self.assertEqual(as_date, date_sc)
3972 self.assertEqual(date_sc, as_date)
3973
3974 # Ditto for datetimes.
3975 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
3976 as_date.day, 0, 0, 0)
3977 self.assertEqual(as_datetime, datetime_sc)
3978 self.assertEqual(datetime_sc, as_datetime)
3979
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04003980 def test_extra_attributes(self):
3981 for x in [date.today(),
3982 time(),
3983 datetime.utcnow(),
3984 timedelta(),
3985 tzinfo(),
3986 timezone(timedelta())]:
3987 with self.assertRaises(AttributeError):
3988 x.abc = 1
3989
3990 def test_check_arg_types(self):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04003991 class Number:
3992 def __init__(self, value):
3993 self.value = value
3994 def __int__(self):
3995 return self.value
3996
3997 for xx in [decimal.Decimal(10),
3998 decimal.Decimal('10.9'),
3999 Number(10)]:
4000 self.assertEqual(datetime(10, 10, 10, 10, 10, 10, 10),
4001 datetime(xx, xx, xx, xx, xx, xx, xx))
4002
4003 with self.assertRaisesRegex(TypeError, '^an integer is required '
R David Murray44b548d2016-09-08 13:59:53 -04004004 r'\(got type str\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004005 datetime(10, 10, '10')
4006
4007 f10 = Number(10.9)
4008 with self.assertRaisesRegex(TypeError, '^__int__ returned non-int '
R David Murray44b548d2016-09-08 13:59:53 -04004009 r'\(type float\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004010 datetime(10, 10, f10)
4011
4012 class Float(float):
4013 pass
4014 s10 = Float(10.9)
4015 with self.assertRaisesRegex(TypeError, '^integer argument expected, '
4016 'got float$'):
4017 datetime(10, 10, s10)
4018
4019 with self.assertRaises(TypeError):
4020 datetime(10., 10, 10)
4021 with self.assertRaises(TypeError):
4022 datetime(10, 10., 10)
4023 with self.assertRaises(TypeError):
4024 datetime(10, 10, 10.)
4025 with self.assertRaises(TypeError):
4026 datetime(10, 10, 10, 10.)
4027 with self.assertRaises(TypeError):
4028 datetime(10, 10, 10, 10, 10.)
4029 with self.assertRaises(TypeError):
4030 datetime(10, 10, 10, 10, 10, 10.)
4031 with self.assertRaises(TypeError):
4032 datetime(10, 10, 10, 10, 10, 10, 10.)
4033
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004034#############################################################################
4035# Local Time Disambiguation
4036
4037# An experimental reimplementation of fromutc that respects the "fold" flag.
4038
4039class tzinfo2(tzinfo):
4040
4041 def fromutc(self, dt):
4042 "datetime in UTC -> datetime in local time."
4043
4044 if not isinstance(dt, datetime):
4045 raise TypeError("fromutc() requires a datetime argument")
4046 if dt.tzinfo is not self:
4047 raise ValueError("dt.tzinfo is not self")
4048 # Returned value satisfies
4049 # dt + ldt.utcoffset() = ldt
4050 off0 = dt.replace(fold=0).utcoffset()
4051 off1 = dt.replace(fold=1).utcoffset()
4052 if off0 is None or off1 is None or dt.dst() is None:
4053 raise ValueError
4054 if off0 == off1:
4055 ldt = dt + off0
4056 off1 = ldt.utcoffset()
4057 if off0 == off1:
4058 return ldt
4059 # Now, we discovered both possible offsets, so
4060 # we can just try four possible solutions:
4061 for off in [off0, off1]:
4062 ldt = dt + off
4063 if ldt.utcoffset() == off:
4064 return ldt
4065 ldt = ldt.replace(fold=1)
4066 if ldt.utcoffset() == off:
4067 return ldt
4068
4069 raise ValueError("No suitable local time found")
4070
4071# Reimplementing simplified US timezones to respect the "fold" flag:
4072
4073class USTimeZone2(tzinfo2):
4074
4075 def __init__(self, hours, reprname, stdname, dstname):
4076 self.stdoffset = timedelta(hours=hours)
4077 self.reprname = reprname
4078 self.stdname = stdname
4079 self.dstname = dstname
4080
4081 def __repr__(self):
4082 return self.reprname
4083
4084 def tzname(self, dt):
4085 if self.dst(dt):
4086 return self.dstname
4087 else:
4088 return self.stdname
4089
4090 def utcoffset(self, dt):
4091 return self.stdoffset + self.dst(dt)
4092
4093 def dst(self, dt):
4094 if dt is None or dt.tzinfo is None:
4095 # An exception instead may be sensible here, in one or more of
4096 # the cases.
4097 return ZERO
4098 assert dt.tzinfo is self
4099
4100 # Find first Sunday in April.
4101 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
4102 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
4103
4104 # Find last Sunday in October.
4105 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
4106 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
4107
4108 # Can't compare naive to aware objects, so strip the timezone from
4109 # dt first.
4110 dt = dt.replace(tzinfo=None)
4111 if start + HOUR <= dt < end:
4112 # DST is in effect.
4113 return HOUR
4114 elif end <= dt < end + HOUR:
4115 # Fold (an ambiguous hour): use dt.fold to disambiguate.
4116 return ZERO if dt.fold else HOUR
4117 elif start <= dt < start + HOUR:
4118 # Gap (a non-existent hour): reverse the fold rule.
4119 return HOUR if dt.fold else ZERO
4120 else:
4121 # DST is off.
4122 return ZERO
4123
4124Eastern2 = USTimeZone2(-5, "Eastern2", "EST", "EDT")
4125Central2 = USTimeZone2(-6, "Central2", "CST", "CDT")
4126Mountain2 = USTimeZone2(-7, "Mountain2", "MST", "MDT")
4127Pacific2 = USTimeZone2(-8, "Pacific2", "PST", "PDT")
4128
4129# Europe_Vilnius_1941 tzinfo implementation reproduces the following
4130# 1941 transition from Olson's tzdist:
4131#
4132# Zone NAME GMTOFF RULES FORMAT [UNTIL]
4133# ZoneEurope/Vilnius 1:00 - CET 1940 Aug 3
4134# 3:00 - MSK 1941 Jun 24
4135# 1:00 C-Eur CE%sT 1944 Aug
4136#
4137# $ zdump -v Europe/Vilnius | grep 1941
4138# Europe/Vilnius Mon Jun 23 20:59:59 1941 UTC = Mon Jun 23 23:59:59 1941 MSK isdst=0 gmtoff=10800
4139# Europe/Vilnius Mon Jun 23 21:00:00 1941 UTC = Mon Jun 23 23:00:00 1941 CEST isdst=1 gmtoff=7200
4140
4141class Europe_Vilnius_1941(tzinfo):
4142 def _utc_fold(self):
4143 return [datetime(1941, 6, 23, 21, tzinfo=self), # Mon Jun 23 21:00:00 1941 UTC
4144 datetime(1941, 6, 23, 22, tzinfo=self)] # Mon Jun 23 22:00:00 1941 UTC
4145
4146 def _loc_fold(self):
4147 return [datetime(1941, 6, 23, 23, tzinfo=self), # Mon Jun 23 23:00:00 1941 MSK / CEST
4148 datetime(1941, 6, 24, 0, tzinfo=self)] # Mon Jun 24 00:00:00 1941 CEST
4149
4150 def utcoffset(self, dt):
4151 fold_start, fold_stop = self._loc_fold()
4152 if dt < fold_start:
4153 return 3 * HOUR
4154 if dt < fold_stop:
4155 return (2 if dt.fold else 3) * HOUR
4156 # if dt >= fold_stop
4157 return 2 * HOUR
4158
4159 def dst(self, dt):
4160 fold_start, fold_stop = self._loc_fold()
4161 if dt < fold_start:
4162 return 0 * HOUR
4163 if dt < fold_stop:
4164 return (1 if dt.fold else 0) * HOUR
4165 # if dt >= fold_stop
4166 return 1 * HOUR
4167
4168 def tzname(self, dt):
4169 fold_start, fold_stop = self._loc_fold()
4170 if dt < fold_start:
4171 return 'MSK'
4172 if dt < fold_stop:
4173 return ('MSK', 'CEST')[dt.fold]
4174 # if dt >= fold_stop
4175 return 'CEST'
4176
4177 def fromutc(self, dt):
4178 assert dt.fold == 0
4179 assert dt.tzinfo is self
4180 if dt.year != 1941:
4181 raise NotImplementedError
4182 fold_start, fold_stop = self._utc_fold()
4183 if dt < fold_start:
4184 return dt + 3 * HOUR
4185 if dt < fold_stop:
4186 return (dt + 2 * HOUR).replace(fold=1)
4187 # if dt >= fold_stop
4188 return dt + 2 * HOUR
4189
4190
4191class TestLocalTimeDisambiguation(unittest.TestCase):
4192
4193 def test_vilnius_1941_fromutc(self):
4194 Vilnius = Europe_Vilnius_1941()
4195
4196 gdt = datetime(1941, 6, 23, 20, 59, 59, tzinfo=timezone.utc)
4197 ldt = gdt.astimezone(Vilnius)
4198 self.assertEqual(ldt.strftime("%c %Z%z"),
4199 'Mon Jun 23 23:59:59 1941 MSK+0300')
4200 self.assertEqual(ldt.fold, 0)
4201 self.assertFalse(ldt.dst())
4202
4203 gdt = datetime(1941, 6, 23, 21, tzinfo=timezone.utc)
4204 ldt = gdt.astimezone(Vilnius)
4205 self.assertEqual(ldt.strftime("%c %Z%z"),
4206 'Mon Jun 23 23:00:00 1941 CEST+0200')
4207 self.assertEqual(ldt.fold, 1)
4208 self.assertTrue(ldt.dst())
4209
4210 gdt = datetime(1941, 6, 23, 22, tzinfo=timezone.utc)
4211 ldt = gdt.astimezone(Vilnius)
4212 self.assertEqual(ldt.strftime("%c %Z%z"),
4213 'Tue Jun 24 00:00:00 1941 CEST+0200')
4214 self.assertEqual(ldt.fold, 0)
4215 self.assertTrue(ldt.dst())
4216
4217 def test_vilnius_1941_toutc(self):
4218 Vilnius = Europe_Vilnius_1941()
4219
4220 ldt = datetime(1941, 6, 23, 22, 59, 59, tzinfo=Vilnius)
4221 gdt = ldt.astimezone(timezone.utc)
4222 self.assertEqual(gdt.strftime("%c %Z"),
4223 'Mon Jun 23 19:59:59 1941 UTC')
4224
4225 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius)
4226 gdt = ldt.astimezone(timezone.utc)
4227 self.assertEqual(gdt.strftime("%c %Z"),
4228 'Mon Jun 23 20:59:59 1941 UTC')
4229
4230 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius, fold=1)
4231 gdt = ldt.astimezone(timezone.utc)
4232 self.assertEqual(gdt.strftime("%c %Z"),
4233 'Mon Jun 23 21:59:59 1941 UTC')
4234
4235 ldt = datetime(1941, 6, 24, 0, tzinfo=Vilnius)
4236 gdt = ldt.astimezone(timezone.utc)
4237 self.assertEqual(gdt.strftime("%c %Z"),
4238 'Mon Jun 23 22:00:00 1941 UTC')
4239
4240
4241 def test_constructors(self):
4242 t = time(0, fold=1)
4243 dt = datetime(1, 1, 1, fold=1)
4244 self.assertEqual(t.fold, 1)
4245 self.assertEqual(dt.fold, 1)
4246 with self.assertRaises(TypeError):
4247 time(0, 0, 0, 0, None, 0)
4248
4249 def test_member(self):
4250 dt = datetime(1, 1, 1, fold=1)
4251 t = dt.time()
4252 self.assertEqual(t.fold, 1)
4253 t = dt.timetz()
4254 self.assertEqual(t.fold, 1)
4255
4256 def test_replace(self):
4257 t = time(0)
4258 dt = datetime(1, 1, 1)
4259 self.assertEqual(t.replace(fold=1).fold, 1)
4260 self.assertEqual(dt.replace(fold=1).fold, 1)
4261 self.assertEqual(t.replace(fold=0).fold, 0)
4262 self.assertEqual(dt.replace(fold=0).fold, 0)
4263 # Check that replacement of other fields does not change "fold".
4264 t = t.replace(fold=1, tzinfo=Eastern)
4265 dt = dt.replace(fold=1, tzinfo=Eastern)
4266 self.assertEqual(t.replace(tzinfo=None).fold, 1)
4267 self.assertEqual(dt.replace(tzinfo=None).fold, 1)
4268 # Check that fold is a keyword-only argument
4269 with self.assertRaises(TypeError):
4270 t.replace(1, 1, 1, None, 1)
4271 with self.assertRaises(TypeError):
4272 dt.replace(1, 1, 1, 1, 1, 1, 1, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04004273
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004274 def test_comparison(self):
4275 t = time(0)
4276 dt = datetime(1, 1, 1)
4277 self.assertEqual(t, t.replace(fold=1))
4278 self.assertEqual(dt, dt.replace(fold=1))
4279
4280 def test_hash(self):
4281 t = time(0)
4282 dt = datetime(1, 1, 1)
4283 self.assertEqual(hash(t), hash(t.replace(fold=1)))
4284 self.assertEqual(hash(dt), hash(dt.replace(fold=1)))
4285
4286 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4287 def test_fromtimestamp(self):
4288 s = 1414906200
4289 dt0 = datetime.fromtimestamp(s)
4290 dt1 = datetime.fromtimestamp(s + 3600)
4291 self.assertEqual(dt0.fold, 0)
4292 self.assertEqual(dt1.fold, 1)
4293
4294 @support.run_with_tz('Australia/Lord_Howe')
4295 def test_fromtimestamp_lord_howe(self):
4296 tm = _time.localtime(1.4e9)
4297 if _time.strftime('%Z%z', tm) != 'LHST+1030':
4298 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
4299 # $ TZ=Australia/Lord_Howe date -r 1428158700
4300 # Sun Apr 5 01:45:00 LHDT 2015
4301 # $ TZ=Australia/Lord_Howe date -r 1428160500
4302 # Sun Apr 5 01:45:00 LHST 2015
4303 s = 1428158700
4304 t0 = datetime.fromtimestamp(s)
4305 t1 = datetime.fromtimestamp(s + 1800)
4306 self.assertEqual(t0, t1)
4307 self.assertEqual(t0.fold, 0)
4308 self.assertEqual(t1.fold, 1)
4309
4310
4311 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4312 def test_timestamp(self):
4313 dt0 = datetime(2014, 11, 2, 1, 30)
4314 dt1 = dt0.replace(fold=1)
4315 self.assertEqual(dt0.timestamp() + 3600,
4316 dt1.timestamp())
4317
4318 @support.run_with_tz('Australia/Lord_Howe')
4319 def test_timestamp_lord_howe(self):
4320 tm = _time.localtime(1.4e9)
4321 if _time.strftime('%Z%z', tm) != 'LHST+1030':
4322 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
4323 t = datetime(2015, 4, 5, 1, 45)
4324 s0 = t.replace(fold=0).timestamp()
4325 s1 = t.replace(fold=1).timestamp()
4326 self.assertEqual(s0 + 1800, s1)
4327
4328
4329 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4330 def test_astimezone(self):
4331 dt0 = datetime(2014, 11, 2, 1, 30)
4332 dt1 = dt0.replace(fold=1)
4333 # Convert both naive instances to aware.
4334 adt0 = dt0.astimezone()
4335 adt1 = dt1.astimezone()
4336 # Check that the first instance in DST zone and the second in STD
4337 self.assertEqual(adt0.tzname(), 'EDT')
4338 self.assertEqual(adt1.tzname(), 'EST')
4339 self.assertEqual(adt0 + HOUR, adt1)
4340 # Aware instances with fixed offset tzinfo's always have fold=0
4341 self.assertEqual(adt0.fold, 0)
4342 self.assertEqual(adt1.fold, 0)
4343
4344
4345 def test_pickle_fold(self):
4346 t = time(fold=1)
4347 dt = datetime(1, 1, 1, fold=1)
4348 for pickler, unpickler, proto in pickle_choices:
4349 for x in [t, dt]:
4350 s = pickler.dumps(x, proto)
4351 y = unpickler.loads(s)
4352 self.assertEqual(x, y)
4353 self.assertEqual((0 if proto < 4 else x.fold), y.fold)
4354
4355 def test_repr(self):
4356 t = time(fold=1)
4357 dt = datetime(1, 1, 1, fold=1)
4358 self.assertEqual(repr(t), 'datetime.time(0, 0, fold=1)')
4359 self.assertEqual(repr(dt),
4360 'datetime.datetime(1, 1, 1, 0, 0, fold=1)')
4361
4362 def test_dst(self):
4363 # Let's first establish that things work in regular times.
4364 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
4365 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
4366 self.assertEqual(dt_summer.dst(), HOUR)
4367 self.assertEqual(dt_winter.dst(), ZERO)
4368 # The disambiguation flag is ignored
4369 self.assertEqual(dt_summer.replace(fold=1).dst(), HOUR)
4370 self.assertEqual(dt_winter.replace(fold=1).dst(), ZERO)
4371
4372 # Pick local time in the fold.
4373 for minute in [0, 30, 59]:
4374 dt = datetime(2002, 10, 27, 1, minute, tzinfo=Eastern2)
4375 # With fold=0 (the default) it is in DST.
4376 self.assertEqual(dt.dst(), HOUR)
4377 # With fold=1 it is in STD.
4378 self.assertEqual(dt.replace(fold=1).dst(), ZERO)
4379
4380 # Pick local time in the gap.
4381 for minute in [0, 30, 59]:
4382 dt = datetime(2002, 4, 7, 2, minute, tzinfo=Eastern2)
4383 # With fold=0 (the default) it is in STD.
4384 self.assertEqual(dt.dst(), ZERO)
4385 # With fold=1 it is in DST.
4386 self.assertEqual(dt.replace(fold=1).dst(), HOUR)
4387
4388
4389 def test_utcoffset(self):
4390 # Let's first establish that things work in regular times.
4391 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
4392 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
4393 self.assertEqual(dt_summer.utcoffset(), -4 * HOUR)
4394 self.assertEqual(dt_winter.utcoffset(), -5 * HOUR)
4395 # The disambiguation flag is ignored
4396 self.assertEqual(dt_summer.replace(fold=1).utcoffset(), -4 * HOUR)
4397 self.assertEqual(dt_winter.replace(fold=1).utcoffset(), -5 * HOUR)
4398
4399 def test_fromutc(self):
4400 # Let's first establish that things work in regular times.
4401 u_summer = datetime(2002, 10, 27, 6, tzinfo=Eastern2) - timedelta.resolution
4402 u_winter = datetime(2002, 10, 27, 7, tzinfo=Eastern2)
4403 t_summer = Eastern2.fromutc(u_summer)
4404 t_winter = Eastern2.fromutc(u_winter)
4405 self.assertEqual(t_summer, u_summer - 4 * HOUR)
4406 self.assertEqual(t_winter, u_winter - 5 * HOUR)
4407 self.assertEqual(t_summer.fold, 0)
4408 self.assertEqual(t_winter.fold, 0)
4409
4410 # What happens in the fall-back fold?
4411 u = datetime(2002, 10, 27, 5, 30, tzinfo=Eastern2)
4412 t0 = Eastern2.fromutc(u)
4413 u += HOUR
4414 t1 = Eastern2.fromutc(u)
4415 self.assertEqual(t0, t1)
4416 self.assertEqual(t0.fold, 0)
4417 self.assertEqual(t1.fold, 1)
4418 # The tricky part is when u is in the local fold:
4419 u = datetime(2002, 10, 27, 1, 30, tzinfo=Eastern2)
4420 t = Eastern2.fromutc(u)
4421 self.assertEqual((t.day, t.hour), (26, 21))
4422 # .. or gets into the local fold after a standard time adjustment
4423 u = datetime(2002, 10, 27, 6, 30, tzinfo=Eastern2)
4424 t = Eastern2.fromutc(u)
4425 self.assertEqual((t.day, t.hour), (27, 1))
4426
4427 # What happens in the spring-forward gap?
4428 u = datetime(2002, 4, 7, 2, 0, tzinfo=Eastern2)
4429 t = Eastern2.fromutc(u)
4430 self.assertEqual((t.day, t.hour), (6, 21))
4431
4432 def test_mixed_compare_regular(self):
4433 t = datetime(2000, 1, 1, tzinfo=Eastern2)
4434 self.assertEqual(t, t.astimezone(timezone.utc))
4435 t = datetime(2000, 6, 1, tzinfo=Eastern2)
4436 self.assertEqual(t, t.astimezone(timezone.utc))
4437
4438 def test_mixed_compare_fold(self):
4439 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
4440 t_fold_utc = t_fold.astimezone(timezone.utc)
4441 self.assertNotEqual(t_fold, t_fold_utc)
4442
4443 def test_mixed_compare_gap(self):
4444 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
4445 t_gap_utc = t_gap.astimezone(timezone.utc)
4446 self.assertNotEqual(t_gap, t_gap_utc)
4447
4448 def test_hash_aware(self):
4449 t = datetime(2000, 1, 1, tzinfo=Eastern2)
4450 self.assertEqual(hash(t), hash(t.replace(fold=1)))
4451 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
4452 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
4453 self.assertEqual(hash(t_fold), hash(t_fold.replace(fold=1)))
4454 self.assertEqual(hash(t_gap), hash(t_gap.replace(fold=1)))
4455
4456SEC = timedelta(0, 1)
4457
4458def pairs(iterable):
4459 a, b = itertools.tee(iterable)
4460 next(b, None)
4461 return zip(a, b)
4462
4463class ZoneInfo(tzinfo):
4464 zoneroot = '/usr/share/zoneinfo'
4465 def __init__(self, ut, ti):
4466 """
4467
4468 :param ut: array
4469 Array of transition point timestamps
4470 :param ti: list
4471 A list of (offset, isdst, abbr) tuples
4472 :return: None
4473 """
4474 self.ut = ut
4475 self.ti = ti
4476 self.lt = self.invert(ut, ti)
4477
4478 @staticmethod
4479 def invert(ut, ti):
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04004480 lt = (array('q', ut), array('q', ut))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004481 if ut:
4482 offset = ti[0][0] // SEC
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04004483 lt[0][0] += offset
4484 lt[1][0] += offset
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004485 for i in range(1, len(ut)):
4486 lt[0][i] += ti[i-1][0] // SEC
4487 lt[1][i] += ti[i][0] // SEC
4488 return lt
4489
4490 @classmethod
4491 def fromfile(cls, fileobj):
4492 if fileobj.read(4).decode() != "TZif":
4493 raise ValueError("not a zoneinfo file")
4494 fileobj.seek(32)
4495 counts = array('i')
4496 counts.fromfile(fileobj, 3)
4497 if sys.byteorder != 'big':
4498 counts.byteswap()
4499
4500 ut = array('i')
4501 ut.fromfile(fileobj, counts[0])
4502 if sys.byteorder != 'big':
4503 ut.byteswap()
4504
4505 type_indices = array('B')
4506 type_indices.fromfile(fileobj, counts[0])
4507
4508 ttis = []
4509 for i in range(counts[1]):
4510 ttis.append(struct.unpack(">lbb", fileobj.read(6)))
4511
4512 abbrs = fileobj.read(counts[2])
4513
4514 # Convert ttis
4515 for i, (gmtoff, isdst, abbrind) in enumerate(ttis):
4516 abbr = abbrs[abbrind:abbrs.find(0, abbrind)].decode()
4517 ttis[i] = (timedelta(0, gmtoff), isdst, abbr)
4518
4519 ti = [None] * len(ut)
4520 for i, idx in enumerate(type_indices):
4521 ti[i] = ttis[idx]
4522
4523 self = cls(ut, ti)
4524
4525 return self
4526
4527 @classmethod
4528 def fromname(cls, name):
4529 path = os.path.join(cls.zoneroot, name)
4530 with open(path, 'rb') as f:
4531 return cls.fromfile(f)
4532
4533 EPOCHORDINAL = date(1970, 1, 1).toordinal()
4534
4535 def fromutc(self, dt):
4536 """datetime in UTC -> datetime in local time."""
4537
4538 if not isinstance(dt, datetime):
4539 raise TypeError("fromutc() requires a datetime argument")
4540 if dt.tzinfo is not self:
4541 raise ValueError("dt.tzinfo is not self")
4542
4543 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
4544 + dt.hour * 3600
4545 + dt.minute * 60
4546 + dt.second)
4547
4548 if timestamp < self.ut[1]:
4549 tti = self.ti[0]
4550 fold = 0
4551 else:
4552 idx = bisect.bisect_right(self.ut, timestamp)
4553 assert self.ut[idx-1] <= timestamp
4554 assert idx == len(self.ut) or timestamp < self.ut[idx]
4555 tti_prev, tti = self.ti[idx-2:idx]
4556 # Detect fold
4557 shift = tti_prev[0] - tti[0]
4558 fold = (shift > timedelta(0, timestamp - self.ut[idx-1]))
4559 dt += tti[0]
4560 if fold:
4561 return dt.replace(fold=1)
4562 else:
4563 return dt
4564
4565 def _find_ti(self, dt, i):
4566 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
4567 + dt.hour * 3600
4568 + dt.minute * 60
4569 + dt.second)
4570 lt = self.lt[dt.fold]
4571 idx = bisect.bisect_right(lt, timestamp)
4572
4573 return self.ti[max(0, idx - 1)][i]
4574
4575 def utcoffset(self, dt):
4576 return self._find_ti(dt, 0)
4577
4578 def dst(self, dt):
4579 isdst = self._find_ti(dt, 1)
4580 # XXX: We cannot accurately determine the "save" value,
4581 # so let's return 1h whenever DST is in effect. Since
4582 # we don't use dst() in fromutc(), it is unlikely that
4583 # it will be needed for anything more than bool(dst()).
4584 return ZERO if isdst else HOUR
4585
4586 def tzname(self, dt):
4587 return self._find_ti(dt, 2)
4588
4589 @classmethod
4590 def zonenames(cls, zonedir=None):
4591 if zonedir is None:
4592 zonedir = cls.zoneroot
Alexander Belopolsky1b8f26c2016-08-11 11:01:45 -04004593 zone_tab = os.path.join(zonedir, 'zone.tab')
4594 try:
4595 f = open(zone_tab)
4596 except OSError:
4597 return
4598 with f:
4599 for line in f:
4600 line = line.strip()
4601 if line and not line.startswith('#'):
4602 yield line.split()[2]
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004603
4604 @classmethod
4605 def stats(cls, start_year=1):
4606 count = gap_count = fold_count = zeros_count = 0
4607 min_gap = min_fold = timedelta.max
4608 max_gap = max_fold = ZERO
4609 min_gap_datetime = max_gap_datetime = datetime.min
4610 min_gap_zone = max_gap_zone = None
4611 min_fold_datetime = max_fold_datetime = datetime.min
4612 min_fold_zone = max_fold_zone = None
4613 stats_since = datetime(start_year, 1, 1) # Starting from 1970 eliminates a lot of noise
4614 for zonename in cls.zonenames():
4615 count += 1
4616 tz = cls.fromname(zonename)
4617 for dt, shift in tz.transitions():
4618 if dt < stats_since:
4619 continue
4620 if shift > ZERO:
4621 gap_count += 1
4622 if (shift, dt) > (max_gap, max_gap_datetime):
4623 max_gap = shift
4624 max_gap_zone = zonename
4625 max_gap_datetime = dt
4626 if (shift, datetime.max - dt) < (min_gap, datetime.max - min_gap_datetime):
4627 min_gap = shift
4628 min_gap_zone = zonename
4629 min_gap_datetime = dt
4630 elif shift < ZERO:
4631 fold_count += 1
4632 shift = -shift
4633 if (shift, dt) > (max_fold, max_fold_datetime):
4634 max_fold = shift
4635 max_fold_zone = zonename
4636 max_fold_datetime = dt
4637 if (shift, datetime.max - dt) < (min_fold, datetime.max - min_fold_datetime):
4638 min_fold = shift
4639 min_fold_zone = zonename
4640 min_fold_datetime = dt
4641 else:
4642 zeros_count += 1
4643 trans_counts = (gap_count, fold_count, zeros_count)
4644 print("Number of zones: %5d" % count)
4645 print("Number of transitions: %5d = %d (gaps) + %d (folds) + %d (zeros)" %
4646 ((sum(trans_counts),) + trans_counts))
4647 print("Min gap: %16s at %s in %s" % (min_gap, min_gap_datetime, min_gap_zone))
4648 print("Max gap: %16s at %s in %s" % (max_gap, max_gap_datetime, max_gap_zone))
4649 print("Min fold: %16s at %s in %s" % (min_fold, min_fold_datetime, min_fold_zone))
4650 print("Max fold: %16s at %s in %s" % (max_fold, max_fold_datetime, max_fold_zone))
4651
4652
4653 def transitions(self):
4654 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
4655 shift = ti[0] - prev_ti[0]
4656 yield datetime.utcfromtimestamp(t), shift
4657
4658 def nondst_folds(self):
4659 """Find all folds with the same value of isdst on both sides of the transition."""
4660 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
4661 shift = ti[0] - prev_ti[0]
4662 if shift < ZERO and ti[1] == prev_ti[1]:
4663 yield datetime.utcfromtimestamp(t), -shift, prev_ti[2], ti[2]
4664
4665 @classmethod
4666 def print_all_nondst_folds(cls, same_abbr=False, start_year=1):
4667 count = 0
4668 for zonename in cls.zonenames():
4669 tz = cls.fromname(zonename)
4670 for dt, shift, prev_abbr, abbr in tz.nondst_folds():
4671 if dt.year < start_year or same_abbr and prev_abbr != abbr:
4672 continue
4673 count += 1
4674 print("%3d) %-30s %s %10s %5s -> %s" %
4675 (count, zonename, dt, shift, prev_abbr, abbr))
4676
4677 def folds(self):
4678 for t, shift in self.transitions():
4679 if shift < ZERO:
4680 yield t, -shift
4681
4682 def gaps(self):
4683 for t, shift in self.transitions():
4684 if shift > ZERO:
4685 yield t, shift
4686
4687 def zeros(self):
4688 for t, shift in self.transitions():
4689 if not shift:
4690 yield t
4691
4692
4693class ZoneInfoTest(unittest.TestCase):
4694 zonename = 'America/New_York'
4695
4696 def setUp(self):
4697 if sys.platform == "win32":
4698 self.skipTest("Skipping zoneinfo tests on Windows")
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04004699 try:
4700 self.tz = ZoneInfo.fromname(self.zonename)
4701 except FileNotFoundError as err:
4702 self.skipTest("Skipping %s: %s" % (self.zonename, err))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004703
4704 def assertEquivDatetimes(self, a, b):
4705 self.assertEqual((a.replace(tzinfo=None), a.fold, id(a.tzinfo)),
4706 (b.replace(tzinfo=None), b.fold, id(b.tzinfo)))
4707
4708 def test_folds(self):
4709 tz = self.tz
4710 for dt, shift in tz.folds():
4711 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
4712 udt = dt + x
4713 ldt = tz.fromutc(udt.replace(tzinfo=tz))
4714 self.assertEqual(ldt.fold, 1)
4715 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
4716 self.assertEquivDatetimes(adt, ldt)
4717 utcoffset = ldt.utcoffset()
4718 self.assertEqual(ldt.replace(tzinfo=None), udt + utcoffset)
4719 # Round trip
4720 self.assertEquivDatetimes(ldt.astimezone(timezone.utc),
4721 udt.replace(tzinfo=timezone.utc))
4722
4723
4724 for x in [-timedelta.resolution, shift]:
4725 udt = dt + x
4726 udt = udt.replace(tzinfo=tz)
4727 ldt = tz.fromutc(udt)
4728 self.assertEqual(ldt.fold, 0)
4729
4730 def test_gaps(self):
4731 tz = self.tz
4732 for dt, shift in tz.gaps():
4733 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
4734 udt = dt + x
4735 udt = udt.replace(tzinfo=tz)
4736 ldt = tz.fromutc(udt)
4737 self.assertEqual(ldt.fold, 0)
4738 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
4739 self.assertEquivDatetimes(adt, ldt)
4740 utcoffset = ldt.utcoffset()
4741 self.assertEqual(ldt.replace(tzinfo=None), udt.replace(tzinfo=None) + utcoffset)
4742 # Create a local time inside the gap
4743 ldt = tz.fromutc(dt.replace(tzinfo=tz)) - shift + x
4744 self.assertLess(ldt.replace(fold=1).utcoffset(),
4745 ldt.replace(fold=0).utcoffset(),
4746 "At %s." % ldt)
4747
4748 for x in [-timedelta.resolution, shift]:
4749 udt = dt + x
4750 ldt = tz.fromutc(udt.replace(tzinfo=tz))
4751 self.assertEqual(ldt.fold, 0)
4752
4753 def test_system_transitions(self):
4754 if ('Riyadh8' in self.zonename or
4755 # From tzdata NEWS file:
4756 # The files solar87, solar88, and solar89 are no longer distributed.
4757 # They were a negative experiment - that is, a demonstration that
4758 # tz data can represent solar time only with some difficulty and error.
4759 # Their presence in the distribution caused confusion, as Riyadh
4760 # civil time was generally not solar time in those years.
4761 self.zonename.startswith('right/')):
4762 self.skipTest("Skipping %s" % self.zonename)
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04004763 tz = self.tz
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004764 TZ = os.environ.get('TZ')
4765 os.environ['TZ'] = self.zonename
4766 try:
4767 _time.tzset()
4768 for udt, shift in tz.transitions():
Alexander Belopolsky10c2dd22016-08-12 19:08:15 -04004769 if udt.year >= 2037:
4770 # System support for times around the end of 32-bit time_t
4771 # and later is flaky on many systems.
4772 break
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004773 s0 = (udt - datetime(1970, 1, 1)) // SEC
4774 ss = shift // SEC # shift seconds
4775 for x in [-40 * 3600, -20*3600, -1, 0,
4776 ss - 1, ss + 20 * 3600, ss + 40 * 3600]:
4777 s = s0 + x
4778 sdt = datetime.fromtimestamp(s)
4779 tzdt = datetime.fromtimestamp(s, tz).replace(tzinfo=None)
4780 self.assertEquivDatetimes(sdt, tzdt)
4781 s1 = sdt.timestamp()
4782 self.assertEqual(s, s1)
4783 if ss > 0: # gap
4784 # Create local time inside the gap
4785 dt = datetime.fromtimestamp(s0) - shift / 2
4786 ts0 = dt.timestamp()
4787 ts1 = dt.replace(fold=1).timestamp()
4788 self.assertEqual(ts0, s0 + ss / 2)
4789 self.assertEqual(ts1, s0 - ss / 2)
4790 finally:
4791 if TZ is None:
4792 del os.environ['TZ']
4793 else:
4794 os.environ['TZ'] = TZ
4795 _time.tzset()
4796
4797
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04004798class ZoneInfoCompleteTest(unittest.TestSuite):
4799 def __init__(self):
4800 tests = []
4801 if is_resource_enabled('tzdata'):
4802 for name in ZoneInfo.zonenames():
4803 Test = type('ZoneInfoTest[%s]' % name, (ZoneInfoTest,), {})
4804 Test.zonename = name
4805 for method in dir(Test):
4806 if method.startswith('test_'):
4807 tests.append(Test(method))
4808 super().__init__(tests)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004809
4810# Iran had a sub-minute UTC offset before 1946.
4811class IranTest(ZoneInfoTest):
Alexander Belopolsky07e2a0a2016-07-30 11:41:02 -04004812 zonename = 'Asia/Tehran'
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004813
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04004814def load_tests(loader, standard_tests, pattern):
4815 standard_tests.addTest(ZoneInfoCompleteTest())
4816 return standard_tests
4817
4818
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004819if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -05004820 unittest.main()