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