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