blob: 1d0c1c5bd236f6e88bb69a769c9f7d4adb2b64a0 [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# module tests
54
55class TestModule(unittest.TestCase):
56
57 def test_constants(self):
58 datetime = datetime_module
59 self.assertEqual(datetime.MINYEAR, 1)
60 self.assertEqual(datetime.MAXYEAR, 9999)
61
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -040062 def test_name_cleanup(self):
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +020063 if '_Pure' in self.__class__.__name__:
64 self.skipTest('Only run for Fast C implementation')
65
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -040066 datetime = datetime_module
67 names = set(name for name in dir(datetime)
68 if not name.startswith('__') and not name.endswith('__'))
69 allowed = set(['MAXYEAR', 'MINYEAR', 'date', 'datetime',
70 'datetime_CAPI', 'time', 'timedelta', 'timezone',
71 'tzinfo'])
72 self.assertEqual(names - allowed, set([]))
73
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -050074 def test_divide_and_round(self):
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +020075 if '_Fast' in self.__class__.__name__:
76 self.skipTest('Only run for Pure Python implementation')
77
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -050078 dar = datetime_module._divide_and_round
79
80 self.assertEqual(dar(-10, -3), 3)
81 self.assertEqual(dar(5, -2), -2)
82
83 # four cases: (2 signs of a) x (2 signs of b)
84 self.assertEqual(dar(7, 3), 2)
85 self.assertEqual(dar(-7, 3), -2)
86 self.assertEqual(dar(7, -3), -2)
87 self.assertEqual(dar(-7, -3), 2)
88
89 # ties to even - eight cases:
90 # (2 signs of a) x (2 signs of b) x (even / odd quotient)
91 self.assertEqual(dar(10, 4), 2)
92 self.assertEqual(dar(-10, 4), -2)
93 self.assertEqual(dar(10, -4), -2)
94 self.assertEqual(dar(-10, -4), 2)
95
96 self.assertEqual(dar(6, 4), 2)
97 self.assertEqual(dar(-6, 4), -2)
98 self.assertEqual(dar(6, -4), -2)
99 self.assertEqual(dar(-6, -4), 2)
100
101
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000102#############################################################################
103# tzinfo tests
104
105class FixedOffset(tzinfo):
106
107 def __init__(self, offset, name, dstoffset=42):
108 if isinstance(offset, int):
109 offset = timedelta(minutes=offset)
110 if isinstance(dstoffset, int):
111 dstoffset = timedelta(minutes=dstoffset)
112 self.__offset = offset
113 self.__name = name
114 self.__dstoffset = dstoffset
115 def __repr__(self):
116 return self.__name.lower()
117 def utcoffset(self, dt):
118 return self.__offset
119 def tzname(self, dt):
120 return self.__name
121 def dst(self, dt):
122 return self.__dstoffset
123
124class PicklableFixedOffset(FixedOffset):
125
126 def __init__(self, offset=None, name=None, dstoffset=None):
127 FixedOffset.__init__(self, offset, name, dstoffset)
128
Berker Peksage3385b42016-03-19 13:16:32 +0200129 def __getstate__(self):
130 return self.__dict__
131
Raymond Hettinger5a2146a2014-07-25 14:59:48 -0700132class _TZInfo(tzinfo):
133 def utcoffset(self, datetime_module):
134 return random.random()
135
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000136class TestTZInfo(unittest.TestCase):
137
Raymond Hettinger5a2146a2014-07-25 14:59:48 -0700138 def test_refcnt_crash_bug_22044(self):
139 tz1 = _TZInfo()
140 dt1 = datetime(2014, 7, 21, 11, 32, 3, 0, tz1)
141 with self.assertRaises(TypeError):
142 dt1.utcoffset()
143
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000144 def test_non_abstractness(self):
145 # In order to allow subclasses to get pickled, the C implementation
146 # wasn't able to get away with having __init__ raise
147 # NotImplementedError.
148 useless = tzinfo()
149 dt = datetime.max
150 self.assertRaises(NotImplementedError, useless.tzname, dt)
151 self.assertRaises(NotImplementedError, useless.utcoffset, dt)
152 self.assertRaises(NotImplementedError, useless.dst, dt)
153
154 def test_subclass_must_override(self):
155 class NotEnough(tzinfo):
156 def __init__(self, offset, name):
157 self.__offset = offset
158 self.__name = name
159 self.assertTrue(issubclass(NotEnough, tzinfo))
160 ne = NotEnough(3, "NotByALongShot")
161 self.assertIsInstance(ne, tzinfo)
162
163 dt = datetime.now()
164 self.assertRaises(NotImplementedError, ne.tzname, dt)
165 self.assertRaises(NotImplementedError, ne.utcoffset, dt)
166 self.assertRaises(NotImplementedError, ne.dst, dt)
167
168 def test_normal(self):
169 fo = FixedOffset(3, "Three")
170 self.assertIsInstance(fo, tzinfo)
171 for dt in datetime.now(), None:
172 self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3))
173 self.assertEqual(fo.tzname(dt), "Three")
174 self.assertEqual(fo.dst(dt), timedelta(minutes=42))
175
176 def test_pickling_base(self):
177 # There's no point to pickling tzinfo objects on their own (they
178 # carry no data), but they need to be picklable anyway else
179 # concrete subclasses can't be pickled.
180 orig = tzinfo.__new__(tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200181 self.assertIs(type(orig), tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000182 for pickler, unpickler, proto in pickle_choices:
183 green = pickler.dumps(orig, proto)
184 derived = unpickler.loads(green)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200185 self.assertIs(type(derived), tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000186
187 def test_pickling_subclass(self):
188 # Make sure we can pickle/unpickle an instance of a subclass.
189 offset = timedelta(minutes=-300)
190 for otype, args in [
191 (PicklableFixedOffset, (offset, 'cookie')),
192 (timezone, (offset,)),
193 (timezone, (offset, "EST"))]:
194 orig = otype(*args)
195 oname = orig.tzname(None)
196 self.assertIsInstance(orig, tzinfo)
197 self.assertIs(type(orig), otype)
198 self.assertEqual(orig.utcoffset(None), offset)
199 self.assertEqual(orig.tzname(None), oname)
200 for pickler, unpickler, proto in pickle_choices:
201 green = pickler.dumps(orig, proto)
202 derived = unpickler.loads(green)
203 self.assertIsInstance(derived, tzinfo)
204 self.assertIs(type(derived), otype)
205 self.assertEqual(derived.utcoffset(None), offset)
206 self.assertEqual(derived.tzname(None), oname)
207
Alexander Belopolskyc79447b2015-09-27 21:41:55 -0400208 def test_issue23600(self):
209 DSTDIFF = DSTOFFSET = timedelta(hours=1)
210
211 class UKSummerTime(tzinfo):
212 """Simple time zone which pretends to always be in summer time, since
213 that's what shows the failure.
214 """
215
216 def utcoffset(self, dt):
217 return DSTOFFSET
218
219 def dst(self, dt):
220 return DSTDIFF
221
222 def tzname(self, dt):
223 return 'UKSummerTime'
224
225 tz = UKSummerTime()
226 u = datetime(2014, 4, 26, 12, 1, tzinfo=tz)
227 t = tz.fromutc(u)
228 self.assertEqual(t - t.utcoffset(), u)
229
230
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000231class TestTimeZone(unittest.TestCase):
232
233 def setUp(self):
234 self.ACDT = timezone(timedelta(hours=9.5), 'ACDT')
235 self.EST = timezone(-timedelta(hours=5), 'EST')
236 self.DT = datetime(2010, 1, 1)
237
238 def test_str(self):
239 for tz in [self.ACDT, self.EST, timezone.utc,
240 timezone.min, timezone.max]:
241 self.assertEqual(str(tz), tz.tzname(None))
242
243 def test_repr(self):
244 datetime = datetime_module
245 for tz in [self.ACDT, self.EST, timezone.utc,
246 timezone.min, timezone.max]:
247 # test round-trip
248 tzrep = repr(tz)
249 self.assertEqual(tz, eval(tzrep))
250
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000251 def test_class_members(self):
252 limit = timedelta(hours=23, minutes=59)
253 self.assertEqual(timezone.utc.utcoffset(None), ZERO)
254 self.assertEqual(timezone.min.utcoffset(None), -limit)
255 self.assertEqual(timezone.max.utcoffset(None), limit)
256
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000257 def test_constructor(self):
Alexander Belopolsky1bcbaab2010-10-14 17:03:51 +0000258 self.assertIs(timezone.utc, timezone(timedelta(0)))
259 self.assertIsNot(timezone.utc, timezone(timedelta(0), 'UTC'))
260 self.assertEqual(timezone.utc, timezone(timedelta(0), 'UTC'))
Alexander Belopolsky018d3532017-07-31 10:26:50 -0400261 for subminute in [timedelta(microseconds=1), timedelta(seconds=1)]:
262 tz = timezone(subminute)
263 self.assertNotEqual(tz.utcoffset(None) % timedelta(minutes=1), 0)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000264 # invalid offsets
Alexander Belopolsky018d3532017-07-31 10:26:50 -0400265 for invalid in [timedelta(1, 1), timedelta(1)]:
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000266 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
Alexander Belopolsky018d3532017-07-31 10:26:50 -0400304 # Sub-minute offsets:
305 self.assertEqual('UTC+01:06:40', timezone(timedelta(0, 4000)).tzname(None))
306 self.assertEqual('UTC-01:06:40',
307 timezone(-timedelta(0, 4000)).tzname(None))
308 self.assertEqual('UTC+01:06:40.000001',
309 timezone(timedelta(0, 4000, 1)).tzname(None))
310 self.assertEqual('UTC-01:06:40.000001',
311 timezone(-timedelta(0, 4000, 1)).tzname(None))
312
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000313 with self.assertRaises(TypeError): self.EST.tzname('')
314 with self.assertRaises(TypeError): self.EST.tzname(5)
315
316 def test_fromutc(self):
317 with self.assertRaises(ValueError):
318 timezone.utc.fromutc(self.DT)
319 with self.assertRaises(TypeError):
320 timezone.utc.fromutc('not datetime')
321 for tz in [self.EST, self.ACDT, Eastern]:
322 utctime = self.DT.replace(tzinfo=tz)
323 local = tz.fromutc(utctime)
324 self.assertEqual(local - utctime, tz.utcoffset(local))
325 self.assertEqual(local,
326 self.DT.replace(tzinfo=timezone.utc))
327
328 def test_comparison(self):
329 self.assertNotEqual(timezone(ZERO), timezone(HOUR))
330 self.assertEqual(timezone(HOUR), timezone(HOUR))
331 self.assertEqual(timezone(-5 * HOUR), timezone(-5 * HOUR, 'EST'))
332 with self.assertRaises(TypeError): timezone(ZERO) < timezone(ZERO)
333 self.assertIn(timezone(ZERO), {timezone(ZERO)})
Georg Brandl0085a242012-09-22 09:23:12 +0200334 self.assertTrue(timezone(ZERO) != None)
335 self.assertFalse(timezone(ZERO) == None)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000336
337 def test_aware_datetime(self):
338 # test that timezone instances can be used by datetime
339 t = datetime(1, 1, 1)
340 for tz in [timezone.min, timezone.max, timezone.utc]:
341 self.assertEqual(tz.tzname(t),
342 t.replace(tzinfo=tz).tzname())
343 self.assertEqual(tz.utcoffset(t),
344 t.replace(tzinfo=tz).utcoffset())
345 self.assertEqual(tz.dst(t),
346 t.replace(tzinfo=tz).dst())
347
Serhiy Storchakae28209f2015-11-16 11:12:58 +0200348 def test_pickle(self):
349 for tz in self.ACDT, self.EST, timezone.min, timezone.max:
350 for pickler, unpickler, proto in pickle_choices:
351 tz_copy = unpickler.loads(pickler.dumps(tz, proto))
352 self.assertEqual(tz_copy, tz)
353 tz = timezone.utc
354 for pickler, unpickler, proto in pickle_choices:
355 tz_copy = unpickler.loads(pickler.dumps(tz, proto))
356 self.assertIs(tz_copy, tz)
357
358 def test_copy(self):
359 for tz in self.ACDT, self.EST, timezone.min, timezone.max:
360 tz_copy = copy.copy(tz)
361 self.assertEqual(tz_copy, tz)
362 tz = timezone.utc
363 tz_copy = copy.copy(tz)
364 self.assertIs(tz_copy, tz)
365
366 def test_deepcopy(self):
367 for tz in self.ACDT, self.EST, timezone.min, timezone.max:
368 tz_copy = copy.deepcopy(tz)
369 self.assertEqual(tz_copy, tz)
370 tz = timezone.utc
371 tz_copy = copy.deepcopy(tz)
372 self.assertIs(tz_copy, tz)
373
374
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000375#############################################################################
Ezio Melotti85a86292013-08-17 16:57:41 +0300376# Base class for testing a particular aspect of timedelta, time, date and
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000377# datetime comparisons.
378
379class HarmlessMixedComparison:
380 # Test that __eq__ and __ne__ don't complain for mixed-type comparisons.
381
382 # Subclasses must define 'theclass', and theclass(1, 1, 1) must be a
383 # legit constructor.
384
385 def test_harmless_mixed_comparison(self):
386 me = self.theclass(1, 1, 1)
387
388 self.assertFalse(me == ())
389 self.assertTrue(me != ())
390 self.assertFalse(() == me)
391 self.assertTrue(() != me)
392
393 self.assertIn(me, [1, 20, [], me])
394 self.assertIn([], [me, 1, 20, []])
395
396 def test_harmful_mixed_comparison(self):
397 me = self.theclass(1, 1, 1)
398
399 self.assertRaises(TypeError, lambda: me < ())
400 self.assertRaises(TypeError, lambda: me <= ())
401 self.assertRaises(TypeError, lambda: me > ())
402 self.assertRaises(TypeError, lambda: me >= ())
403
404 self.assertRaises(TypeError, lambda: () < me)
405 self.assertRaises(TypeError, lambda: () <= me)
406 self.assertRaises(TypeError, lambda: () > me)
407 self.assertRaises(TypeError, lambda: () >= me)
408
409#############################################################################
410# timedelta tests
411
412class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
413
414 theclass = timedelta
415
416 def test_constructor(self):
417 eq = self.assertEqual
418 td = timedelta
419
420 # Check keyword args to constructor
421 eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
422 milliseconds=0, microseconds=0))
423 eq(td(1), td(days=1))
424 eq(td(0, 1), td(seconds=1))
425 eq(td(0, 0, 1), td(microseconds=1))
426 eq(td(weeks=1), td(days=7))
427 eq(td(days=1), td(hours=24))
428 eq(td(hours=1), td(minutes=60))
429 eq(td(minutes=1), td(seconds=60))
430 eq(td(seconds=1), td(milliseconds=1000))
431 eq(td(milliseconds=1), td(microseconds=1000))
432
433 # Check float args to constructor
434 eq(td(weeks=1.0/7), td(days=1))
435 eq(td(days=1.0/24), td(hours=1))
436 eq(td(hours=1.0/60), td(minutes=1))
437 eq(td(minutes=1.0/60), td(seconds=1))
438 eq(td(seconds=0.001), td(milliseconds=1))
439 eq(td(milliseconds=0.001), td(microseconds=1))
440
441 def test_computations(self):
442 eq = self.assertEqual
443 td = timedelta
444
445 a = td(7) # One week
446 b = td(0, 60) # One minute
447 c = td(0, 0, 1000) # One millisecond
448 eq(a+b+c, td(7, 60, 1000))
449 eq(a-b, td(6, 24*3600 - 60))
450 eq(b.__rsub__(a), td(6, 24*3600 - 60))
451 eq(-a, td(-7))
452 eq(+a, td(7))
453 eq(-b, td(-1, 24*3600 - 60))
454 eq(-c, td(-1, 24*3600 - 1, 999000))
455 eq(abs(a), a)
456 eq(abs(-a), a)
457 eq(td(6, 24*3600), a)
458 eq(td(0, 0, 60*1000000), b)
459 eq(a*10, td(70))
460 eq(a*10, 10*a)
461 eq(a*10, 10*a)
462 eq(b*10, td(0, 600))
463 eq(10*b, td(0, 600))
464 eq(b*10, td(0, 600))
465 eq(c*10, td(0, 0, 10000))
466 eq(10*c, td(0, 0, 10000))
467 eq(c*10, td(0, 0, 10000))
468 eq(a*-1, -a)
469 eq(b*-2, -b-b)
470 eq(c*-2, -c+-c)
471 eq(b*(60*24), (b*60)*24)
472 eq(b*(60*24), (60*b)*24)
473 eq(c*1000, td(0, 1))
474 eq(1000*c, td(0, 1))
475 eq(a//7, td(1))
476 eq(b//10, td(0, 6))
477 eq(c//1000, td(0, 0, 1))
478 eq(a//10, td(0, 7*24*360))
479 eq(a//3600000, td(0, 0, 7*24*1000))
480 eq(a/0.5, td(14))
481 eq(b/0.5, td(0, 120))
482 eq(a/7, td(1))
483 eq(b/10, td(0, 6))
484 eq(c/1000, td(0, 0, 1))
485 eq(a/10, td(0, 7*24*360))
486 eq(a/3600000, td(0, 0, 7*24*1000))
487
488 # Multiplication by float
489 us = td(microseconds=1)
490 eq((3*us) * 0.5, 2*us)
491 eq((5*us) * 0.5, 2*us)
492 eq(0.5 * (3*us), 2*us)
493 eq(0.5 * (5*us), 2*us)
494 eq((-3*us) * 0.5, -2*us)
495 eq((-5*us) * 0.5, -2*us)
496
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -0500497 # Issue #23521
498 eq(td(seconds=1) * 0.123456, td(microseconds=123456))
499 eq(td(seconds=1) * 0.6112295, td(microseconds=611229))
500
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000501 # Division by int and float
502 eq((3*us) / 2, 2*us)
503 eq((5*us) / 2, 2*us)
504 eq((-3*us) / 2.0, -2*us)
505 eq((-5*us) / 2.0, -2*us)
506 eq((3*us) / -2, -2*us)
507 eq((5*us) / -2, -2*us)
508 eq((3*us) / -2.0, -2*us)
509 eq((5*us) / -2.0, -2*us)
510 for i in range(-10, 10):
511 eq((i*us/3)//us, round(i/3))
512 for i in range(-10, 10):
513 eq((i*us/-3)//us, round(i/-3))
514
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -0500515 # Issue #23521
516 eq(td(seconds=1) / (1 / 0.6112295), td(microseconds=611229))
517
Alexander Belopolskyb6f5ec72011-04-05 20:07:38 -0400518 # Issue #11576
519 eq(td(999999999, 86399, 999999) - td(999999999, 86399, 999998),
520 td(0, 0, 1))
521 eq(td(999999999, 1, 1) - td(999999999, 1, 0),
522 td(0, 0, 1))
523
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000524 def test_disallowed_computations(self):
525 a = timedelta(42)
526
527 # Add/sub ints or floats should be illegal
528 for i in 1, 1.0:
529 self.assertRaises(TypeError, lambda: a+i)
530 self.assertRaises(TypeError, lambda: a-i)
531 self.assertRaises(TypeError, lambda: i+a)
532 self.assertRaises(TypeError, lambda: i-a)
533
534 # Division of int by timedelta doesn't make sense.
535 # Division by zero doesn't make sense.
536 zero = 0
537 self.assertRaises(TypeError, lambda: zero // a)
538 self.assertRaises(ZeroDivisionError, lambda: a // zero)
539 self.assertRaises(ZeroDivisionError, lambda: a / zero)
540 self.assertRaises(ZeroDivisionError, lambda: a / 0.0)
541 self.assertRaises(TypeError, lambda: a / '')
542
Eric Smith3ab08ca2010-12-04 15:17:38 +0000543 @support.requires_IEEE_754
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000544 def test_disallowed_special(self):
545 a = timedelta(42)
546 self.assertRaises(ValueError, a.__mul__, NAN)
547 self.assertRaises(ValueError, a.__truediv__, NAN)
548
549 def test_basic_attributes(self):
550 days, seconds, us = 1, 7, 31
551 td = timedelta(days, seconds, us)
552 self.assertEqual(td.days, days)
553 self.assertEqual(td.seconds, seconds)
554 self.assertEqual(td.microseconds, us)
555
556 def test_total_seconds(self):
557 td = timedelta(days=365)
558 self.assertEqual(td.total_seconds(), 31536000.0)
559 for total_seconds in [123456.789012, -123456.789012, 0.123456, 0, 1e6]:
560 td = timedelta(seconds=total_seconds)
561 self.assertEqual(td.total_seconds(), total_seconds)
562 # Issue8644: Test that td.total_seconds() has the same
563 # accuracy as td / timedelta(seconds=1).
564 for ms in [-1, -2, -123]:
565 td = timedelta(microseconds=ms)
566 self.assertEqual(td.total_seconds(), td / timedelta(seconds=1))
567
568 def test_carries(self):
569 t1 = timedelta(days=100,
570 weeks=-7,
571 hours=-24*(100-49),
572 minutes=-3,
573 seconds=12,
574 microseconds=(3*60 - 12) * 1e6 + 1)
575 t2 = timedelta(microseconds=1)
576 self.assertEqual(t1, t2)
577
578 def test_hash_equality(self):
579 t1 = timedelta(days=100,
580 weeks=-7,
581 hours=-24*(100-49),
582 minutes=-3,
583 seconds=12,
584 microseconds=(3*60 - 12) * 1000000)
585 t2 = timedelta()
586 self.assertEqual(hash(t1), hash(t2))
587
588 t1 += timedelta(weeks=7)
589 t2 += timedelta(days=7*7)
590 self.assertEqual(t1, t2)
591 self.assertEqual(hash(t1), hash(t2))
592
593 d = {t1: 1}
594 d[t2] = 2
595 self.assertEqual(len(d), 1)
596 self.assertEqual(d[t1], 2)
597
598 def test_pickling(self):
599 args = 12, 34, 56
600 orig = timedelta(*args)
601 for pickler, unpickler, proto in pickle_choices:
602 green = pickler.dumps(orig, proto)
603 derived = unpickler.loads(green)
604 self.assertEqual(orig, derived)
605
606 def test_compare(self):
607 t1 = timedelta(2, 3, 4)
608 t2 = timedelta(2, 3, 4)
609 self.assertEqual(t1, t2)
610 self.assertTrue(t1 <= t2)
611 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200612 self.assertFalse(t1 != t2)
613 self.assertFalse(t1 < t2)
614 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000615
616 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
617 t2 = timedelta(*args) # this is larger than t1
618 self.assertTrue(t1 < t2)
619 self.assertTrue(t2 > t1)
620 self.assertTrue(t1 <= t2)
621 self.assertTrue(t2 >= t1)
622 self.assertTrue(t1 != t2)
623 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200624 self.assertFalse(t1 == t2)
625 self.assertFalse(t2 == t1)
626 self.assertFalse(t1 > t2)
627 self.assertFalse(t2 < t1)
628 self.assertFalse(t1 >= t2)
629 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000630
631 for badarg in OTHERSTUFF:
632 self.assertEqual(t1 == badarg, False)
633 self.assertEqual(t1 != badarg, True)
634 self.assertEqual(badarg == t1, False)
635 self.assertEqual(badarg != t1, True)
636
637 self.assertRaises(TypeError, lambda: t1 <= badarg)
638 self.assertRaises(TypeError, lambda: t1 < badarg)
639 self.assertRaises(TypeError, lambda: t1 > badarg)
640 self.assertRaises(TypeError, lambda: t1 >= badarg)
641 self.assertRaises(TypeError, lambda: badarg <= t1)
642 self.assertRaises(TypeError, lambda: badarg < t1)
643 self.assertRaises(TypeError, lambda: badarg > t1)
644 self.assertRaises(TypeError, lambda: badarg >= t1)
645
646 def test_str(self):
647 td = timedelta
648 eq = self.assertEqual
649
650 eq(str(td(1)), "1 day, 0:00:00")
651 eq(str(td(-1)), "-1 day, 0:00:00")
652 eq(str(td(2)), "2 days, 0:00:00")
653 eq(str(td(-2)), "-2 days, 0:00:00")
654
655 eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
656 eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
657 eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
658 "-210 days, 23:12:34")
659
660 eq(str(td(milliseconds=1)), "0:00:00.001000")
661 eq(str(td(microseconds=3)), "0:00:00.000003")
662
663 eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
664 microseconds=999999)),
665 "999999999 days, 23:59:59.999999")
666
667 def test_repr(self):
668 name = 'datetime.' + self.theclass.__name__
669 self.assertEqual(repr(self.theclass(1)),
Utkarsh Upadhyaycc5a65c2017-07-25 23:51:33 +0200670 "%s(days=1)" % name)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000671 self.assertEqual(repr(self.theclass(10, 2)),
Utkarsh Upadhyaycc5a65c2017-07-25 23:51:33 +0200672 "%s(days=10, seconds=2)" % name)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000673 self.assertEqual(repr(self.theclass(-10, 2, 400000)),
Utkarsh Upadhyaycc5a65c2017-07-25 23:51:33 +0200674 "%s(days=-10, seconds=2, microseconds=400000)" % name)
675 self.assertEqual(repr(self.theclass(seconds=60)),
676 "%s(seconds=60)" % name)
677 self.assertEqual(repr(self.theclass()),
678 "%s(0)" % name)
679 self.assertEqual(repr(self.theclass(microseconds=100)),
680 "%s(microseconds=100)" % name)
681 self.assertEqual(repr(self.theclass(days=1, microseconds=100)),
682 "%s(days=1, microseconds=100)" % name)
683 self.assertEqual(repr(self.theclass(seconds=1, microseconds=100)),
684 "%s(seconds=1, microseconds=100)" % name)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000685
686 def test_roundtrip(self):
687 for td in (timedelta(days=999999999, hours=23, minutes=59,
688 seconds=59, microseconds=999999),
689 timedelta(days=-999999999),
690 timedelta(days=-999999999, seconds=1),
691 timedelta(days=1, seconds=2, microseconds=3)):
692
693 # Verify td -> string -> td identity.
694 s = repr(td)
695 self.assertTrue(s.startswith('datetime.'))
696 s = s[9:]
697 td2 = eval(s)
698 self.assertEqual(td, td2)
699
700 # Verify identity via reconstructing from pieces.
701 td2 = timedelta(td.days, td.seconds, td.microseconds)
702 self.assertEqual(td, td2)
703
704 def test_resolution_info(self):
705 self.assertIsInstance(timedelta.min, timedelta)
706 self.assertIsInstance(timedelta.max, timedelta)
707 self.assertIsInstance(timedelta.resolution, timedelta)
708 self.assertTrue(timedelta.max > timedelta.min)
709 self.assertEqual(timedelta.min, timedelta(-999999999))
710 self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
711 self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
712
713 def test_overflow(self):
714 tiny = timedelta.resolution
715
716 td = timedelta.min + tiny
717 td -= tiny # no problem
718 self.assertRaises(OverflowError, td.__sub__, tiny)
719 self.assertRaises(OverflowError, td.__add__, -tiny)
720
721 td = timedelta.max - tiny
722 td += tiny # no problem
723 self.assertRaises(OverflowError, td.__add__, tiny)
724 self.assertRaises(OverflowError, td.__sub__, -tiny)
725
726 self.assertRaises(OverflowError, lambda: -timedelta.max)
727
728 day = timedelta(1)
729 self.assertRaises(OverflowError, day.__mul__, 10**9)
730 self.assertRaises(OverflowError, day.__mul__, 1e9)
731 self.assertRaises(OverflowError, day.__truediv__, 1e-20)
732 self.assertRaises(OverflowError, day.__truediv__, 1e-10)
733 self.assertRaises(OverflowError, day.__truediv__, 9e-10)
734
Eric Smith3ab08ca2010-12-04 15:17:38 +0000735 @support.requires_IEEE_754
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000736 def _test_overflow_special(self):
737 day = timedelta(1)
738 self.assertRaises(OverflowError, day.__mul__, INF)
739 self.assertRaises(OverflowError, day.__mul__, -INF)
740
741 def test_microsecond_rounding(self):
742 td = timedelta
743 eq = self.assertEqual
744
745 # Single-field rounding.
746 eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
747 eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
Victor Stinner69cc4872015-09-08 23:58:54 +0200748 eq(td(milliseconds=0.5/1000), td(microseconds=0))
749 eq(td(milliseconds=-0.5/1000), td(microseconds=-0))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000750 eq(td(milliseconds=0.6/1000), td(microseconds=1))
751 eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
Victor Stinner69cc4872015-09-08 23:58:54 +0200752 eq(td(milliseconds=1.5/1000), td(microseconds=2))
753 eq(td(milliseconds=-1.5/1000), td(microseconds=-2))
754 eq(td(seconds=0.5/10**6), td(microseconds=0))
755 eq(td(seconds=-0.5/10**6), td(microseconds=-0))
756 eq(td(seconds=1/2**7), td(microseconds=7812))
757 eq(td(seconds=-1/2**7), td(microseconds=-7812))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000758
759 # Rounding due to contributions from more than one field.
760 us_per_hour = 3600e6
761 us_per_day = us_per_hour * 24
762 eq(td(days=.4/us_per_day), td(0))
763 eq(td(hours=.2/us_per_hour), td(0))
Victor Stinnercd5d7652015-09-09 01:09:21 +0200764 eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000765
766 eq(td(days=-.4/us_per_day), td(0))
767 eq(td(hours=-.2/us_per_hour), td(0))
768 eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
769
Victor Stinner69cc4872015-09-08 23:58:54 +0200770 # Test for a patch in Issue 8860
771 eq(td(microseconds=0.5), 0.5*td(microseconds=1.0))
772 eq(td(microseconds=0.5)//td.resolution, 0.5*td.resolution//td.resolution)
773
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000774 def test_massive_normalization(self):
775 td = timedelta(microseconds=-1)
776 self.assertEqual((td.days, td.seconds, td.microseconds),
777 (-1, 24*3600-1, 999999))
778
779 def test_bool(self):
780 self.assertTrue(timedelta(1))
781 self.assertTrue(timedelta(0, 1))
782 self.assertTrue(timedelta(0, 0, 1))
783 self.assertTrue(timedelta(microseconds=1))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200784 self.assertFalse(timedelta(0))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000785
786 def test_subclass_timedelta(self):
787
788 class T(timedelta):
789 @staticmethod
790 def from_td(td):
791 return T(td.days, td.seconds, td.microseconds)
792
793 def as_hours(self):
794 sum = (self.days * 24 +
795 self.seconds / 3600.0 +
796 self.microseconds / 3600e6)
797 return round(sum)
798
799 t1 = T(days=1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200800 self.assertIs(type(t1), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000801 self.assertEqual(t1.as_hours(), 24)
802
803 t2 = T(days=-1, seconds=-3600)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200804 self.assertIs(type(t2), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000805 self.assertEqual(t2.as_hours(), -25)
806
807 t3 = t1 + t2
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200808 self.assertIs(type(t3), timedelta)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000809 t4 = T.from_td(t3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200810 self.assertIs(type(t4), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000811 self.assertEqual(t3.days, t4.days)
812 self.assertEqual(t3.seconds, t4.seconds)
813 self.assertEqual(t3.microseconds, t4.microseconds)
814 self.assertEqual(str(t3), str(t4))
815 self.assertEqual(t4.as_hours(), -1)
816
817 def test_division(self):
818 t = timedelta(hours=1, minutes=24, seconds=19)
819 second = timedelta(seconds=1)
820 self.assertEqual(t / second, 5059.0)
821 self.assertEqual(t // second, 5059)
822
823 t = timedelta(minutes=2, seconds=30)
824 minute = timedelta(minutes=1)
825 self.assertEqual(t / minute, 2.5)
826 self.assertEqual(t // minute, 2)
827
828 zerotd = timedelta(0)
829 self.assertRaises(ZeroDivisionError, truediv, t, zerotd)
830 self.assertRaises(ZeroDivisionError, floordiv, t, zerotd)
831
832 # self.assertRaises(TypeError, truediv, t, 2)
833 # note: floor division of a timedelta by an integer *is*
834 # currently permitted.
835
836 def test_remainder(self):
837 t = timedelta(minutes=2, seconds=30)
838 minute = timedelta(minutes=1)
839 r = t % minute
840 self.assertEqual(r, timedelta(seconds=30))
841
842 t = timedelta(minutes=-2, seconds=30)
843 r = t % minute
844 self.assertEqual(r, timedelta(seconds=30))
845
846 zerotd = timedelta(0)
847 self.assertRaises(ZeroDivisionError, mod, t, zerotd)
848
849 self.assertRaises(TypeError, mod, t, 10)
850
851 def test_divmod(self):
852 t = timedelta(minutes=2, seconds=30)
853 minute = timedelta(minutes=1)
854 q, r = divmod(t, minute)
855 self.assertEqual(q, 2)
856 self.assertEqual(r, timedelta(seconds=30))
857
858 t = timedelta(minutes=-2, seconds=30)
859 q, r = divmod(t, minute)
860 self.assertEqual(q, -2)
861 self.assertEqual(r, timedelta(seconds=30))
862
863 zerotd = timedelta(0)
864 self.assertRaises(ZeroDivisionError, divmod, t, zerotd)
865
866 self.assertRaises(TypeError, divmod, t, 10)
867
Oren Milman865e4b42017-09-19 15:58:11 +0300868 def test_issue31293(self):
869 # The interpreter shouldn't crash in case a timedelta is divided or
870 # multiplied by a float with a bad as_integer_ratio() method.
871 def get_bad_float(bad_ratio):
872 class BadFloat(float):
873 def as_integer_ratio(self):
874 return bad_ratio
875 return BadFloat()
876
877 with self.assertRaises(TypeError):
878 timedelta() / get_bad_float(1 << 1000)
879 with self.assertRaises(TypeError):
880 timedelta() * get_bad_float(1 << 1000)
881
882 for bad_ratio in [(), (42, ), (1, 2, 3)]:
883 with self.assertRaises(ValueError):
884 timedelta() / get_bad_float(bad_ratio)
885 with self.assertRaises(ValueError):
886 timedelta() * get_bad_float(bad_ratio)
887
Serhiy Storchaka4ffd4652017-10-23 17:12:28 +0300888 def test_issue31752(self):
889 # The interpreter shouldn't crash because divmod() returns negative
890 # remainder.
891 class BadInt(int):
892 def __mul__(self, other):
893 return Prod()
894
895 class Prod:
896 def __radd__(self, other):
897 return Sum()
898
899 class Sum(int):
900 def __divmod__(self, other):
901 # negative remainder
902 return (0, -1)
903
904 timedelta(microseconds=BadInt(1))
905 timedelta(hours=BadInt(1))
906 timedelta(weeks=BadInt(1))
907
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000908
909#############################################################################
910# date tests
911
912class TestDateOnly(unittest.TestCase):
913 # Tests here won't pass if also run on datetime objects, so don't
914 # subclass this to test datetimes too.
915
916 def test_delta_non_days_ignored(self):
917 dt = date(2000, 1, 2)
918 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
919 microseconds=5)
920 days = timedelta(delta.days)
921 self.assertEqual(days, timedelta(1))
922
923 dt2 = dt + delta
924 self.assertEqual(dt2, dt + days)
925
926 dt2 = delta + dt
927 self.assertEqual(dt2, dt + days)
928
929 dt2 = dt - delta
930 self.assertEqual(dt2, dt - days)
931
932 delta = -delta
933 days = timedelta(delta.days)
934 self.assertEqual(days, timedelta(-2))
935
936 dt2 = dt + delta
937 self.assertEqual(dt2, dt + days)
938
939 dt2 = delta + dt
940 self.assertEqual(dt2, dt + days)
941
942 dt2 = dt - delta
943 self.assertEqual(dt2, dt - days)
944
945class SubclassDate(date):
946 sub_var = 1
947
948class TestDate(HarmlessMixedComparison, unittest.TestCase):
949 # Tests here should pass for both dates and datetimes, except for a
950 # few tests that TestDateTime overrides.
951
952 theclass = date
953
954 def test_basic_attributes(self):
955 dt = self.theclass(2002, 3, 1)
956 self.assertEqual(dt.year, 2002)
957 self.assertEqual(dt.month, 3)
958 self.assertEqual(dt.day, 1)
959
960 def test_roundtrip(self):
961 for dt in (self.theclass(1, 2, 3),
962 self.theclass.today()):
963 # Verify dt -> string -> date identity.
964 s = repr(dt)
965 self.assertTrue(s.startswith('datetime.'))
966 s = s[9:]
967 dt2 = eval(s)
968 self.assertEqual(dt, dt2)
969
970 # Verify identity via reconstructing from pieces.
971 dt2 = self.theclass(dt.year, dt.month, dt.day)
972 self.assertEqual(dt, dt2)
973
974 def test_ordinal_conversions(self):
975 # Check some fixed values.
976 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
977 (1, 12, 31, 365),
978 (2, 1, 1, 366),
979 # first example from "Calendrical Calculations"
980 (1945, 11, 12, 710347)]:
981 d = self.theclass(y, m, d)
982 self.assertEqual(n, d.toordinal())
983 fromord = self.theclass.fromordinal(n)
984 self.assertEqual(d, fromord)
985 if hasattr(fromord, "hour"):
986 # if we're checking something fancier than a date, verify
987 # the extra fields have been zeroed out
988 self.assertEqual(fromord.hour, 0)
989 self.assertEqual(fromord.minute, 0)
990 self.assertEqual(fromord.second, 0)
991 self.assertEqual(fromord.microsecond, 0)
992
993 # Check first and last days of year spottily across the whole
994 # range of years supported.
995 for year in range(MINYEAR, MAXYEAR+1, 7):
996 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
997 d = self.theclass(year, 1, 1)
998 n = d.toordinal()
999 d2 = self.theclass.fromordinal(n)
1000 self.assertEqual(d, d2)
1001 # Verify that moving back a day gets to the end of year-1.
1002 if year > 1:
1003 d = self.theclass.fromordinal(n-1)
1004 d2 = self.theclass(year-1, 12, 31)
1005 self.assertEqual(d, d2)
1006 self.assertEqual(d2.toordinal(), n-1)
1007
1008 # Test every day in a leap-year and a non-leap year.
1009 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
1010 for year, isleap in (2000, True), (2002, False):
1011 n = self.theclass(year, 1, 1).toordinal()
1012 for month, maxday in zip(range(1, 13), dim):
1013 if month == 2 and isleap:
1014 maxday += 1
1015 for day in range(1, maxday+1):
1016 d = self.theclass(year, month, day)
1017 self.assertEqual(d.toordinal(), n)
1018 self.assertEqual(d, self.theclass.fromordinal(n))
1019 n += 1
1020
1021 def test_extreme_ordinals(self):
1022 a = self.theclass.min
1023 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
1024 aord = a.toordinal()
1025 b = a.fromordinal(aord)
1026 self.assertEqual(a, b)
1027
1028 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
1029
1030 b = a + timedelta(days=1)
1031 self.assertEqual(b.toordinal(), aord + 1)
1032 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
1033
1034 a = self.theclass.max
1035 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
1036 aord = a.toordinal()
1037 b = a.fromordinal(aord)
1038 self.assertEqual(a, b)
1039
1040 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
1041
1042 b = a - timedelta(days=1)
1043 self.assertEqual(b.toordinal(), aord - 1)
1044 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
1045
1046 def test_bad_constructor_arguments(self):
1047 # bad years
1048 self.theclass(MINYEAR, 1, 1) # no exception
1049 self.theclass(MAXYEAR, 1, 1) # no exception
1050 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1051 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1052 # bad months
1053 self.theclass(2000, 1, 1) # no exception
1054 self.theclass(2000, 12, 1) # no exception
1055 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1056 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1057 # bad days
1058 self.theclass(2000, 2, 29) # no exception
1059 self.theclass(2004, 2, 29) # no exception
1060 self.theclass(2400, 2, 29) # no exception
1061 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1062 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1063 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1064 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1065 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1066 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1067
1068 def test_hash_equality(self):
1069 d = self.theclass(2000, 12, 31)
1070 # same thing
1071 e = self.theclass(2000, 12, 31)
1072 self.assertEqual(d, e)
1073 self.assertEqual(hash(d), hash(e))
1074
1075 dic = {d: 1}
1076 dic[e] = 2
1077 self.assertEqual(len(dic), 1)
1078 self.assertEqual(dic[d], 2)
1079 self.assertEqual(dic[e], 2)
1080
1081 d = self.theclass(2001, 1, 1)
1082 # same thing
1083 e = self.theclass(2001, 1, 1)
1084 self.assertEqual(d, e)
1085 self.assertEqual(hash(d), hash(e))
1086
1087 dic = {d: 1}
1088 dic[e] = 2
1089 self.assertEqual(len(dic), 1)
1090 self.assertEqual(dic[d], 2)
1091 self.assertEqual(dic[e], 2)
1092
1093 def test_computations(self):
1094 a = self.theclass(2002, 1, 31)
1095 b = self.theclass(1956, 1, 31)
1096 c = self.theclass(2001,2,1)
1097
1098 diff = a-b
1099 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1100 self.assertEqual(diff.seconds, 0)
1101 self.assertEqual(diff.microseconds, 0)
1102
1103 day = timedelta(1)
1104 week = timedelta(7)
1105 a = self.theclass(2002, 3, 2)
1106 self.assertEqual(a + day, self.theclass(2002, 3, 3))
1107 self.assertEqual(day + a, self.theclass(2002, 3, 3))
1108 self.assertEqual(a - day, self.theclass(2002, 3, 1))
1109 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
1110 self.assertEqual(a + week, self.theclass(2002, 3, 9))
1111 self.assertEqual(a - week, self.theclass(2002, 2, 23))
1112 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
1113 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
1114 self.assertEqual((a + week) - a, week)
1115 self.assertEqual((a + day) - a, day)
1116 self.assertEqual((a - week) - a, -week)
1117 self.assertEqual((a - day) - a, -day)
1118 self.assertEqual(a - (a + week), -week)
1119 self.assertEqual(a - (a + day), -day)
1120 self.assertEqual(a - (a - week), week)
1121 self.assertEqual(a - (a - day), day)
1122 self.assertEqual(c - (c - day), day)
1123
1124 # Add/sub ints or floats should be illegal
1125 for i in 1, 1.0:
1126 self.assertRaises(TypeError, lambda: a+i)
1127 self.assertRaises(TypeError, lambda: a-i)
1128 self.assertRaises(TypeError, lambda: i+a)
1129 self.assertRaises(TypeError, lambda: i-a)
1130
1131 # delta - date is senseless.
1132 self.assertRaises(TypeError, lambda: day - a)
1133 # mixing date and (delta or date) via * or // is senseless
1134 self.assertRaises(TypeError, lambda: day * a)
1135 self.assertRaises(TypeError, lambda: a * day)
1136 self.assertRaises(TypeError, lambda: day // a)
1137 self.assertRaises(TypeError, lambda: a // day)
1138 self.assertRaises(TypeError, lambda: a * a)
1139 self.assertRaises(TypeError, lambda: a // a)
1140 # date + date is senseless
1141 self.assertRaises(TypeError, lambda: a + a)
1142
1143 def test_overflow(self):
1144 tiny = self.theclass.resolution
1145
1146 for delta in [tiny, timedelta(1), timedelta(2)]:
1147 dt = self.theclass.min + delta
1148 dt -= delta # no problem
1149 self.assertRaises(OverflowError, dt.__sub__, delta)
1150 self.assertRaises(OverflowError, dt.__add__, -delta)
1151
1152 dt = self.theclass.max - delta
1153 dt += delta # no problem
1154 self.assertRaises(OverflowError, dt.__add__, delta)
1155 self.assertRaises(OverflowError, dt.__sub__, -delta)
1156
1157 def test_fromtimestamp(self):
1158 import time
1159
1160 # Try an arbitrary fixed value.
1161 year, month, day = 1999, 9, 19
1162 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
1163 d = self.theclass.fromtimestamp(ts)
1164 self.assertEqual(d.year, year)
1165 self.assertEqual(d.month, month)
1166 self.assertEqual(d.day, day)
1167
1168 def test_insane_fromtimestamp(self):
1169 # It's possible that some platform maps time_t to double,
1170 # and that this test will fail there. This test should
1171 # exempt such platforms (provided they return reasonable
1172 # results!).
1173 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001174 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001175 insane)
1176
1177 def test_today(self):
1178 import time
1179
1180 # We claim that today() is like fromtimestamp(time.time()), so
1181 # prove it.
1182 for dummy in range(3):
1183 today = self.theclass.today()
1184 ts = time.time()
1185 todayagain = self.theclass.fromtimestamp(ts)
1186 if today == todayagain:
1187 break
1188 # There are several legit reasons that could fail:
1189 # 1. It recently became midnight, between the today() and the
1190 # time() calls.
1191 # 2. The platform time() has such fine resolution that we'll
1192 # never get the same value twice.
1193 # 3. The platform time() has poor resolution, and we just
1194 # happened to call today() right before a resolution quantum
1195 # boundary.
1196 # 4. The system clock got fiddled between calls.
1197 # In any case, wait a little while and try again.
1198 time.sleep(0.1)
1199
1200 # It worked or it didn't. If it didn't, assume it's reason #2, and
1201 # let the test pass if they're within half a second of each other.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001202 if today != todayagain:
1203 self.assertAlmostEqual(todayagain, today,
1204 delta=timedelta(seconds=0.5))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001205
1206 def test_weekday(self):
1207 for i in range(7):
1208 # March 4, 2002 is a Monday
1209 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
1210 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
1211 # January 2, 1956 is a Monday
1212 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
1213 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
1214
1215 def test_isocalendar(self):
1216 # Check examples from
1217 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1218 for i in range(7):
1219 d = self.theclass(2003, 12, 22+i)
1220 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
1221 d = self.theclass(2003, 12, 29) + timedelta(i)
1222 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
1223 d = self.theclass(2004, 1, 5+i)
1224 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
1225 d = self.theclass(2009, 12, 21+i)
1226 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
1227 d = self.theclass(2009, 12, 28) + timedelta(i)
1228 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
1229 d = self.theclass(2010, 1, 4+i)
1230 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
1231
1232 def test_iso_long_years(self):
1233 # Calculate long ISO years and compare to table from
1234 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1235 ISO_LONG_YEARS_TABLE = """
1236 4 32 60 88
1237 9 37 65 93
1238 15 43 71 99
1239 20 48 76
1240 26 54 82
1241
1242 105 133 161 189
1243 111 139 167 195
1244 116 144 172
1245 122 150 178
1246 128 156 184
1247
1248 201 229 257 285
1249 207 235 263 291
1250 212 240 268 296
1251 218 246 274
1252 224 252 280
1253
1254 303 331 359 387
1255 308 336 364 392
1256 314 342 370 398
1257 320 348 376
1258 325 353 381
1259 """
1260 iso_long_years = sorted(map(int, ISO_LONG_YEARS_TABLE.split()))
1261 L = []
1262 for i in range(400):
1263 d = self.theclass(2000+i, 12, 31)
1264 d1 = self.theclass(1600+i, 12, 31)
1265 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
1266 if d.isocalendar()[1] == 53:
1267 L.append(i)
1268 self.assertEqual(L, iso_long_years)
1269
1270 def test_isoformat(self):
1271 t = self.theclass(2, 3, 2)
1272 self.assertEqual(t.isoformat(), "0002-03-02")
1273
1274 def test_ctime(self):
1275 t = self.theclass(2002, 3, 2)
1276 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
1277
1278 def test_strftime(self):
1279 t = self.theclass(2005, 3, 2)
1280 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
1281 self.assertEqual(t.strftime(""), "") # SF bug #761337
1282 self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784
1283
1284 self.assertRaises(TypeError, t.strftime) # needs an arg
1285 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
1286 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
1287
1288 # test that unicode input is allowed (issue 2782)
1289 self.assertEqual(t.strftime("%m"), "03")
1290
1291 # A naive object replaces %z and %Z w/ empty strings.
1292 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1293
1294 #make sure that invalid format specifiers are handled correctly
1295 #self.assertRaises(ValueError, t.strftime, "%e")
1296 #self.assertRaises(ValueError, t.strftime, "%")
1297 #self.assertRaises(ValueError, t.strftime, "%#")
1298
1299 #oh well, some systems just ignore those invalid ones.
Martin Panter46f50722016-05-26 05:35:26 +00001300 #at least, exercise them to make sure that no crashes
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001301 #are generated
1302 for f in ["%e", "%", "%#"]:
1303 try:
1304 t.strftime(f)
1305 except ValueError:
1306 pass
1307
1308 #check that this standard extension works
1309 t.strftime("%f")
1310
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001311 def test_format(self):
1312 dt = self.theclass(2007, 9, 10)
1313 self.assertEqual(dt.__format__(''), str(dt))
1314
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02001315 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001316 dt.__format__(123)
1317
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001318 # check that a derived class's __str__() gets called
1319 class A(self.theclass):
1320 def __str__(self):
1321 return 'A'
1322 a = A(2007, 9, 10)
1323 self.assertEqual(a.__format__(''), 'A')
1324
1325 # check that a derived class's strftime gets called
1326 class B(self.theclass):
1327 def strftime(self, format_spec):
1328 return 'B'
1329 b = B(2007, 9, 10)
1330 self.assertEqual(b.__format__(''), str(dt))
1331
1332 for fmt in ["m:%m d:%d y:%y",
1333 "m:%m d:%d y:%y H:%H M:%M S:%S",
1334 "%z %Z",
1335 ]:
1336 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1337 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1338 self.assertEqual(b.__format__(fmt), 'B')
1339
1340 def test_resolution_info(self):
1341 # XXX: Should min and max respect subclassing?
1342 if issubclass(self.theclass, datetime):
1343 expected_class = datetime
1344 else:
1345 expected_class = date
1346 self.assertIsInstance(self.theclass.min, expected_class)
1347 self.assertIsInstance(self.theclass.max, expected_class)
1348 self.assertIsInstance(self.theclass.resolution, timedelta)
1349 self.assertTrue(self.theclass.max > self.theclass.min)
1350
1351 def test_extreme_timedelta(self):
1352 big = self.theclass.max - self.theclass.min
1353 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
1354 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
1355 # n == 315537897599999999 ~= 2**58.13
1356 justasbig = timedelta(0, 0, n)
1357 self.assertEqual(big, justasbig)
1358 self.assertEqual(self.theclass.min + big, self.theclass.max)
1359 self.assertEqual(self.theclass.max - big, self.theclass.min)
1360
1361 def test_timetuple(self):
1362 for i in range(7):
1363 # January 2, 1956 is a Monday (0)
1364 d = self.theclass(1956, 1, 2+i)
1365 t = d.timetuple()
1366 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
1367 # February 1, 1956 is a Wednesday (2)
1368 d = self.theclass(1956, 2, 1+i)
1369 t = d.timetuple()
1370 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
1371 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
1372 # of the year.
1373 d = self.theclass(1956, 3, 1+i)
1374 t = d.timetuple()
1375 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
1376 self.assertEqual(t.tm_year, 1956)
1377 self.assertEqual(t.tm_mon, 3)
1378 self.assertEqual(t.tm_mday, 1+i)
1379 self.assertEqual(t.tm_hour, 0)
1380 self.assertEqual(t.tm_min, 0)
1381 self.assertEqual(t.tm_sec, 0)
1382 self.assertEqual(t.tm_wday, (3+i)%7)
1383 self.assertEqual(t.tm_yday, 61+i)
1384 self.assertEqual(t.tm_isdst, -1)
1385
1386 def test_pickling(self):
1387 args = 6, 7, 23
1388 orig = self.theclass(*args)
1389 for pickler, unpickler, proto in pickle_choices:
1390 green = pickler.dumps(orig, proto)
1391 derived = unpickler.loads(green)
1392 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02001393 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001394
1395 def test_compare(self):
1396 t1 = self.theclass(2, 3, 4)
1397 t2 = self.theclass(2, 3, 4)
1398 self.assertEqual(t1, t2)
1399 self.assertTrue(t1 <= t2)
1400 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001401 self.assertFalse(t1 != t2)
1402 self.assertFalse(t1 < t2)
1403 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001404
1405 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
1406 t2 = self.theclass(*args) # this is larger than t1
1407 self.assertTrue(t1 < t2)
1408 self.assertTrue(t2 > t1)
1409 self.assertTrue(t1 <= t2)
1410 self.assertTrue(t2 >= t1)
1411 self.assertTrue(t1 != t2)
1412 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001413 self.assertFalse(t1 == t2)
1414 self.assertFalse(t2 == t1)
1415 self.assertFalse(t1 > t2)
1416 self.assertFalse(t2 < t1)
1417 self.assertFalse(t1 >= t2)
1418 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001419
1420 for badarg in OTHERSTUFF:
1421 self.assertEqual(t1 == badarg, False)
1422 self.assertEqual(t1 != badarg, True)
1423 self.assertEqual(badarg == t1, False)
1424 self.assertEqual(badarg != t1, True)
1425
1426 self.assertRaises(TypeError, lambda: t1 < badarg)
1427 self.assertRaises(TypeError, lambda: t1 > badarg)
1428 self.assertRaises(TypeError, lambda: t1 >= badarg)
1429 self.assertRaises(TypeError, lambda: badarg <= t1)
1430 self.assertRaises(TypeError, lambda: badarg < t1)
1431 self.assertRaises(TypeError, lambda: badarg > t1)
1432 self.assertRaises(TypeError, lambda: badarg >= t1)
1433
1434 def test_mixed_compare(self):
1435 our = self.theclass(2000, 4, 5)
1436
1437 # Our class can be compared for equality to other classes
1438 self.assertEqual(our == 1, False)
1439 self.assertEqual(1 == our, False)
1440 self.assertEqual(our != 1, True)
1441 self.assertEqual(1 != our, True)
1442
1443 # But the ordering is undefined
1444 self.assertRaises(TypeError, lambda: our < 1)
1445 self.assertRaises(TypeError, lambda: 1 < our)
1446
1447 # Repeat those tests with a different class
1448
1449 class SomeClass:
1450 pass
1451
1452 their = SomeClass()
1453 self.assertEqual(our == their, False)
1454 self.assertEqual(their == our, False)
1455 self.assertEqual(our != their, True)
1456 self.assertEqual(their != our, True)
1457 self.assertRaises(TypeError, lambda: our < their)
1458 self.assertRaises(TypeError, lambda: their < our)
1459
1460 # However, if the other class explicitly defines ordering
1461 # relative to our class, it is allowed to do so
1462
1463 class LargerThanAnything:
1464 def __lt__(self, other):
1465 return False
1466 def __le__(self, other):
1467 return isinstance(other, LargerThanAnything)
1468 def __eq__(self, other):
1469 return isinstance(other, LargerThanAnything)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001470 def __gt__(self, other):
1471 return not isinstance(other, LargerThanAnything)
1472 def __ge__(self, other):
1473 return True
1474
1475 their = LargerThanAnything()
1476 self.assertEqual(our == their, False)
1477 self.assertEqual(their == our, False)
1478 self.assertEqual(our != their, True)
1479 self.assertEqual(their != our, True)
1480 self.assertEqual(our < their, True)
1481 self.assertEqual(their < our, False)
1482
1483 def test_bool(self):
1484 # All dates are considered true.
1485 self.assertTrue(self.theclass.min)
1486 self.assertTrue(self.theclass.max)
1487
Alexander Belopolsky89da3492011-05-02 13:14:24 -04001488 def test_strftime_y2k(self):
1489 for y in (1, 49, 70, 99, 100, 999, 1000, 1970):
Florent Xicluna49ce0682011-11-01 12:56:14 +01001490 d = self.theclass(y, 1, 1)
1491 # Issue 13305: For years < 1000, the value is not always
1492 # padded to 4 digits across platforms. The C standard
1493 # assumes year >= 1900, so it does not specify the number
1494 # of digits.
1495 if d.strftime("%Y") != '%04d' % y:
1496 # Year 42 returns '42', not padded
1497 self.assertEqual(d.strftime("%Y"), '%d' % y)
Victor Stinner15a83e82016-03-12 08:16:48 +01001498 # '0042' is obtained anyway
1499 self.assertEqual(d.strftime("%4Y"), '%04d' % y)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001500
1501 def test_replace(self):
1502 cls = self.theclass
1503 args = [1, 2, 3]
1504 base = cls(*args)
1505 self.assertEqual(base, base.replace())
1506
1507 i = 0
1508 for name, newval in (("year", 2),
1509 ("month", 3),
1510 ("day", 4)):
1511 newargs = args[:]
1512 newargs[i] = newval
1513 expected = cls(*newargs)
1514 got = base.replace(**{name: newval})
1515 self.assertEqual(expected, got)
1516 i += 1
1517
1518 # Out of bounds.
1519 base = cls(2000, 2, 29)
1520 self.assertRaises(ValueError, base.replace, year=2001)
1521
Paul Ganssle191e9932017-11-09 16:34:29 -05001522 def test_subclass_replace(self):
1523 class DateSubclass(self.theclass):
1524 pass
1525
1526 dt = DateSubclass(2012, 1, 1)
1527 self.assertIs(type(dt.replace(year=2013)), DateSubclass)
1528
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001529 def test_subclass_date(self):
1530
1531 class C(self.theclass):
1532 theAnswer = 42
1533
1534 def __new__(cls, *args, **kws):
1535 temp = kws.copy()
1536 extra = temp.pop('extra')
1537 result = self.theclass.__new__(cls, *args, **temp)
1538 result.extra = extra
1539 return result
1540
1541 def newmeth(self, start):
1542 return start + self.year + self.month
1543
1544 args = 2003, 4, 14
1545
1546 dt1 = self.theclass(*args)
1547 dt2 = C(*args, **{'extra': 7})
1548
1549 self.assertEqual(dt2.__class__, C)
1550 self.assertEqual(dt2.theAnswer, 42)
1551 self.assertEqual(dt2.extra, 7)
1552 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1553 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1554
1555 def test_pickling_subclass_date(self):
1556
1557 args = 6, 7, 23
1558 orig = SubclassDate(*args)
1559 for pickler, unpickler, proto in pickle_choices:
1560 green = pickler.dumps(orig, proto)
1561 derived = unpickler.loads(green)
1562 self.assertEqual(orig, derived)
1563
1564 def test_backdoor_resistance(self):
1565 # For fast unpickling, the constructor accepts a pickle byte string.
1566 # This is a low-overhead backdoor. A user can (by intent or
1567 # mistake) pass a string directly, which (if it's the right length)
1568 # will get treated like a pickle, and bypass the normal sanity
1569 # checks in the constructor. This can create insane objects.
1570 # The constructor doesn't want to burn the time to validate all
1571 # fields, but does check the month field. This stops, e.g.,
1572 # datetime.datetime('1995-03-25') from yielding an insane object.
1573 base = b'1995-03-25'
1574 if not issubclass(self.theclass, datetime):
1575 base = base[:4]
1576 for month_byte in b'9', b'\0', b'\r', b'\xff':
1577 self.assertRaises(TypeError, self.theclass,
1578 base[:2] + month_byte + base[3:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001579 if issubclass(self.theclass, datetime):
1580 # Good bytes, but bad tzinfo:
1581 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
1582 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001583
1584 for ord_byte in range(1, 13):
1585 # This shouldn't blow up because of the month byte alone. If
1586 # the implementation changes to do more-careful checking, it may
1587 # blow up because other fields are insane.
1588 self.theclass(base[:2] + bytes([ord_byte]) + base[3:])
1589
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001590 def test_fromisoformat(self):
1591 # Test that isoformat() is reversible
1592 base_dates = [
1593 (1, 1, 1),
1594 (1000, 2, 14),
1595 (1900, 1, 1),
1596 (2000, 2, 29),
1597 (2004, 11, 12),
1598 (2004, 4, 3),
1599 (2017, 5, 30)
1600 ]
1601
1602 for dt_tuple in base_dates:
1603 dt = self.theclass(*dt_tuple)
1604 dt_str = dt.isoformat()
1605 with self.subTest(dt_str=dt_str):
1606 dt_rt = self.theclass.fromisoformat(dt.isoformat())
1607
1608 self.assertEqual(dt, dt_rt)
1609
1610 def test_fromisoformat_subclass(self):
1611 class DateSubclass(self.theclass):
1612 pass
1613
1614 dt = DateSubclass(2014, 12, 14)
1615
1616 dt_rt = DateSubclass.fromisoformat(dt.isoformat())
1617
1618 self.assertIsInstance(dt_rt, DateSubclass)
1619
1620 def test_fromisoformat_fails(self):
1621 # Test that fromisoformat() fails on invalid values
1622 bad_strs = [
1623 '', # Empty string
1624 '009-03-04', # Not 10 characters
1625 '123456789', # Not a date
1626 '200a-12-04', # Invalid character in year
1627 '2009-1a-04', # Invalid character in month
1628 '2009-12-0a', # Invalid character in day
1629 '2009-01-32', # Invalid day
1630 '2009-02-29', # Invalid leap day
1631 '20090228', # Valid ISO8601 output not from isoformat()
1632 ]
1633
1634 for bad_str in bad_strs:
1635 with self.assertRaises(ValueError):
1636 self.theclass.fromisoformat(bad_str)
1637
1638 def test_fromisoformat_fails_typeerror(self):
1639 # Test that fromisoformat fails when passed the wrong type
1640 import io
1641
1642 bad_types = [b'2009-03-01', None, io.StringIO('2009-03-01')]
1643 for bad_type in bad_types:
1644 with self.assertRaises(TypeError):
1645 self.theclass.fromisoformat(bad_type)
1646
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001647#############################################################################
1648# datetime tests
1649
1650class SubclassDatetime(datetime):
1651 sub_var = 1
1652
1653class TestDateTime(TestDate):
1654
1655 theclass = datetime
1656
1657 def test_basic_attributes(self):
1658 dt = self.theclass(2002, 3, 1, 12, 0)
1659 self.assertEqual(dt.year, 2002)
1660 self.assertEqual(dt.month, 3)
1661 self.assertEqual(dt.day, 1)
1662 self.assertEqual(dt.hour, 12)
1663 self.assertEqual(dt.minute, 0)
1664 self.assertEqual(dt.second, 0)
1665 self.assertEqual(dt.microsecond, 0)
1666
1667 def test_basic_attributes_nonzero(self):
1668 # Make sure all attributes are non-zero so bugs in
1669 # bit-shifting access show up.
1670 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1671 self.assertEqual(dt.year, 2002)
1672 self.assertEqual(dt.month, 3)
1673 self.assertEqual(dt.day, 1)
1674 self.assertEqual(dt.hour, 12)
1675 self.assertEqual(dt.minute, 59)
1676 self.assertEqual(dt.second, 59)
1677 self.assertEqual(dt.microsecond, 8000)
1678
1679 def test_roundtrip(self):
1680 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1681 self.theclass.now()):
1682 # Verify dt -> string -> datetime identity.
1683 s = repr(dt)
1684 self.assertTrue(s.startswith('datetime.'))
1685 s = s[9:]
1686 dt2 = eval(s)
1687 self.assertEqual(dt, dt2)
1688
1689 # Verify identity via reconstructing from pieces.
1690 dt2 = self.theclass(dt.year, dt.month, dt.day,
1691 dt.hour, dt.minute, dt.second,
1692 dt.microsecond)
1693 self.assertEqual(dt, dt2)
1694
1695 def test_isoformat(self):
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001696 t = self.theclass(1, 2, 3, 4, 5, 1, 123)
1697 self.assertEqual(t.isoformat(), "0001-02-03T04:05:01.000123")
1698 self.assertEqual(t.isoformat('T'), "0001-02-03T04:05:01.000123")
1699 self.assertEqual(t.isoformat(' '), "0001-02-03 04:05:01.000123")
1700 self.assertEqual(t.isoformat('\x00'), "0001-02-03\x0004:05:01.000123")
1701 self.assertEqual(t.isoformat(timespec='hours'), "0001-02-03T04")
1702 self.assertEqual(t.isoformat(timespec='minutes'), "0001-02-03T04:05")
1703 self.assertEqual(t.isoformat(timespec='seconds'), "0001-02-03T04:05:01")
1704 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1705 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000123")
1706 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01.000123")
1707 self.assertEqual(t.isoformat(sep=' ', timespec='minutes'), "0001-02-03 04:05")
1708 self.assertRaises(ValueError, t.isoformat, timespec='foo')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001709 # str is ISO format with the separator forced to a blank.
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001710 self.assertEqual(str(t), "0001-02-03 04:05:01.000123")
1711
1712 t = self.theclass(1, 2, 3, 4, 5, 1, 999500, tzinfo=timezone.utc)
1713 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999+00:00")
1714
1715 t = self.theclass(1, 2, 3, 4, 5, 1, 999500)
1716 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999")
1717
1718 t = self.theclass(1, 2, 3, 4, 5, 1)
1719 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01")
1720 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1721 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000000")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001722
1723 t = self.theclass(2, 3, 2)
1724 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1725 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1726 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1727 # str is ISO format with the separator forced to a blank.
1728 self.assertEqual(str(t), "0002-03-02 00:00:00")
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001729 # ISO format with timezone
1730 tz = FixedOffset(timedelta(seconds=16), 'XXX')
1731 t = self.theclass(2, 3, 2, tzinfo=tz)
1732 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00+00:00:16")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001733
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001734 def test_isoformat_timezone(self):
1735 tzoffsets = [
1736 ('05:00', timedelta(hours=5)),
1737 ('02:00', timedelta(hours=2)),
1738 ('06:27', timedelta(hours=6, minutes=27)),
1739 ('12:32:30', timedelta(hours=12, minutes=32, seconds=30)),
1740 ('02:04:09.123456', timedelta(hours=2, minutes=4, seconds=9, microseconds=123456))
1741 ]
1742
1743 tzinfos = [
1744 ('', None),
1745 ('+00:00', timezone.utc),
1746 ('+00:00', timezone(timedelta(0))),
1747 ]
1748
1749 tzinfos += [
1750 (prefix + expected, timezone(sign * td))
1751 for expected, td in tzoffsets
1752 for prefix, sign in [('-', -1), ('+', 1)]
1753 ]
1754
1755 dt_base = self.theclass(2016, 4, 1, 12, 37, 9)
1756 exp_base = '2016-04-01T12:37:09'
1757
1758 for exp_tz, tzi in tzinfos:
1759 dt = dt_base.replace(tzinfo=tzi)
1760 exp = exp_base + exp_tz
1761 with self.subTest(tzi=tzi):
1762 assert dt.isoformat() == exp
1763
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001764 def test_format(self):
1765 dt = self.theclass(2007, 9, 10, 4, 5, 1, 123)
1766 self.assertEqual(dt.__format__(''), str(dt))
1767
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02001768 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001769 dt.__format__(123)
1770
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001771 # check that a derived class's __str__() gets called
1772 class A(self.theclass):
1773 def __str__(self):
1774 return 'A'
1775 a = A(2007, 9, 10, 4, 5, 1, 123)
1776 self.assertEqual(a.__format__(''), 'A')
1777
1778 # check that a derived class's strftime gets called
1779 class B(self.theclass):
1780 def strftime(self, format_spec):
1781 return 'B'
1782 b = B(2007, 9, 10, 4, 5, 1, 123)
1783 self.assertEqual(b.__format__(''), str(dt))
1784
1785 for fmt in ["m:%m d:%d y:%y",
1786 "m:%m d:%d y:%y H:%H M:%M S:%S",
1787 "%z %Z",
1788 ]:
1789 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1790 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1791 self.assertEqual(b.__format__(fmt), 'B')
1792
1793 def test_more_ctime(self):
1794 # Test fields that TestDate doesn't touch.
1795 import time
1796
1797 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1798 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1799 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1800 # out. The difference is that t.ctime() produces " 2" for the day,
1801 # but platform ctime() produces "02" for the day. According to
1802 # C99, t.ctime() is correct here.
1803 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1804
1805 # So test a case where that difference doesn't matter.
1806 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1807 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1808
1809 def test_tz_independent_comparing(self):
1810 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1811 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1812 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1813 self.assertEqual(dt1, dt3)
1814 self.assertTrue(dt2 > dt3)
1815
1816 # Make sure comparison doesn't forget microseconds, and isn't done
1817 # via comparing a float timestamp (an IEEE double doesn't have enough
1818 # precision to span microsecond resolution across years 1 thru 9999,
1819 # so comparing via timestamp necessarily calls some distinct values
1820 # equal).
1821 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1822 us = timedelta(microseconds=1)
1823 dt2 = dt1 + us
1824 self.assertEqual(dt2 - dt1, us)
1825 self.assertTrue(dt1 < dt2)
1826
1827 def test_strftime_with_bad_tzname_replace(self):
1828 # verify ok if tzinfo.tzname().replace() returns a non-string
1829 class MyTzInfo(FixedOffset):
1830 def tzname(self, dt):
1831 class MyStr(str):
1832 def replace(self, *args):
1833 return None
1834 return MyStr('name')
1835 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
1836 self.assertRaises(TypeError, t.strftime, '%Z')
1837
1838 def test_bad_constructor_arguments(self):
1839 # bad years
1840 self.theclass(MINYEAR, 1, 1) # no exception
1841 self.theclass(MAXYEAR, 1, 1) # no exception
1842 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1843 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1844 # bad months
1845 self.theclass(2000, 1, 1) # no exception
1846 self.theclass(2000, 12, 1) # no exception
1847 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1848 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1849 # bad days
1850 self.theclass(2000, 2, 29) # no exception
1851 self.theclass(2004, 2, 29) # no exception
1852 self.theclass(2400, 2, 29) # no exception
1853 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1854 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1855 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1856 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1857 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1858 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1859 # bad hours
1860 self.theclass(2000, 1, 31, 0) # no exception
1861 self.theclass(2000, 1, 31, 23) # no exception
1862 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1863 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1864 # bad minutes
1865 self.theclass(2000, 1, 31, 23, 0) # no exception
1866 self.theclass(2000, 1, 31, 23, 59) # no exception
1867 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1868 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1869 # bad seconds
1870 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1871 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1872 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1873 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1874 # bad microseconds
1875 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1876 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1877 self.assertRaises(ValueError, self.theclass,
1878 2000, 1, 31, 23, 59, 59, -1)
1879 self.assertRaises(ValueError, self.theclass,
1880 2000, 1, 31, 23, 59, 59,
1881 1000000)
Alexander Belopolsky47649ab2016-08-08 17:05:40 -04001882 # bad fold
1883 self.assertRaises(ValueError, self.theclass,
1884 2000, 1, 31, fold=-1)
1885 self.assertRaises(ValueError, self.theclass,
1886 2000, 1, 31, fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001887 # Positional fold:
1888 self.assertRaises(TypeError, self.theclass,
1889 2000, 1, 31, 23, 59, 59, 0, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04001890
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001891 def test_hash_equality(self):
1892 d = self.theclass(2000, 12, 31, 23, 30, 17)
1893 e = self.theclass(2000, 12, 31, 23, 30, 17)
1894 self.assertEqual(d, e)
1895 self.assertEqual(hash(d), hash(e))
1896
1897 dic = {d: 1}
1898 dic[e] = 2
1899 self.assertEqual(len(dic), 1)
1900 self.assertEqual(dic[d], 2)
1901 self.assertEqual(dic[e], 2)
1902
1903 d = self.theclass(2001, 1, 1, 0, 5, 17)
1904 e = self.theclass(2001, 1, 1, 0, 5, 17)
1905 self.assertEqual(d, e)
1906 self.assertEqual(hash(d), hash(e))
1907
1908 dic = {d: 1}
1909 dic[e] = 2
1910 self.assertEqual(len(dic), 1)
1911 self.assertEqual(dic[d], 2)
1912 self.assertEqual(dic[e], 2)
1913
1914 def test_computations(self):
1915 a = self.theclass(2002, 1, 31)
1916 b = self.theclass(1956, 1, 31)
1917 diff = a-b
1918 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1919 self.assertEqual(diff.seconds, 0)
1920 self.assertEqual(diff.microseconds, 0)
1921 a = self.theclass(2002, 3, 2, 17, 6)
1922 millisec = timedelta(0, 0, 1000)
1923 hour = timedelta(0, 3600)
1924 day = timedelta(1)
1925 week = timedelta(7)
1926 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1927 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1928 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1929 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1930 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1931 self.assertEqual(a - hour, a + -hour)
1932 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1933 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1934 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1935 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1936 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1937 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1938 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1939 self.assertEqual((a + week) - a, week)
1940 self.assertEqual((a + day) - a, day)
1941 self.assertEqual((a + hour) - a, hour)
1942 self.assertEqual((a + millisec) - a, millisec)
1943 self.assertEqual((a - week) - a, -week)
1944 self.assertEqual((a - day) - a, -day)
1945 self.assertEqual((a - hour) - a, -hour)
1946 self.assertEqual((a - millisec) - a, -millisec)
1947 self.assertEqual(a - (a + week), -week)
1948 self.assertEqual(a - (a + day), -day)
1949 self.assertEqual(a - (a + hour), -hour)
1950 self.assertEqual(a - (a + millisec), -millisec)
1951 self.assertEqual(a - (a - week), week)
1952 self.assertEqual(a - (a - day), day)
1953 self.assertEqual(a - (a - hour), hour)
1954 self.assertEqual(a - (a - millisec), millisec)
1955 self.assertEqual(a + (week + day + hour + millisec),
1956 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1957 self.assertEqual(a + (week + day + hour + millisec),
1958 (((a + week) + day) + hour) + millisec)
1959 self.assertEqual(a - (week + day + hour + millisec),
1960 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1961 self.assertEqual(a - (week + day + hour + millisec),
1962 (((a - week) - day) - hour) - millisec)
1963 # Add/sub ints or floats should be illegal
1964 for i in 1, 1.0:
1965 self.assertRaises(TypeError, lambda: a+i)
1966 self.assertRaises(TypeError, lambda: a-i)
1967 self.assertRaises(TypeError, lambda: i+a)
1968 self.assertRaises(TypeError, lambda: i-a)
1969
1970 # delta - datetime is senseless.
1971 self.assertRaises(TypeError, lambda: day - a)
1972 # mixing datetime and (delta or datetime) via * or // is senseless
1973 self.assertRaises(TypeError, lambda: day * a)
1974 self.assertRaises(TypeError, lambda: a * day)
1975 self.assertRaises(TypeError, lambda: day // a)
1976 self.assertRaises(TypeError, lambda: a // day)
1977 self.assertRaises(TypeError, lambda: a * a)
1978 self.assertRaises(TypeError, lambda: a // a)
1979 # datetime + datetime is senseless
1980 self.assertRaises(TypeError, lambda: a + a)
1981
1982 def test_pickling(self):
1983 args = 6, 7, 23, 20, 59, 1, 64**2
1984 orig = self.theclass(*args)
1985 for pickler, unpickler, proto in pickle_choices:
1986 green = pickler.dumps(orig, proto)
1987 derived = unpickler.loads(green)
1988 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02001989 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001990
1991 def test_more_pickling(self):
1992 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
Serhiy Storchakabad12572014-12-15 14:03:42 +02001993 for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1994 s = pickle.dumps(a, proto)
1995 b = pickle.loads(s)
1996 self.assertEqual(b.year, 2003)
1997 self.assertEqual(b.month, 2)
1998 self.assertEqual(b.day, 7)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001999
2000 def test_pickling_subclass_datetime(self):
2001 args = 6, 7, 23, 20, 59, 1, 64**2
2002 orig = SubclassDatetime(*args)
2003 for pickler, unpickler, proto in pickle_choices:
2004 green = pickler.dumps(orig, proto)
2005 derived = unpickler.loads(green)
2006 self.assertEqual(orig, derived)
2007
2008 def test_more_compare(self):
2009 # The test_compare() inherited from TestDate covers the error cases.
2010 # We just want to test lexicographic ordering on the members datetime
2011 # has that date lacks.
2012 args = [2000, 11, 29, 20, 58, 16, 999998]
2013 t1 = self.theclass(*args)
2014 t2 = self.theclass(*args)
2015 self.assertEqual(t1, t2)
2016 self.assertTrue(t1 <= t2)
2017 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002018 self.assertFalse(t1 != t2)
2019 self.assertFalse(t1 < t2)
2020 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002021
2022 for i in range(len(args)):
2023 newargs = args[:]
2024 newargs[i] = args[i] + 1
2025 t2 = self.theclass(*newargs) # this is larger than t1
2026 self.assertTrue(t1 < t2)
2027 self.assertTrue(t2 > t1)
2028 self.assertTrue(t1 <= t2)
2029 self.assertTrue(t2 >= t1)
2030 self.assertTrue(t1 != t2)
2031 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002032 self.assertFalse(t1 == t2)
2033 self.assertFalse(t2 == t1)
2034 self.assertFalse(t1 > t2)
2035 self.assertFalse(t2 < t1)
2036 self.assertFalse(t1 >= t2)
2037 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002038
2039
2040 # A helper for timestamp constructor tests.
2041 def verify_field_equality(self, expected, got):
2042 self.assertEqual(expected.tm_year, got.year)
2043 self.assertEqual(expected.tm_mon, got.month)
2044 self.assertEqual(expected.tm_mday, got.day)
2045 self.assertEqual(expected.tm_hour, got.hour)
2046 self.assertEqual(expected.tm_min, got.minute)
2047 self.assertEqual(expected.tm_sec, got.second)
2048
2049 def test_fromtimestamp(self):
2050 import time
2051
2052 ts = time.time()
2053 expected = time.localtime(ts)
2054 got = self.theclass.fromtimestamp(ts)
2055 self.verify_field_equality(expected, got)
2056
2057 def test_utcfromtimestamp(self):
2058 import time
2059
2060 ts = time.time()
2061 expected = time.gmtime(ts)
2062 got = self.theclass.utcfromtimestamp(ts)
2063 self.verify_field_equality(expected, got)
2064
Alexander Belopolskya4415142012-06-08 12:33:09 -04002065 # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in
2066 # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0).
2067 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
2068 def test_timestamp_naive(self):
2069 t = self.theclass(1970, 1, 1)
2070 self.assertEqual(t.timestamp(), 18000.0)
2071 t = self.theclass(1970, 1, 1, 1, 2, 3, 4)
2072 self.assertEqual(t.timestamp(),
2073 18000.0 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002074 # Missing hour
2075 t0 = self.theclass(2012, 3, 11, 2, 30)
2076 t1 = t0.replace(fold=1)
2077 self.assertEqual(self.theclass.fromtimestamp(t1.timestamp()),
2078 t0 - timedelta(hours=1))
2079 self.assertEqual(self.theclass.fromtimestamp(t0.timestamp()),
2080 t1 + timedelta(hours=1))
Alexander Belopolskya4415142012-06-08 12:33:09 -04002081 # Ambiguous hour defaults to DST
2082 t = self.theclass(2012, 11, 4, 1, 30)
2083 self.assertEqual(self.theclass.fromtimestamp(t.timestamp()), t)
2084
2085 # Timestamp may raise an overflow error on some platforms
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002086 # XXX: Do we care to support the first and last year?
2087 for t in [self.theclass(2,1,1), self.theclass(9998,12,12)]:
Alexander Belopolskya4415142012-06-08 12:33:09 -04002088 try:
2089 s = t.timestamp()
2090 except OverflowError:
2091 pass
2092 else:
2093 self.assertEqual(self.theclass.fromtimestamp(s), t)
2094
2095 def test_timestamp_aware(self):
2096 t = self.theclass(1970, 1, 1, tzinfo=timezone.utc)
2097 self.assertEqual(t.timestamp(), 0.0)
2098 t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone.utc)
2099 self.assertEqual(t.timestamp(),
2100 3600 + 2*60 + 3 + 4*1e-6)
2101 t = self.theclass(1970, 1, 1, 1, 2, 3, 4,
2102 tzinfo=timezone(timedelta(hours=-5), 'EST'))
2103 self.assertEqual(t.timestamp(),
2104 18000 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002105
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002106 @support.run_with_tz('MSK-03') # Something east of Greenwich
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002107 def test_microsecond_rounding(self):
Victor Stinner5ebfe422015-09-18 14:52:15 +02002108 for fts in [self.theclass.fromtimestamp,
2109 self.theclass.utcfromtimestamp]:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002110 zero = fts(0)
2111 self.assertEqual(zero.second, 0)
2112 self.assertEqual(zero.microsecond, 0)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002113 one = fts(1e-6)
Victor Stinner8050ca92012-03-14 00:17:05 +01002114 try:
2115 minus_one = fts(-1e-6)
2116 except OSError:
2117 # localtime(-1) and gmtime(-1) is not supported on Windows
2118 pass
2119 else:
2120 self.assertEqual(minus_one.second, 59)
2121 self.assertEqual(minus_one.microsecond, 999999)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002122
Victor Stinner8050ca92012-03-14 00:17:05 +01002123 t = fts(-1e-8)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002124 self.assertEqual(t, zero)
Victor Stinner8050ca92012-03-14 00:17:05 +01002125 t = fts(-9e-7)
2126 self.assertEqual(t, minus_one)
2127 t = fts(-1e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002128 self.assertEqual(t, zero)
Victor Stinner8820a352015-09-05 10:50:20 +02002129 t = fts(-1/2**7)
2130 self.assertEqual(t.second, 59)
Victor Stinner7667f582015-09-09 01:02:23 +02002131 self.assertEqual(t.microsecond, 992188)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002132
2133 t = fts(1e-7)
2134 self.assertEqual(t, zero)
2135 t = fts(9e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002136 self.assertEqual(t, one)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002137 t = fts(0.99999949)
2138 self.assertEqual(t.second, 0)
2139 self.assertEqual(t.microsecond, 999999)
2140 t = fts(0.9999999)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002141 self.assertEqual(t.second, 1)
2142 self.assertEqual(t.microsecond, 0)
Victor Stinneradfefa52015-09-04 23:57:25 +02002143 t = fts(1/2**7)
2144 self.assertEqual(t.second, 0)
Victor Stinner7667f582015-09-09 01:02:23 +02002145 self.assertEqual(t.microsecond, 7812)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002146
Victor Stinnerb67f0962017-02-10 10:34:02 +01002147 def test_timestamp_limits(self):
2148 # minimum timestamp
2149 min_dt = self.theclass.min.replace(tzinfo=timezone.utc)
2150 min_ts = min_dt.timestamp()
Victor Stinner6f37e362017-02-10 11:45:14 +01002151 try:
2152 # date 0001-01-01 00:00:00+00:00: timestamp=-62135596800
2153 self.assertEqual(self.theclass.fromtimestamp(min_ts, tz=timezone.utc),
2154 min_dt)
Victor Stinner2a35c932017-02-10 12:37:21 +01002155 except (OverflowError, OSError) as exc:
2156 # the date 0001-01-01 doesn't fit into 32-bit time_t,
2157 # or platform doesn't support such very old date
Victor Stinner6f37e362017-02-10 11:45:14 +01002158 self.skipTest(str(exc))
Victor Stinnerb67f0962017-02-10 10:34:02 +01002159
2160 # maximum timestamp: set seconds to zero to avoid rounding issues
2161 max_dt = self.theclass.max.replace(tzinfo=timezone.utc,
2162 second=0, microsecond=0)
2163 max_ts = max_dt.timestamp()
2164 # date 9999-12-31 23:59:00+00:00: timestamp 253402300740
2165 self.assertEqual(self.theclass.fromtimestamp(max_ts, tz=timezone.utc),
2166 max_dt)
2167
2168 # number of seconds greater than 1 year: make sure that the new date
2169 # is not valid in datetime.datetime limits
2170 delta = 3600 * 24 * 400
2171
2172 # too small
2173 ts = min_ts - delta
2174 # converting a Python int to C time_t can raise a OverflowError,
2175 # especially on 32-bit platforms.
2176 with self.assertRaises((ValueError, OverflowError)):
2177 self.theclass.fromtimestamp(ts)
2178 with self.assertRaises((ValueError, OverflowError)):
2179 self.theclass.utcfromtimestamp(ts)
2180
2181 # too big
2182 ts = max_dt.timestamp() + delta
2183 with self.assertRaises((ValueError, OverflowError)):
2184 self.theclass.fromtimestamp(ts)
2185 with self.assertRaises((ValueError, OverflowError)):
2186 self.theclass.utcfromtimestamp(ts)
2187
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002188 def test_insane_fromtimestamp(self):
2189 # It's possible that some platform maps time_t to double,
2190 # and that this test will fail there. This test should
2191 # exempt such platforms (provided they return reasonable
2192 # results!).
2193 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002194 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002195 insane)
2196
2197 def test_insane_utcfromtimestamp(self):
2198 # It's possible that some platform maps time_t to double,
2199 # and that this test will fail there. This test should
2200 # exempt such platforms (provided they return reasonable
2201 # results!).
2202 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002203 self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002204 insane)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002205
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002206 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2207 def test_negative_float_fromtimestamp(self):
2208 # The result is tz-dependent; at least test that this doesn't
2209 # fail (like it did before bug 1646728 was fixed).
2210 self.theclass.fromtimestamp(-1.05)
2211
2212 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2213 def test_negative_float_utcfromtimestamp(self):
2214 d = self.theclass.utcfromtimestamp(-1.05)
2215 self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
2216
2217 def test_utcnow(self):
2218 import time
2219
2220 # Call it a success if utcnow() and utcfromtimestamp() are within
2221 # a second of each other.
2222 tolerance = timedelta(seconds=1)
2223 for dummy in range(3):
2224 from_now = self.theclass.utcnow()
2225 from_timestamp = self.theclass.utcfromtimestamp(time.time())
2226 if abs(from_timestamp - from_now) <= tolerance:
2227 break
2228 # Else try again a few times.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002229 self.assertLessEqual(abs(from_timestamp - from_now), tolerance)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002230
2231 def test_strptime(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002232 string = '2004-12-01 13:02:47.197'
2233 format = '%Y-%m-%d %H:%M:%S.%f'
2234 expected = _strptime._strptime_datetime(self.theclass, string, format)
2235 got = self.theclass.strptime(string, format)
2236 self.assertEqual(expected, got)
2237 self.assertIs(type(expected), self.theclass)
2238 self.assertIs(type(got), self.theclass)
2239
2240 strptime = self.theclass.strptime
2241 self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
2242 self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
Mario Corchero32318932017-10-26 01:35:41 +01002243 self.assertEqual(
2244 strptime("-00:02:01.000003", "%z").utcoffset(),
2245 -timedelta(minutes=2, seconds=1, microseconds=3)
2246 )
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002247 # Only local timezone and UTC are supported
2248 for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
2249 (-_time.timezone, _time.tzname[0])):
2250 if tzseconds < 0:
2251 sign = '-'
2252 seconds = -tzseconds
2253 else:
2254 sign ='+'
2255 seconds = tzseconds
2256 hours, minutes = divmod(seconds//60, 60)
2257 dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
Martin Panterfca22322015-11-16 09:22:19 +00002258 dt = strptime(dtstr, "%z %Z")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002259 self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
2260 self.assertEqual(dt.tzname(), tzname)
2261 # Can produce inconsistent datetime
2262 dtstr, fmt = "+1234 UTC", "%z %Z"
2263 dt = strptime(dtstr, fmt)
2264 self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
2265 self.assertEqual(dt.tzname(), 'UTC')
2266 # yet will roundtrip
2267 self.assertEqual(dt.strftime(fmt), dtstr)
2268
2269 # Produce naive datetime if no %z is provided
2270 self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
2271
2272 with self.assertRaises(ValueError): strptime("-2400", "%z")
2273 with self.assertRaises(ValueError): strptime("-000", "%z")
2274
2275 def test_more_timetuple(self):
2276 # This tests fields beyond those tested by the TestDate.test_timetuple.
2277 t = self.theclass(2004, 12, 31, 6, 22, 33)
2278 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
2279 self.assertEqual(t.timetuple(),
2280 (t.year, t.month, t.day,
2281 t.hour, t.minute, t.second,
2282 t.weekday(),
2283 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
2284 -1))
2285 tt = t.timetuple()
2286 self.assertEqual(tt.tm_year, t.year)
2287 self.assertEqual(tt.tm_mon, t.month)
2288 self.assertEqual(tt.tm_mday, t.day)
2289 self.assertEqual(tt.tm_hour, t.hour)
2290 self.assertEqual(tt.tm_min, t.minute)
2291 self.assertEqual(tt.tm_sec, t.second)
2292 self.assertEqual(tt.tm_wday, t.weekday())
2293 self.assertEqual(tt.tm_yday, t.toordinal() -
2294 date(t.year, 1, 1).toordinal() + 1)
2295 self.assertEqual(tt.tm_isdst, -1)
2296
2297 def test_more_strftime(self):
2298 # This tests fields beyond those tested by the TestDate.test_strftime.
2299 t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
2300 self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
2301 "12 31 04 000047 33 22 06 366")
Alexander Belopolsky018d3532017-07-31 10:26:50 -04002302 tz = timezone(-timedelta(hours=2, seconds=33, microseconds=123))
2303 t = t.replace(tzinfo=tz)
2304 self.assertEqual(t.strftime("%z"), "-020033.000123")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002305
2306 def test_extract(self):
2307 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2308 self.assertEqual(dt.date(), date(2002, 3, 4))
2309 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2310
2311 def test_combine(self):
2312 d = date(2002, 3, 4)
2313 t = time(18, 45, 3, 1234)
2314 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2315 combine = self.theclass.combine
2316 dt = combine(d, t)
2317 self.assertEqual(dt, expected)
2318
2319 dt = combine(time=t, date=d)
2320 self.assertEqual(dt, expected)
2321
2322 self.assertEqual(d, dt.date())
2323 self.assertEqual(t, dt.time())
2324 self.assertEqual(dt, combine(dt.date(), dt.time()))
2325
2326 self.assertRaises(TypeError, combine) # need an arg
2327 self.assertRaises(TypeError, combine, d) # need two args
2328 self.assertRaises(TypeError, combine, t, d) # args reversed
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002329 self.assertRaises(TypeError, combine, d, t, 1) # wrong tzinfo type
2330 self.assertRaises(TypeError, combine, d, t, 1, 2) # too many args
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002331 self.assertRaises(TypeError, combine, "date", "time") # wrong types
2332 self.assertRaises(TypeError, combine, d, "time") # wrong type
2333 self.assertRaises(TypeError, combine, "date", t) # wrong type
2334
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002335 # tzinfo= argument
2336 dt = combine(d, t, timezone.utc)
2337 self.assertIs(dt.tzinfo, timezone.utc)
2338 dt = combine(d, t, tzinfo=timezone.utc)
2339 self.assertIs(dt.tzinfo, timezone.utc)
2340 t = time()
2341 dt = combine(dt, t)
2342 self.assertEqual(dt.date(), d)
2343 self.assertEqual(dt.time(), t)
2344
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002345 def test_replace(self):
2346 cls = self.theclass
2347 args = [1, 2, 3, 4, 5, 6, 7]
2348 base = cls(*args)
2349 self.assertEqual(base, base.replace())
2350
2351 i = 0
2352 for name, newval in (("year", 2),
2353 ("month", 3),
2354 ("day", 4),
2355 ("hour", 5),
2356 ("minute", 6),
2357 ("second", 7),
2358 ("microsecond", 8)):
2359 newargs = args[:]
2360 newargs[i] = newval
2361 expected = cls(*newargs)
2362 got = base.replace(**{name: newval})
2363 self.assertEqual(expected, got)
2364 i += 1
2365
2366 # Out of bounds.
2367 base = cls(2000, 2, 29)
2368 self.assertRaises(ValueError, base.replace, year=2001)
2369
2370 def test_astimezone(self):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002371 return # The rest is no longer applicable
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002372 # Pretty boring! The TZ test is more interesting here. astimezone()
2373 # simply can't be applied to a naive object.
2374 dt = self.theclass.now()
2375 f = FixedOffset(44, "")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04002376 self.assertRaises(ValueError, dt.astimezone) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002377 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
2378 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
2379 self.assertRaises(ValueError, dt.astimezone, f) # naive
2380 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
2381
2382 class Bogus(tzinfo):
2383 def utcoffset(self, dt): return None
2384 def dst(self, dt): return timedelta(0)
2385 bog = Bogus()
2386 self.assertRaises(ValueError, dt.astimezone, bog) # naive
2387 self.assertRaises(ValueError,
2388 dt.replace(tzinfo=bog).astimezone, f)
2389
2390 class AlsoBogus(tzinfo):
2391 def utcoffset(self, dt): return timedelta(0)
2392 def dst(self, dt): return None
2393 alsobog = AlsoBogus()
2394 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
2395
2396 def test_subclass_datetime(self):
2397
2398 class C(self.theclass):
2399 theAnswer = 42
2400
2401 def __new__(cls, *args, **kws):
2402 temp = kws.copy()
2403 extra = temp.pop('extra')
2404 result = self.theclass.__new__(cls, *args, **temp)
2405 result.extra = extra
2406 return result
2407
2408 def newmeth(self, start):
2409 return start + self.year + self.month + self.second
2410
2411 args = 2003, 4, 14, 12, 13, 41
2412
2413 dt1 = self.theclass(*args)
2414 dt2 = C(*args, **{'extra': 7})
2415
2416 self.assertEqual(dt2.__class__, C)
2417 self.assertEqual(dt2.theAnswer, 42)
2418 self.assertEqual(dt2.extra, 7)
2419 self.assertEqual(dt1.toordinal(), dt2.toordinal())
2420 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
2421 dt1.second - 7)
2422
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002423 def test_fromisoformat_datetime(self):
2424 # Test that isoformat() is reversible
2425 base_dates = [
2426 (1, 1, 1),
2427 (1900, 1, 1),
2428 (2004, 11, 12),
2429 (2017, 5, 30)
2430 ]
2431
2432 base_times = [
2433 (0, 0, 0, 0),
2434 (0, 0, 0, 241000),
2435 (0, 0, 0, 234567),
2436 (12, 30, 45, 234567)
2437 ]
2438
2439 separators = [' ', 'T']
2440
2441 tzinfos = [None, timezone.utc,
2442 timezone(timedelta(hours=-5)),
2443 timezone(timedelta(hours=2))]
2444
2445 dts = [self.theclass(*date_tuple, *time_tuple, tzinfo=tzi)
2446 for date_tuple in base_dates
2447 for time_tuple in base_times
2448 for tzi in tzinfos]
2449
2450 for dt in dts:
2451 for sep in separators:
2452 dtstr = dt.isoformat(sep=sep)
2453
2454 with self.subTest(dtstr=dtstr):
2455 dt_rt = self.theclass.fromisoformat(dtstr)
2456 self.assertEqual(dt, dt_rt)
2457
2458 def test_fromisoformat_timezone(self):
2459 base_dt = self.theclass(2014, 12, 30, 12, 30, 45, 217456)
2460
2461 tzoffsets = [
2462 timedelta(hours=5), timedelta(hours=2),
2463 timedelta(hours=6, minutes=27),
2464 timedelta(hours=12, minutes=32, seconds=30),
2465 timedelta(hours=2, minutes=4, seconds=9, microseconds=123456)
2466 ]
2467
2468 tzoffsets += [-1 * td for td in tzoffsets]
2469
2470 tzinfos = [None, timezone.utc,
2471 timezone(timedelta(hours=0))]
2472
2473 tzinfos += [timezone(td) for td in tzoffsets]
2474
2475 for tzi in tzinfos:
2476 dt = base_dt.replace(tzinfo=tzi)
2477 dtstr = dt.isoformat()
2478
2479 with self.subTest(tstr=dtstr):
2480 dt_rt = self.theclass.fromisoformat(dtstr)
2481 assert dt == dt_rt, dt_rt
2482
2483 def test_fromisoformat_separators(self):
2484 separators = [
2485 ' ', 'T', '\u007f', # 1-bit widths
2486 '\u0080', 'ʁ', # 2-bit widths
2487 'ᛇ', '時', # 3-bit widths
2488 '🐍' # 4-bit widths
2489 ]
2490
2491 for sep in separators:
2492 dt = self.theclass(2018, 1, 31, 23, 59, 47, 124789)
2493 dtstr = dt.isoformat(sep=sep)
2494
2495 with self.subTest(dtstr=dtstr):
2496 dt_rt = self.theclass.fromisoformat(dtstr)
2497 self.assertEqual(dt, dt_rt)
2498
2499 def test_fromisoformat_ambiguous(self):
2500 # Test strings like 2018-01-31+12:15 (where +12:15 is not a time zone)
2501 separators = ['+', '-']
2502 for sep in separators:
2503 dt = self.theclass(2018, 1, 31, 12, 15)
2504 dtstr = dt.isoformat(sep=sep)
2505
2506 with self.subTest(dtstr=dtstr):
2507 dt_rt = self.theclass.fromisoformat(dtstr)
2508 self.assertEqual(dt, dt_rt)
2509
2510 def test_fromisoformat_timespecs(self):
2511 datetime_bases = [
2512 (2009, 12, 4, 8, 17, 45, 123456),
2513 (2009, 12, 4, 8, 17, 45, 0)]
2514
2515 tzinfos = [None, timezone.utc,
2516 timezone(timedelta(hours=-5)),
2517 timezone(timedelta(hours=2)),
2518 timezone(timedelta(hours=6, minutes=27))]
2519
2520 timespecs = ['hours', 'minutes', 'seconds',
2521 'milliseconds', 'microseconds']
2522
2523 for ip, ts in enumerate(timespecs):
2524 for tzi in tzinfos:
2525 for dt_tuple in datetime_bases:
2526 if ts == 'milliseconds':
2527 new_microseconds = 1000 * (dt_tuple[6] // 1000)
2528 dt_tuple = dt_tuple[0:6] + (new_microseconds,)
2529
2530 dt = self.theclass(*(dt_tuple[0:(4 + ip)]), tzinfo=tzi)
2531 dtstr = dt.isoformat(timespec=ts)
2532 with self.subTest(dtstr=dtstr):
2533 dt_rt = self.theclass.fromisoformat(dtstr)
2534 self.assertEqual(dt, dt_rt)
2535
2536 def test_fromisoformat_fails_datetime(self):
2537 # Test that fromisoformat() fails on invalid values
2538 bad_strs = [
2539 '', # Empty string
2540 '2009.04-19T03', # Wrong first separator
2541 '2009-04.19T03', # Wrong second separator
2542 '2009-04-19T0a', # Invalid hours
2543 '2009-04-19T03:1a:45', # Invalid minutes
2544 '2009-04-19T03:15:4a', # Invalid seconds
2545 '2009-04-19T03;15:45', # Bad first time separator
2546 '2009-04-19T03:15;45', # Bad second time separator
2547 '2009-04-19T03:15:4500:00', # Bad time zone separator
2548 '2009-04-19T03:15:45.2345', # Too many digits for milliseconds
2549 '2009-04-19T03:15:45.1234567', # Too many digits for microseconds
2550 '2009-04-19T03:15:45.123456+24:30', # Invalid time zone offset
2551 '2009-04-19T03:15:45.123456-24:30', # Invalid negative offset
2552 '2009-04-10ᛇᛇᛇᛇᛇ12:15', # Too many unicode separators
2553 '2009-04-19T1', # Incomplete hours
2554 '2009-04-19T12:3', # Incomplete minutes
2555 '2009-04-19T12:30:4', # Incomplete seconds
2556 '2009-04-19T12:', # Ends with time separator
2557 '2009-04-19T12:30:', # Ends with time separator
2558 '2009-04-19T12:30:45.', # Ends with time separator
2559 '2009-04-19T12:30:45.123456+', # Ends with timzone separator
2560 '2009-04-19T12:30:45.123456-', # Ends with timzone separator
2561 '2009-04-19T12:30:45.123456-05:00a', # Extra text
2562 '2009-04-19T12:30:45.123-05:00a', # Extra text
2563 '2009-04-19T12:30:45-05:00a', # Extra text
2564 ]
2565
2566 for bad_str in bad_strs:
2567 with self.subTest(bad_str=bad_str):
2568 with self.assertRaises(ValueError):
2569 self.theclass.fromisoformat(bad_str)
2570
2571 def test_fromisoformat_utc(self):
2572 dt_str = '2014-04-19T13:21:13+00:00'
2573 dt = self.theclass.fromisoformat(dt_str)
2574
2575 self.assertIs(dt.tzinfo, timezone.utc)
2576
2577 def test_fromisoformat_subclass(self):
2578 class DateTimeSubclass(self.theclass):
2579 pass
2580
2581 dt = DateTimeSubclass(2014, 12, 14, 9, 30, 45, 457390,
2582 tzinfo=timezone(timedelta(hours=10, minutes=45)))
2583
2584 dt_rt = DateTimeSubclass.fromisoformat(dt.isoformat())
2585
2586 self.assertEqual(dt, dt_rt)
2587 self.assertIsInstance(dt_rt, DateTimeSubclass)
2588
2589
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002590class TestSubclassDateTime(TestDateTime):
2591 theclass = SubclassDatetime
2592 # Override tests not designed for subclass
Zachary Ware9fe6d862013-12-08 00:20:35 -06002593 @unittest.skip('not appropriate for subclasses')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002594 def test_roundtrip(self):
2595 pass
2596
2597class SubclassTime(time):
2598 sub_var = 1
2599
2600class TestTime(HarmlessMixedComparison, unittest.TestCase):
2601
2602 theclass = time
2603
2604 def test_basic_attributes(self):
2605 t = self.theclass(12, 0)
2606 self.assertEqual(t.hour, 12)
2607 self.assertEqual(t.minute, 0)
2608 self.assertEqual(t.second, 0)
2609 self.assertEqual(t.microsecond, 0)
2610
2611 def test_basic_attributes_nonzero(self):
2612 # Make sure all attributes are non-zero so bugs in
2613 # bit-shifting access show up.
2614 t = self.theclass(12, 59, 59, 8000)
2615 self.assertEqual(t.hour, 12)
2616 self.assertEqual(t.minute, 59)
2617 self.assertEqual(t.second, 59)
2618 self.assertEqual(t.microsecond, 8000)
2619
2620 def test_roundtrip(self):
2621 t = self.theclass(1, 2, 3, 4)
2622
2623 # Verify t -> string -> time identity.
2624 s = repr(t)
2625 self.assertTrue(s.startswith('datetime.'))
2626 s = s[9:]
2627 t2 = eval(s)
2628 self.assertEqual(t, t2)
2629
2630 # Verify identity via reconstructing from pieces.
2631 t2 = self.theclass(t.hour, t.minute, t.second,
2632 t.microsecond)
2633 self.assertEqual(t, t2)
2634
2635 def test_comparing(self):
2636 args = [1, 2, 3, 4]
2637 t1 = self.theclass(*args)
2638 t2 = self.theclass(*args)
2639 self.assertEqual(t1, t2)
2640 self.assertTrue(t1 <= t2)
2641 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002642 self.assertFalse(t1 != t2)
2643 self.assertFalse(t1 < t2)
2644 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002645
2646 for i in range(len(args)):
2647 newargs = args[:]
2648 newargs[i] = args[i] + 1
2649 t2 = self.theclass(*newargs) # this is larger than t1
2650 self.assertTrue(t1 < t2)
2651 self.assertTrue(t2 > t1)
2652 self.assertTrue(t1 <= t2)
2653 self.assertTrue(t2 >= t1)
2654 self.assertTrue(t1 != t2)
2655 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002656 self.assertFalse(t1 == t2)
2657 self.assertFalse(t2 == t1)
2658 self.assertFalse(t1 > t2)
2659 self.assertFalse(t2 < t1)
2660 self.assertFalse(t1 >= t2)
2661 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002662
2663 for badarg in OTHERSTUFF:
2664 self.assertEqual(t1 == badarg, False)
2665 self.assertEqual(t1 != badarg, True)
2666 self.assertEqual(badarg == t1, False)
2667 self.assertEqual(badarg != t1, True)
2668
2669 self.assertRaises(TypeError, lambda: t1 <= badarg)
2670 self.assertRaises(TypeError, lambda: t1 < badarg)
2671 self.assertRaises(TypeError, lambda: t1 > badarg)
2672 self.assertRaises(TypeError, lambda: t1 >= badarg)
2673 self.assertRaises(TypeError, lambda: badarg <= t1)
2674 self.assertRaises(TypeError, lambda: badarg < t1)
2675 self.assertRaises(TypeError, lambda: badarg > t1)
2676 self.assertRaises(TypeError, lambda: badarg >= t1)
2677
2678 def test_bad_constructor_arguments(self):
2679 # bad hours
2680 self.theclass(0, 0) # no exception
2681 self.theclass(23, 0) # no exception
2682 self.assertRaises(ValueError, self.theclass, -1, 0)
2683 self.assertRaises(ValueError, self.theclass, 24, 0)
2684 # bad minutes
2685 self.theclass(23, 0) # no exception
2686 self.theclass(23, 59) # no exception
2687 self.assertRaises(ValueError, self.theclass, 23, -1)
2688 self.assertRaises(ValueError, self.theclass, 23, 60)
2689 # bad seconds
2690 self.theclass(23, 59, 0) # no exception
2691 self.theclass(23, 59, 59) # no exception
2692 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
2693 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
2694 # bad microseconds
2695 self.theclass(23, 59, 59, 0) # no exception
2696 self.theclass(23, 59, 59, 999999) # no exception
2697 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
2698 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
2699
2700 def test_hash_equality(self):
2701 d = self.theclass(23, 30, 17)
2702 e = self.theclass(23, 30, 17)
2703 self.assertEqual(d, e)
2704 self.assertEqual(hash(d), hash(e))
2705
2706 dic = {d: 1}
2707 dic[e] = 2
2708 self.assertEqual(len(dic), 1)
2709 self.assertEqual(dic[d], 2)
2710 self.assertEqual(dic[e], 2)
2711
2712 d = self.theclass(0, 5, 17)
2713 e = self.theclass(0, 5, 17)
2714 self.assertEqual(d, e)
2715 self.assertEqual(hash(d), hash(e))
2716
2717 dic = {d: 1}
2718 dic[e] = 2
2719 self.assertEqual(len(dic), 1)
2720 self.assertEqual(dic[d], 2)
2721 self.assertEqual(dic[e], 2)
2722
2723 def test_isoformat(self):
2724 t = self.theclass(4, 5, 1, 123)
2725 self.assertEqual(t.isoformat(), "04:05:01.000123")
2726 self.assertEqual(t.isoformat(), str(t))
2727
2728 t = self.theclass()
2729 self.assertEqual(t.isoformat(), "00:00:00")
2730 self.assertEqual(t.isoformat(), str(t))
2731
2732 t = self.theclass(microsecond=1)
2733 self.assertEqual(t.isoformat(), "00:00:00.000001")
2734 self.assertEqual(t.isoformat(), str(t))
2735
2736 t = self.theclass(microsecond=10)
2737 self.assertEqual(t.isoformat(), "00:00:00.000010")
2738 self.assertEqual(t.isoformat(), str(t))
2739
2740 t = self.theclass(microsecond=100)
2741 self.assertEqual(t.isoformat(), "00:00:00.000100")
2742 self.assertEqual(t.isoformat(), str(t))
2743
2744 t = self.theclass(microsecond=1000)
2745 self.assertEqual(t.isoformat(), "00:00:00.001000")
2746 self.assertEqual(t.isoformat(), str(t))
2747
2748 t = self.theclass(microsecond=10000)
2749 self.assertEqual(t.isoformat(), "00:00:00.010000")
2750 self.assertEqual(t.isoformat(), str(t))
2751
2752 t = self.theclass(microsecond=100000)
2753 self.assertEqual(t.isoformat(), "00:00:00.100000")
2754 self.assertEqual(t.isoformat(), str(t))
2755
Alexander Belopolskya2998a62016-03-06 14:58:43 -05002756 t = self.theclass(hour=12, minute=34, second=56, microsecond=123456)
2757 self.assertEqual(t.isoformat(timespec='hours'), "12")
2758 self.assertEqual(t.isoformat(timespec='minutes'), "12:34")
2759 self.assertEqual(t.isoformat(timespec='seconds'), "12:34:56")
2760 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.123")
2761 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.123456")
2762 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56.123456")
2763 self.assertRaises(ValueError, t.isoformat, timespec='monkey')
2764
2765 t = self.theclass(hour=12, minute=34, second=56, microsecond=999500)
2766 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.999")
2767
2768 t = self.theclass(hour=12, minute=34, second=56, microsecond=0)
2769 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.000")
2770 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.000000")
2771 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56")
2772
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002773 def test_isoformat_timezone(self):
2774 tzoffsets = [
2775 ('05:00', timedelta(hours=5)),
2776 ('02:00', timedelta(hours=2)),
2777 ('06:27', timedelta(hours=6, minutes=27)),
2778 ('12:32:30', timedelta(hours=12, minutes=32, seconds=30)),
2779 ('02:04:09.123456', timedelta(hours=2, minutes=4, seconds=9, microseconds=123456))
2780 ]
2781
2782 tzinfos = [
2783 ('', None),
2784 ('+00:00', timezone.utc),
2785 ('+00:00', timezone(timedelta(0))),
2786 ]
2787
2788 tzinfos += [
2789 (prefix + expected, timezone(sign * td))
2790 for expected, td in tzoffsets
2791 for prefix, sign in [('-', -1), ('+', 1)]
2792 ]
2793
2794 t_base = self.theclass(12, 37, 9)
2795 exp_base = '12:37:09'
2796
2797 for exp_tz, tzi in tzinfos:
2798 t = t_base.replace(tzinfo=tzi)
2799 exp = exp_base + exp_tz
2800 with self.subTest(tzi=tzi):
2801 assert t.isoformat() == exp
2802
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002803 def test_1653736(self):
2804 # verify it doesn't accept extra keyword arguments
2805 t = self.theclass(second=1)
2806 self.assertRaises(TypeError, t.isoformat, foo=3)
2807
2808 def test_strftime(self):
2809 t = self.theclass(1, 2, 3, 4)
2810 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
2811 # A naive object replaces %z and %Z with empty strings.
2812 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
2813
2814 def test_format(self):
2815 t = self.theclass(1, 2, 3, 4)
2816 self.assertEqual(t.__format__(''), str(t))
2817
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02002818 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002819 t.__format__(123)
2820
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002821 # check that a derived class's __str__() gets called
2822 class A(self.theclass):
2823 def __str__(self):
2824 return 'A'
2825 a = A(1, 2, 3, 4)
2826 self.assertEqual(a.__format__(''), 'A')
2827
2828 # check that a derived class's strftime gets called
2829 class B(self.theclass):
2830 def strftime(self, format_spec):
2831 return 'B'
2832 b = B(1, 2, 3, 4)
2833 self.assertEqual(b.__format__(''), str(t))
2834
2835 for fmt in ['%H %M %S',
2836 ]:
2837 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
2838 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
2839 self.assertEqual(b.__format__(fmt), 'B')
2840
2841 def test_str(self):
2842 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
2843 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
2844 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
2845 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
2846 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
2847
2848 def test_repr(self):
2849 name = 'datetime.' + self.theclass.__name__
2850 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
2851 "%s(1, 2, 3, 4)" % name)
2852 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
2853 "%s(10, 2, 3, 4000)" % name)
2854 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
2855 "%s(0, 2, 3, 400000)" % name)
2856 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
2857 "%s(12, 2, 3)" % name)
2858 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
2859 "%s(23, 15)" % name)
2860
2861 def test_resolution_info(self):
2862 self.assertIsInstance(self.theclass.min, self.theclass)
2863 self.assertIsInstance(self.theclass.max, self.theclass)
2864 self.assertIsInstance(self.theclass.resolution, timedelta)
2865 self.assertTrue(self.theclass.max > self.theclass.min)
2866
2867 def test_pickling(self):
2868 args = 20, 59, 16, 64**2
2869 orig = self.theclass(*args)
2870 for pickler, unpickler, proto in pickle_choices:
2871 green = pickler.dumps(orig, proto)
2872 derived = unpickler.loads(green)
2873 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02002874 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002875
2876 def test_pickling_subclass_time(self):
2877 args = 20, 59, 16, 64**2
2878 orig = SubclassTime(*args)
2879 for pickler, unpickler, proto in pickle_choices:
2880 green = pickler.dumps(orig, proto)
2881 derived = unpickler.loads(green)
2882 self.assertEqual(orig, derived)
2883
2884 def test_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002885 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002886 cls = self.theclass
2887 self.assertTrue(cls(1))
2888 self.assertTrue(cls(0, 1))
2889 self.assertTrue(cls(0, 0, 1))
2890 self.assertTrue(cls(0, 0, 0, 1))
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002891 self.assertTrue(cls(0))
2892 self.assertTrue(cls())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002893
2894 def test_replace(self):
2895 cls = self.theclass
2896 args = [1, 2, 3, 4]
2897 base = cls(*args)
2898 self.assertEqual(base, base.replace())
2899
2900 i = 0
2901 for name, newval in (("hour", 5),
2902 ("minute", 6),
2903 ("second", 7),
2904 ("microsecond", 8)):
2905 newargs = args[:]
2906 newargs[i] = newval
2907 expected = cls(*newargs)
2908 got = base.replace(**{name: newval})
2909 self.assertEqual(expected, got)
2910 i += 1
2911
2912 # Out of bounds.
2913 base = cls(1)
2914 self.assertRaises(ValueError, base.replace, hour=24)
2915 self.assertRaises(ValueError, base.replace, minute=-1)
2916 self.assertRaises(ValueError, base.replace, second=100)
2917 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2918
Paul Ganssle191e9932017-11-09 16:34:29 -05002919 def test_subclass_replace(self):
2920 class TimeSubclass(self.theclass):
2921 pass
2922
2923 ctime = TimeSubclass(12, 30)
2924 self.assertIs(type(ctime.replace(hour=10)), TimeSubclass)
2925
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002926 def test_subclass_time(self):
2927
2928 class C(self.theclass):
2929 theAnswer = 42
2930
2931 def __new__(cls, *args, **kws):
2932 temp = kws.copy()
2933 extra = temp.pop('extra')
2934 result = self.theclass.__new__(cls, *args, **temp)
2935 result.extra = extra
2936 return result
2937
2938 def newmeth(self, start):
2939 return start + self.hour + self.second
2940
2941 args = 4, 5, 6
2942
2943 dt1 = self.theclass(*args)
2944 dt2 = C(*args, **{'extra': 7})
2945
2946 self.assertEqual(dt2.__class__, C)
2947 self.assertEqual(dt2.theAnswer, 42)
2948 self.assertEqual(dt2.extra, 7)
2949 self.assertEqual(dt1.isoformat(), dt2.isoformat())
2950 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2951
2952 def test_backdoor_resistance(self):
2953 # see TestDate.test_backdoor_resistance().
2954 base = '2:59.0'
2955 for hour_byte in ' ', '9', chr(24), '\xff':
2956 self.assertRaises(TypeError, self.theclass,
2957 hour_byte + base[1:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002958 # Good bytes, but bad tzinfo:
2959 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
2960 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002961
2962# A mixin for classes with a tzinfo= argument. Subclasses must define
Martin Panter46f50722016-05-26 05:35:26 +00002963# theclass as a class attribute, and theclass(1, 1, 1, tzinfo=whatever)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002964# must be legit (which is true for time and datetime).
2965class TZInfoBase:
2966
2967 def test_argument_passing(self):
2968 cls = self.theclass
2969 # A datetime passes itself on, a time passes None.
2970 class introspective(tzinfo):
2971 def tzname(self, dt): return dt and "real" or "none"
2972 def utcoffset(self, dt):
2973 return timedelta(minutes = dt and 42 or -42)
2974 dst = utcoffset
2975
2976 obj = cls(1, 2, 3, tzinfo=introspective())
2977
2978 expected = cls is time and "none" or "real"
2979 self.assertEqual(obj.tzname(), expected)
2980
2981 expected = timedelta(minutes=(cls is time and -42 or 42))
2982 self.assertEqual(obj.utcoffset(), expected)
2983 self.assertEqual(obj.dst(), expected)
2984
2985 def test_bad_tzinfo_classes(self):
2986 cls = self.theclass
2987 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
2988
2989 class NiceTry(object):
2990 def __init__(self): pass
2991 def utcoffset(self, dt): pass
2992 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
2993
2994 class BetterTry(tzinfo):
2995 def __init__(self): pass
2996 def utcoffset(self, dt): pass
2997 b = BetterTry()
2998 t = cls(1, 1, 1, tzinfo=b)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002999 self.assertIs(t.tzinfo, b)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003000
3001 def test_utc_offset_out_of_bounds(self):
3002 class Edgy(tzinfo):
3003 def __init__(self, offset):
3004 self.offset = timedelta(minutes=offset)
3005 def utcoffset(self, dt):
3006 return self.offset
3007
3008 cls = self.theclass
3009 for offset, legit in ((-1440, False),
3010 (-1439, True),
3011 (1439, True),
3012 (1440, False)):
3013 if cls is time:
3014 t = cls(1, 2, 3, tzinfo=Edgy(offset))
3015 elif cls is datetime:
3016 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
3017 else:
3018 assert 0, "impossible"
3019 if legit:
3020 aofs = abs(offset)
3021 h, m = divmod(aofs, 60)
3022 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
3023 if isinstance(t, datetime):
3024 t = t.timetz()
3025 self.assertEqual(str(t), "01:02:03" + tag)
3026 else:
3027 self.assertRaises(ValueError, str, t)
3028
3029 def test_tzinfo_classes(self):
3030 cls = self.theclass
3031 class C1(tzinfo):
3032 def utcoffset(self, dt): return None
3033 def dst(self, dt): return None
3034 def tzname(self, dt): return None
3035 for t in (cls(1, 1, 1),
3036 cls(1, 1, 1, tzinfo=None),
3037 cls(1, 1, 1, tzinfo=C1())):
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003038 self.assertIsNone(t.utcoffset())
3039 self.assertIsNone(t.dst())
3040 self.assertIsNone(t.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003041
3042 class C3(tzinfo):
3043 def utcoffset(self, dt): return timedelta(minutes=-1439)
3044 def dst(self, dt): return timedelta(minutes=1439)
3045 def tzname(self, dt): return "aname"
3046 t = cls(1, 1, 1, tzinfo=C3())
3047 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
3048 self.assertEqual(t.dst(), timedelta(minutes=1439))
3049 self.assertEqual(t.tzname(), "aname")
3050
3051 # Wrong types.
3052 class C4(tzinfo):
3053 def utcoffset(self, dt): return "aname"
3054 def dst(self, dt): return 7
3055 def tzname(self, dt): return 0
3056 t = cls(1, 1, 1, tzinfo=C4())
3057 self.assertRaises(TypeError, t.utcoffset)
3058 self.assertRaises(TypeError, t.dst)
3059 self.assertRaises(TypeError, t.tzname)
3060
3061 # Offset out of range.
3062 class C6(tzinfo):
3063 def utcoffset(self, dt): return timedelta(hours=-24)
3064 def dst(self, dt): return timedelta(hours=24)
3065 t = cls(1, 1, 1, tzinfo=C6())
3066 self.assertRaises(ValueError, t.utcoffset)
3067 self.assertRaises(ValueError, t.dst)
3068
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04003069 # Not a whole number of seconds.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003070 class C7(tzinfo):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04003071 def utcoffset(self, dt): return timedelta(microseconds=61)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003072 def dst(self, dt): return timedelta(microseconds=-81)
3073 t = cls(1, 1, 1, tzinfo=C7())
Alexander Belopolsky018d3532017-07-31 10:26:50 -04003074 self.assertEqual(t.utcoffset(), timedelta(microseconds=61))
3075 self.assertEqual(t.dst(), timedelta(microseconds=-81))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003076
3077 def test_aware_compare(self):
3078 cls = self.theclass
3079
3080 # Ensure that utcoffset() gets ignored if the comparands have
3081 # the same tzinfo member.
3082 class OperandDependentOffset(tzinfo):
3083 def utcoffset(self, t):
3084 if t.minute < 10:
3085 # d0 and d1 equal after adjustment
3086 return timedelta(minutes=t.minute)
3087 else:
3088 # d2 off in the weeds
3089 return timedelta(minutes=59)
3090
3091 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
3092 d0 = base.replace(minute=3)
3093 d1 = base.replace(minute=9)
3094 d2 = base.replace(minute=11)
3095 for x in d0, d1, d2:
3096 for y in d0, d1, d2:
3097 for op in lt, le, gt, ge, eq, ne:
3098 got = op(x, y)
3099 expected = op(x.minute, y.minute)
3100 self.assertEqual(got, expected)
3101
3102 # However, if they're different members, uctoffset is not ignored.
3103 # Note that a time can't actually have an operand-depedent offset,
3104 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
3105 # so skip this test for time.
3106 if cls is not time:
3107 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3108 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3109 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3110 for x in d0, d1, d2:
3111 for y in d0, d1, d2:
3112 got = (x > y) - (x < y)
3113 if (x is d0 or x is d1) and (y is d0 or y is d1):
3114 expected = 0
3115 elif x is y is d2:
3116 expected = 0
3117 elif x is d2:
3118 expected = -1
3119 else:
3120 assert y is d2
3121 expected = 1
3122 self.assertEqual(got, expected)
3123
3124
3125# Testing time objects with a non-None tzinfo.
3126class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
3127 theclass = time
3128
3129 def test_empty(self):
3130 t = self.theclass()
3131 self.assertEqual(t.hour, 0)
3132 self.assertEqual(t.minute, 0)
3133 self.assertEqual(t.second, 0)
3134 self.assertEqual(t.microsecond, 0)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003135 self.assertIsNone(t.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003136
3137 def test_zones(self):
3138 est = FixedOffset(-300, "EST", 1)
3139 utc = FixedOffset(0, "UTC", -2)
3140 met = FixedOffset(60, "MET", 3)
3141 t1 = time( 7, 47, tzinfo=est)
3142 t2 = time(12, 47, tzinfo=utc)
3143 t3 = time(13, 47, tzinfo=met)
3144 t4 = time(microsecond=40)
3145 t5 = time(microsecond=40, tzinfo=utc)
3146
3147 self.assertEqual(t1.tzinfo, est)
3148 self.assertEqual(t2.tzinfo, utc)
3149 self.assertEqual(t3.tzinfo, met)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003150 self.assertIsNone(t4.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003151 self.assertEqual(t5.tzinfo, utc)
3152
3153 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
3154 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
3155 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003156 self.assertIsNone(t4.utcoffset())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003157 self.assertRaises(TypeError, t1.utcoffset, "no args")
3158
3159 self.assertEqual(t1.tzname(), "EST")
3160 self.assertEqual(t2.tzname(), "UTC")
3161 self.assertEqual(t3.tzname(), "MET")
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003162 self.assertIsNone(t4.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003163 self.assertRaises(TypeError, t1.tzname, "no args")
3164
3165 self.assertEqual(t1.dst(), timedelta(minutes=1))
3166 self.assertEqual(t2.dst(), timedelta(minutes=-2))
3167 self.assertEqual(t3.dst(), timedelta(minutes=3))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003168 self.assertIsNone(t4.dst())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003169 self.assertRaises(TypeError, t1.dst, "no args")
3170
3171 self.assertEqual(hash(t1), hash(t2))
3172 self.assertEqual(hash(t1), hash(t3))
3173 self.assertEqual(hash(t2), hash(t3))
3174
3175 self.assertEqual(t1, t2)
3176 self.assertEqual(t1, t3)
3177 self.assertEqual(t2, t3)
Alexander Belopolsky08313822012-06-15 20:19:47 -04003178 self.assertNotEqual(t4, t5) # mixed tz-aware & naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003179 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
3180 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
3181
3182 self.assertEqual(str(t1), "07:47:00-05:00")
3183 self.assertEqual(str(t2), "12:47:00+00:00")
3184 self.assertEqual(str(t3), "13:47:00+01:00")
3185 self.assertEqual(str(t4), "00:00:00.000040")
3186 self.assertEqual(str(t5), "00:00:00.000040+00:00")
3187
3188 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
3189 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
3190 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
3191 self.assertEqual(t4.isoformat(), "00:00:00.000040")
3192 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
3193
3194 d = 'datetime.time'
3195 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
3196 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
3197 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
3198 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
3199 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
3200
3201 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
3202 "07:47:00 %Z=EST %z=-0500")
3203 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
3204 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
3205
3206 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
3207 t1 = time(23, 59, tzinfo=yuck)
3208 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
3209 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
3210
3211 # Check that an invalid tzname result raises an exception.
3212 class Badtzname(tzinfo):
Alexander Belopolskye239d232010-12-08 23:31:48 +00003213 tz = 42
3214 def tzname(self, dt): return self.tz
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003215 t = time(2, 3, 4, tzinfo=Badtzname())
3216 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
3217 self.assertRaises(TypeError, t.strftime, "%Z")
3218
Alexander Belopolskye239d232010-12-08 23:31:48 +00003219 # Issue #6697:
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +02003220 if '_Fast' in self.__class__.__name__:
Alexander Belopolskye239d232010-12-08 23:31:48 +00003221 Badtzname.tz = '\ud800'
3222 self.assertRaises(ValueError, t.strftime, "%Z")
3223
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003224 def test_hash_edge_cases(self):
3225 # Offsets that overflow a basic time.
3226 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
3227 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
3228 self.assertEqual(hash(t1), hash(t2))
3229
3230 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
3231 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
3232 self.assertEqual(hash(t1), hash(t2))
3233
3234 def test_pickling(self):
3235 # Try one without a tzinfo.
3236 args = 20, 59, 16, 64**2
3237 orig = self.theclass(*args)
3238 for pickler, unpickler, proto in pickle_choices:
3239 green = pickler.dumps(orig, proto)
3240 derived = unpickler.loads(green)
3241 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003242 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003243
3244 # Try one with a tzinfo.
3245 tinfo = PicklableFixedOffset(-300, 'cookie')
3246 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
3247 for pickler, unpickler, proto in pickle_choices:
3248 green = pickler.dumps(orig, proto)
3249 derived = unpickler.loads(green)
3250 self.assertEqual(orig, derived)
3251 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3252 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3253 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003254 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003255
3256 def test_more_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003257 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003258 cls = self.theclass
3259
3260 t = cls(0, tzinfo=FixedOffset(-300, ""))
3261 self.assertTrue(t)
3262
3263 t = cls(5, tzinfo=FixedOffset(-300, ""))
3264 self.assertTrue(t)
3265
3266 t = cls(5, tzinfo=FixedOffset(300, ""))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003267 self.assertTrue(t)
3268
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003269 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
3270 self.assertTrue(t)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003271
3272 def test_replace(self):
3273 cls = self.theclass
3274 z100 = FixedOffset(100, "+100")
3275 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3276 args = [1, 2, 3, 4, z100]
3277 base = cls(*args)
3278 self.assertEqual(base, base.replace())
3279
3280 i = 0
3281 for name, newval in (("hour", 5),
3282 ("minute", 6),
3283 ("second", 7),
3284 ("microsecond", 8),
3285 ("tzinfo", zm200)):
3286 newargs = args[:]
3287 newargs[i] = newval
3288 expected = cls(*newargs)
3289 got = base.replace(**{name: newval})
3290 self.assertEqual(expected, got)
3291 i += 1
3292
3293 # Ensure we can get rid of a tzinfo.
3294 self.assertEqual(base.tzname(), "+100")
3295 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003296 self.assertIsNone(base2.tzinfo)
3297 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003298
3299 # Ensure we can add one.
3300 base3 = base2.replace(tzinfo=z100)
3301 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003302 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003303
3304 # Out of bounds.
3305 base = cls(1)
3306 self.assertRaises(ValueError, base.replace, hour=24)
3307 self.assertRaises(ValueError, base.replace, minute=-1)
3308 self.assertRaises(ValueError, base.replace, second=100)
3309 self.assertRaises(ValueError, base.replace, microsecond=1000000)
3310
3311 def test_mixed_compare(self):
3312 t1 = time(1, 2, 3)
3313 t2 = time(1, 2, 3)
3314 self.assertEqual(t1, t2)
3315 t2 = t2.replace(tzinfo=None)
3316 self.assertEqual(t1, t2)
3317 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3318 self.assertEqual(t1, t2)
3319 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04003320 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003321
3322 # In time w/ identical tzinfo objects, utcoffset is ignored.
3323 class Varies(tzinfo):
3324 def __init__(self):
3325 self.offset = timedelta(minutes=22)
3326 def utcoffset(self, t):
3327 self.offset += timedelta(minutes=1)
3328 return self.offset
3329
3330 v = Varies()
3331 t1 = t2.replace(tzinfo=v)
3332 t2 = t2.replace(tzinfo=v)
3333 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3334 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3335 self.assertEqual(t1, t2)
3336
3337 # But if they're not identical, it isn't ignored.
3338 t2 = t2.replace(tzinfo=Varies())
3339 self.assertTrue(t1 < t2) # t1's offset counter still going up
3340
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003341 def test_fromisoformat(self):
3342 time_examples = [
3343 (0, 0, 0, 0),
3344 (23, 59, 59, 999999),
3345 ]
3346
3347 hh = (9, 12, 20)
3348 mm = (5, 30)
3349 ss = (4, 45)
3350 usec = (0, 245000, 678901)
3351
3352 time_examples += list(itertools.product(hh, mm, ss, usec))
3353
3354 tzinfos = [None, timezone.utc,
3355 timezone(timedelta(hours=2)),
3356 timezone(timedelta(hours=6, minutes=27))]
3357
3358 for ttup in time_examples:
3359 for tzi in tzinfos:
3360 t = self.theclass(*ttup, tzinfo=tzi)
3361 tstr = t.isoformat()
3362
3363 with self.subTest(tstr=tstr):
3364 t_rt = self.theclass.fromisoformat(tstr)
3365 self.assertEqual(t, t_rt)
3366
3367 def test_fromisoformat_timezone(self):
3368 base_time = self.theclass(12, 30, 45, 217456)
3369
3370 tzoffsets = [
3371 timedelta(hours=5), timedelta(hours=2),
3372 timedelta(hours=6, minutes=27),
3373 timedelta(hours=12, minutes=32, seconds=30),
3374 timedelta(hours=2, minutes=4, seconds=9, microseconds=123456)
3375 ]
3376
3377 tzoffsets += [-1 * td for td in tzoffsets]
3378
3379 tzinfos = [None, timezone.utc,
3380 timezone(timedelta(hours=0))]
3381
3382 tzinfos += [timezone(td) for td in tzoffsets]
3383
3384 for tzi in tzinfos:
3385 t = base_time.replace(tzinfo=tzi)
3386 tstr = t.isoformat()
3387
3388 with self.subTest(tstr=tstr):
3389 t_rt = self.theclass.fromisoformat(tstr)
3390 assert t == t_rt, t_rt
3391
3392 def test_fromisoformat_timespecs(self):
3393 time_bases = [
3394 (8, 17, 45, 123456),
3395 (8, 17, 45, 0)
3396 ]
3397
3398 tzinfos = [None, timezone.utc,
3399 timezone(timedelta(hours=-5)),
3400 timezone(timedelta(hours=2)),
3401 timezone(timedelta(hours=6, minutes=27))]
3402
3403 timespecs = ['hours', 'minutes', 'seconds',
3404 'milliseconds', 'microseconds']
3405
3406 for ip, ts in enumerate(timespecs):
3407 for tzi in tzinfos:
3408 for t_tuple in time_bases:
3409 if ts == 'milliseconds':
3410 new_microseconds = 1000 * (t_tuple[-1] // 1000)
3411 t_tuple = t_tuple[0:-1] + (new_microseconds,)
3412
3413 t = self.theclass(*(t_tuple[0:(1 + ip)]), tzinfo=tzi)
3414 tstr = t.isoformat(timespec=ts)
3415 with self.subTest(tstr=tstr):
3416 t_rt = self.theclass.fromisoformat(tstr)
3417 self.assertEqual(t, t_rt)
3418
3419 def test_fromisoformat_fails(self):
3420 bad_strs = [
3421 '', # Empty string
3422 '12:', # Ends on a separator
3423 '12:30:', # Ends on a separator
3424 '12:30:15.', # Ends on a separator
3425 '1', # Incomplete hours
3426 '12:3', # Incomplete minutes
3427 '12:30:1', # Incomplete seconds
3428 '1a:30:45.334034', # Invalid character in hours
3429 '12:a0:45.334034', # Invalid character in minutes
3430 '12:30:a5.334034', # Invalid character in seconds
3431 '12:30:45.1234', # Too many digits for milliseconds
3432 '12:30:45.1234567', # Too many digits for microseconds
3433 '12:30:45.123456+24:30', # Invalid time zone offset
3434 '12:30:45.123456-24:30', # Invalid negative offset
3435 '12:30:45', # Uses full-width unicode colons
3436 '12:30:45․123456', # Uses \u2024 in place of decimal point
3437 '12:30:45a', # Extra at tend of basic time
3438 '12:30:45.123a', # Extra at end of millisecond time
3439 '12:30:45.123456a', # Extra at end of microsecond time
3440 '12:30:45.123456+12:00:30a', # Extra at end of full time
3441 ]
3442
3443 for bad_str in bad_strs:
3444 with self.subTest(bad_str=bad_str):
3445 with self.assertRaises(ValueError):
3446 self.theclass.fromisoformat(bad_str)
3447
3448 def test_fromisoformat_fails_typeerror(self):
3449 # Test the fromisoformat fails when passed the wrong type
3450 import io
3451
3452 bad_types = [b'12:30:45', None, io.StringIO('12:30:45')]
3453
3454 for bad_type in bad_types:
3455 with self.assertRaises(TypeError):
3456 self.theclass.fromisoformat(bad_type)
3457
3458 def test_fromisoformat_subclass(self):
3459 class TimeSubclass(self.theclass):
3460 pass
3461
3462 tsc = TimeSubclass(12, 14, 45, 203745, tzinfo=timezone.utc)
3463 tsc_rt = TimeSubclass.fromisoformat(tsc.isoformat())
3464
3465 self.assertEqual(tsc, tsc_rt)
3466 self.assertIsInstance(tsc_rt, TimeSubclass)
3467
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003468 def test_subclass_timetz(self):
3469
3470 class C(self.theclass):
3471 theAnswer = 42
3472
3473 def __new__(cls, *args, **kws):
3474 temp = kws.copy()
3475 extra = temp.pop('extra')
3476 result = self.theclass.__new__(cls, *args, **temp)
3477 result.extra = extra
3478 return result
3479
3480 def newmeth(self, start):
3481 return start + self.hour + self.second
3482
3483 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3484
3485 dt1 = self.theclass(*args)
3486 dt2 = C(*args, **{'extra': 7})
3487
3488 self.assertEqual(dt2.__class__, C)
3489 self.assertEqual(dt2.theAnswer, 42)
3490 self.assertEqual(dt2.extra, 7)
3491 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3492 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
3493
3494
3495# Testing datetime objects with a non-None tzinfo.
3496
3497class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
3498 theclass = datetime
3499
3500 def test_trivial(self):
3501 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
3502 self.assertEqual(dt.year, 1)
3503 self.assertEqual(dt.month, 2)
3504 self.assertEqual(dt.day, 3)
3505 self.assertEqual(dt.hour, 4)
3506 self.assertEqual(dt.minute, 5)
3507 self.assertEqual(dt.second, 6)
3508 self.assertEqual(dt.microsecond, 7)
3509 self.assertEqual(dt.tzinfo, None)
3510
3511 def test_even_more_compare(self):
3512 # The test_compare() and test_more_compare() inherited from TestDate
3513 # and TestDateTime covered non-tzinfo cases.
3514
3515 # Smallest possible after UTC adjustment.
3516 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3517 # Largest possible after UTC adjustment.
3518 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3519 tzinfo=FixedOffset(-1439, ""))
3520
3521 # Make sure those compare correctly, and w/o overflow.
3522 self.assertTrue(t1 < t2)
3523 self.assertTrue(t1 != t2)
3524 self.assertTrue(t2 > t1)
3525
3526 self.assertEqual(t1, t1)
3527 self.assertEqual(t2, t2)
3528
3529 # Equal afer adjustment.
3530 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
3531 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
3532 self.assertEqual(t1, t2)
3533
3534 # Change t1 not to subtract a minute, and t1 should be larger.
3535 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
3536 self.assertTrue(t1 > t2)
3537
3538 # Change t1 to subtract 2 minutes, and t1 should be smaller.
3539 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
3540 self.assertTrue(t1 < t2)
3541
3542 # Back to the original t1, but make seconds resolve it.
3543 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3544 second=1)
3545 self.assertTrue(t1 > t2)
3546
3547 # Likewise, but make microseconds resolve it.
3548 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3549 microsecond=1)
3550 self.assertTrue(t1 > t2)
3551
Alexander Belopolsky08313822012-06-15 20:19:47 -04003552 # Make t2 naive and it should differ.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003553 t2 = self.theclass.min
Alexander Belopolsky08313822012-06-15 20:19:47 -04003554 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003555 self.assertEqual(t2, t2)
3556
3557 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
3558 class Naive(tzinfo):
3559 def utcoffset(self, dt): return None
3560 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
Alexander Belopolsky08313822012-06-15 20:19:47 -04003561 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003562 self.assertEqual(t2, t2)
3563
3564 # OTOH, it's OK to compare two of these mixing the two ways of being
3565 # naive.
3566 t1 = self.theclass(5, 6, 7)
3567 self.assertEqual(t1, t2)
3568
3569 # Try a bogus uctoffset.
3570 class Bogus(tzinfo):
3571 def utcoffset(self, dt):
3572 return timedelta(minutes=1440) # out of bounds
3573 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
3574 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
3575 self.assertRaises(ValueError, lambda: t1 == t2)
3576
3577 def test_pickling(self):
3578 # Try one without a tzinfo.
3579 args = 6, 7, 23, 20, 59, 1, 64**2
3580 orig = self.theclass(*args)
3581 for pickler, unpickler, proto in pickle_choices:
3582 green = pickler.dumps(orig, proto)
3583 derived = unpickler.loads(green)
3584 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003585 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003586
3587 # Try one with a tzinfo.
3588 tinfo = PicklableFixedOffset(-300, 'cookie')
3589 orig = self.theclass(*args, **{'tzinfo': tinfo})
3590 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
3591 for pickler, unpickler, proto in pickle_choices:
3592 green = pickler.dumps(orig, proto)
3593 derived = unpickler.loads(green)
3594 self.assertEqual(orig, derived)
3595 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3596 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3597 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003598 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003599
3600 def test_extreme_hashes(self):
3601 # If an attempt is made to hash these via subtracting the offset
3602 # then hashing a datetime object, OverflowError results. The
3603 # Python implementation used to blow up here.
3604 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3605 hash(t)
3606 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3607 tzinfo=FixedOffset(-1439, ""))
3608 hash(t)
3609
3610 # OTOH, an OOB offset should blow up.
3611 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
3612 self.assertRaises(ValueError, hash, t)
3613
3614 def test_zones(self):
3615 est = FixedOffset(-300, "EST")
3616 utc = FixedOffset(0, "UTC")
3617 met = FixedOffset(60, "MET")
3618 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
3619 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
3620 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
3621 self.assertEqual(t1.tzinfo, est)
3622 self.assertEqual(t2.tzinfo, utc)
3623 self.assertEqual(t3.tzinfo, met)
3624 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
3625 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
3626 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
3627 self.assertEqual(t1.tzname(), "EST")
3628 self.assertEqual(t2.tzname(), "UTC")
3629 self.assertEqual(t3.tzname(), "MET")
3630 self.assertEqual(hash(t1), hash(t2))
3631 self.assertEqual(hash(t1), hash(t3))
3632 self.assertEqual(hash(t2), hash(t3))
3633 self.assertEqual(t1, t2)
3634 self.assertEqual(t1, t3)
3635 self.assertEqual(t2, t3)
3636 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
3637 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
3638 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
3639 d = 'datetime.datetime(2002, 3, 19, '
3640 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
3641 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
3642 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
3643
3644 def test_combine(self):
3645 met = FixedOffset(60, "MET")
3646 d = date(2002, 3, 4)
3647 tz = time(18, 45, 3, 1234, tzinfo=met)
3648 dt = datetime.combine(d, tz)
3649 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
3650 tzinfo=met))
3651
3652 def test_extract(self):
3653 met = FixedOffset(60, "MET")
3654 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
3655 self.assertEqual(dt.date(), date(2002, 3, 4))
3656 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
3657 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
3658
3659 def test_tz_aware_arithmetic(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003660 now = self.theclass.now()
3661 tz55 = FixedOffset(-330, "west 5:30")
3662 timeaware = now.time().replace(tzinfo=tz55)
3663 nowaware = self.theclass.combine(now.date(), timeaware)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003664 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003665 self.assertEqual(nowaware.timetz(), timeaware)
3666
3667 # Can't mix aware and non-aware.
3668 self.assertRaises(TypeError, lambda: now - nowaware)
3669 self.assertRaises(TypeError, lambda: nowaware - now)
3670
3671 # And adding datetime's doesn't make sense, aware or not.
3672 self.assertRaises(TypeError, lambda: now + nowaware)
3673 self.assertRaises(TypeError, lambda: nowaware + now)
3674 self.assertRaises(TypeError, lambda: nowaware + nowaware)
3675
3676 # Subtracting should yield 0.
3677 self.assertEqual(now - now, timedelta(0))
3678 self.assertEqual(nowaware - nowaware, timedelta(0))
3679
3680 # Adding a delta should preserve tzinfo.
3681 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
3682 nowawareplus = nowaware + delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003683 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003684 nowawareplus2 = delta + nowaware
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003685 self.assertIs(nowawareplus2.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003686 self.assertEqual(nowawareplus, nowawareplus2)
3687
3688 # that - delta should be what we started with, and that - what we
3689 # started with should be delta.
3690 diff = nowawareplus - delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003691 self.assertIs(diff.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003692 self.assertEqual(nowaware, diff)
3693 self.assertRaises(TypeError, lambda: delta - nowawareplus)
3694 self.assertEqual(nowawareplus - nowaware, delta)
3695
3696 # Make up a random timezone.
3697 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
3698 # Attach it to nowawareplus.
3699 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003700 self.assertIs(nowawareplus.tzinfo, tzr)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003701 # Make sure the difference takes the timezone adjustments into account.
3702 got = nowaware - nowawareplus
3703 # Expected: (nowaware base - nowaware offset) -
3704 # (nowawareplus base - nowawareplus offset) =
3705 # (nowaware base - nowawareplus base) +
3706 # (nowawareplus offset - nowaware offset) =
3707 # -delta + nowawareplus offset - nowaware offset
3708 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
3709 self.assertEqual(got, expected)
3710
3711 # Try max possible difference.
3712 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
3713 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3714 tzinfo=FixedOffset(-1439, "max"))
3715 maxdiff = max - min
3716 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
3717 timedelta(minutes=2*1439))
3718 # Different tzinfo, but the same offset
3719 tza = timezone(HOUR, 'A')
3720 tzb = timezone(HOUR, 'B')
3721 delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
3722 self.assertEqual(delta, self.theclass.min - self.theclass.max)
3723
3724 def test_tzinfo_now(self):
3725 meth = self.theclass.now
3726 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3727 base = meth()
3728 # Try with and without naming the keyword.
3729 off42 = FixedOffset(42, "42")
3730 another = meth(off42)
3731 again = meth(tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003732 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003733 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3734 # Bad argument with and w/o naming the keyword.
3735 self.assertRaises(TypeError, meth, 16)
3736 self.assertRaises(TypeError, meth, tzinfo=16)
3737 # Bad keyword name.
3738 self.assertRaises(TypeError, meth, tinfo=off42)
3739 # Too many args.
3740 self.assertRaises(TypeError, meth, off42, off42)
3741
3742 # We don't know which time zone we're in, and don't have a tzinfo
3743 # class to represent it, so seeing whether a tz argument actually
3744 # does a conversion is tricky.
3745 utc = FixedOffset(0, "utc", 0)
3746 for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
3747 timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
3748 for dummy in range(3):
3749 now = datetime.now(weirdtz)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003750 self.assertIs(now.tzinfo, weirdtz)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003751 utcnow = datetime.utcnow().replace(tzinfo=utc)
3752 now2 = utcnow.astimezone(weirdtz)
3753 if abs(now - now2) < timedelta(seconds=30):
3754 break
3755 # Else the code is broken, or more than 30 seconds passed between
3756 # calls; assuming the latter, just try again.
3757 else:
3758 # Three strikes and we're out.
3759 self.fail("utcnow(), now(tz), or astimezone() may be broken")
3760
3761 def test_tzinfo_fromtimestamp(self):
3762 import time
3763 meth = self.theclass.fromtimestamp
3764 ts = time.time()
3765 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3766 base = meth(ts)
3767 # Try with and without naming the keyword.
3768 off42 = FixedOffset(42, "42")
3769 another = meth(ts, off42)
3770 again = meth(ts, tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003771 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003772 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3773 # Bad argument with and w/o naming the keyword.
3774 self.assertRaises(TypeError, meth, ts, 16)
3775 self.assertRaises(TypeError, meth, ts, tzinfo=16)
3776 # Bad keyword name.
3777 self.assertRaises(TypeError, meth, ts, tinfo=off42)
3778 # Too many args.
3779 self.assertRaises(TypeError, meth, ts, off42, off42)
3780 # Too few args.
3781 self.assertRaises(TypeError, meth)
3782
3783 # Try to make sure tz= actually does some conversion.
3784 timestamp = 1000000000
3785 utcdatetime = datetime.utcfromtimestamp(timestamp)
3786 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
3787 # But on some flavor of Mac, it's nowhere near that. So we can't have
3788 # any idea here what time that actually is, we can only test that
3789 # relative changes match.
3790 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
3791 tz = FixedOffset(utcoffset, "tz", 0)
3792 expected = utcdatetime + utcoffset
3793 got = datetime.fromtimestamp(timestamp, tz)
3794 self.assertEqual(expected, got.replace(tzinfo=None))
3795
3796 def test_tzinfo_utcnow(self):
3797 meth = self.theclass.utcnow
3798 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3799 base = meth()
3800 # Try with and without naming the keyword; for whatever reason,
3801 # utcnow() doesn't accept a tzinfo argument.
3802 off42 = FixedOffset(42, "42")
3803 self.assertRaises(TypeError, meth, off42)
3804 self.assertRaises(TypeError, meth, tzinfo=off42)
3805
3806 def test_tzinfo_utcfromtimestamp(self):
3807 import time
3808 meth = self.theclass.utcfromtimestamp
3809 ts = time.time()
3810 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3811 base = meth(ts)
3812 # Try with and without naming the keyword; for whatever reason,
3813 # utcfromtimestamp() doesn't accept a tzinfo argument.
3814 off42 = FixedOffset(42, "42")
3815 self.assertRaises(TypeError, meth, ts, off42)
3816 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
3817
3818 def test_tzinfo_timetuple(self):
3819 # TestDateTime tested most of this. datetime adds a twist to the
3820 # DST flag.
3821 class DST(tzinfo):
3822 def __init__(self, dstvalue):
3823 if isinstance(dstvalue, int):
3824 dstvalue = timedelta(minutes=dstvalue)
3825 self.dstvalue = dstvalue
3826 def dst(self, dt):
3827 return self.dstvalue
3828
3829 cls = self.theclass
3830 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
3831 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
3832 t = d.timetuple()
3833 self.assertEqual(1, t.tm_year)
3834 self.assertEqual(1, t.tm_mon)
3835 self.assertEqual(1, t.tm_mday)
3836 self.assertEqual(10, t.tm_hour)
3837 self.assertEqual(20, t.tm_min)
3838 self.assertEqual(30, t.tm_sec)
3839 self.assertEqual(0, t.tm_wday)
3840 self.assertEqual(1, t.tm_yday)
3841 self.assertEqual(flag, t.tm_isdst)
3842
3843 # dst() returns wrong type.
3844 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
3845
3846 # dst() at the edge.
3847 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
3848 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
3849
3850 # dst() out of range.
3851 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
3852 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
3853
3854 def test_utctimetuple(self):
3855 class DST(tzinfo):
3856 def __init__(self, dstvalue=0):
3857 if isinstance(dstvalue, int):
3858 dstvalue = timedelta(minutes=dstvalue)
3859 self.dstvalue = dstvalue
3860 def dst(self, dt):
3861 return self.dstvalue
3862
3863 cls = self.theclass
3864 # This can't work: DST didn't implement utcoffset.
3865 self.assertRaises(NotImplementedError,
3866 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
3867
3868 class UOFS(DST):
3869 def __init__(self, uofs, dofs=None):
3870 DST.__init__(self, dofs)
3871 self.uofs = timedelta(minutes=uofs)
3872 def utcoffset(self, dt):
3873 return self.uofs
3874
3875 for dstvalue in -33, 33, 0, None:
3876 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
3877 t = d.utctimetuple()
3878 self.assertEqual(d.year, t.tm_year)
3879 self.assertEqual(d.month, t.tm_mon)
3880 self.assertEqual(d.day, t.tm_mday)
3881 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
3882 self.assertEqual(13, t.tm_min)
3883 self.assertEqual(d.second, t.tm_sec)
3884 self.assertEqual(d.weekday(), t.tm_wday)
3885 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
3886 t.tm_yday)
3887 # Ensure tm_isdst is 0 regardless of what dst() says: DST
3888 # is never in effect for a UTC time.
3889 self.assertEqual(0, t.tm_isdst)
3890
3891 # For naive datetime, utctimetuple == timetuple except for isdst
3892 d = cls(1, 2, 3, 10, 20, 30, 40)
3893 t = d.utctimetuple()
3894 self.assertEqual(t[:-1], d.timetuple()[:-1])
3895 self.assertEqual(0, t.tm_isdst)
3896 # Same if utcoffset is None
3897 class NOFS(DST):
3898 def utcoffset(self, dt):
3899 return None
3900 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
3901 t = d.utctimetuple()
3902 self.assertEqual(t[:-1], d.timetuple()[:-1])
3903 self.assertEqual(0, t.tm_isdst)
3904 # Check that bad tzinfo is detected
3905 class BOFS(DST):
3906 def utcoffset(self, dt):
3907 return "EST"
3908 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
3909 self.assertRaises(TypeError, d.utctimetuple)
3910
3911 # Check that utctimetuple() is the same as
3912 # astimezone(utc).timetuple()
3913 d = cls(2010, 11, 13, 14, 15, 16, 171819)
3914 for tz in [timezone.min, timezone.utc, timezone.max]:
3915 dtz = d.replace(tzinfo=tz)
3916 self.assertEqual(dtz.utctimetuple()[:-1],
3917 dtz.astimezone(timezone.utc).timetuple()[:-1])
3918 # At the edges, UTC adjustment can produce years out-of-range
3919 # for a datetime object. Ensure that an OverflowError is
3920 # raised.
3921 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
3922 # That goes back 1 minute less than a full day.
3923 self.assertRaises(OverflowError, tiny.utctimetuple)
3924
3925 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
3926 # That goes forward 1 minute less than a full day.
3927 self.assertRaises(OverflowError, huge.utctimetuple)
3928 # More overflow cases
3929 tiny = cls.min.replace(tzinfo=timezone(MINUTE))
3930 self.assertRaises(OverflowError, tiny.utctimetuple)
3931 huge = cls.max.replace(tzinfo=timezone(-MINUTE))
3932 self.assertRaises(OverflowError, huge.utctimetuple)
3933
3934 def test_tzinfo_isoformat(self):
3935 zero = FixedOffset(0, "+00:00")
3936 plus = FixedOffset(220, "+03:40")
3937 minus = FixedOffset(-231, "-03:51")
3938 unknown = FixedOffset(None, "")
3939
3940 cls = self.theclass
3941 datestr = '0001-02-03'
3942 for ofs in None, zero, plus, minus, unknown:
3943 for us in 0, 987001:
3944 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
3945 timestr = '04:05:59' + (us and '.987001' or '')
3946 ofsstr = ofs is not None and d.tzname() or ''
3947 tailstr = timestr + ofsstr
3948 iso = d.isoformat()
3949 self.assertEqual(iso, datestr + 'T' + tailstr)
3950 self.assertEqual(iso, d.isoformat('T'))
3951 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
3952 self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
3953 self.assertEqual(str(d), datestr + ' ' + tailstr)
3954
3955 def test_replace(self):
3956 cls = self.theclass
3957 z100 = FixedOffset(100, "+100")
3958 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3959 args = [1, 2, 3, 4, 5, 6, 7, z100]
3960 base = cls(*args)
3961 self.assertEqual(base, base.replace())
3962
3963 i = 0
3964 for name, newval in (("year", 2),
3965 ("month", 3),
3966 ("day", 4),
3967 ("hour", 5),
3968 ("minute", 6),
3969 ("second", 7),
3970 ("microsecond", 8),
3971 ("tzinfo", zm200)):
3972 newargs = args[:]
3973 newargs[i] = newval
3974 expected = cls(*newargs)
3975 got = base.replace(**{name: newval})
3976 self.assertEqual(expected, got)
3977 i += 1
3978
3979 # Ensure we can get rid of a tzinfo.
3980 self.assertEqual(base.tzname(), "+100")
3981 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003982 self.assertIsNone(base2.tzinfo)
3983 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003984
3985 # Ensure we can add one.
3986 base3 = base2.replace(tzinfo=z100)
3987 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003988 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003989
3990 # Out of bounds.
3991 base = cls(2000, 2, 29)
3992 self.assertRaises(ValueError, base.replace, year=2001)
3993
3994 def test_more_astimezone(self):
3995 # The inherited test_astimezone covered some trivial and error cases.
3996 fnone = FixedOffset(None, "None")
3997 f44m = FixedOffset(44, "44")
3998 fm5h = FixedOffset(-timedelta(hours=5), "m300")
3999
4000 dt = self.theclass.now(tz=f44m)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004001 self.assertIs(dt.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004002 # Replacing with degenerate tzinfo raises an exception.
4003 self.assertRaises(ValueError, dt.astimezone, fnone)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004004 # Replacing with same tzinfo makes no change.
4005 x = dt.astimezone(dt.tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004006 self.assertIs(x.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004007 self.assertEqual(x.date(), dt.date())
4008 self.assertEqual(x.time(), dt.time())
4009
4010 # Replacing with different tzinfo does adjust.
4011 got = dt.astimezone(fm5h)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004012 self.assertIs(got.tzinfo, fm5h)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004013 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
4014 expected = dt - dt.utcoffset() # in effect, convert to UTC
4015 expected += fm5h.utcoffset(dt) # and from there to local time
4016 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
4017 self.assertEqual(got.date(), expected.date())
4018 self.assertEqual(got.time(), expected.time())
4019 self.assertEqual(got.timetz(), expected.timetz())
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004020 self.assertIs(got.tzinfo, expected.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004021 self.assertEqual(got, expected)
4022
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004023 @support.run_with_tz('UTC')
4024 def test_astimezone_default_utc(self):
4025 dt = self.theclass.now(timezone.utc)
4026 self.assertEqual(dt.astimezone(None), dt)
4027 self.assertEqual(dt.astimezone(), dt)
4028
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004029 # Note that offset in TZ variable has the opposite sign to that
4030 # produced by %z directive.
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004031 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4032 def test_astimezone_default_eastern(self):
4033 dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
4034 local = dt.astimezone()
4035 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004036 self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004037 dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
4038 local = dt.astimezone()
4039 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004040 self.assertEqual(local.strftime("%z %Z"), "-0400 EDT")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004041
Alexander Belopolsky1dcf4f92016-03-25 15:42:59 -04004042 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4043 def test_astimezone_default_near_fold(self):
4044 # Issue #26616.
4045 u = datetime(2015, 11, 1, 5, tzinfo=timezone.utc)
4046 t = u.astimezone()
4047 s = t.astimezone()
4048 self.assertEqual(t.tzinfo, s.tzinfo)
4049
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004050 def test_aware_subtract(self):
4051 cls = self.theclass
4052
4053 # Ensure that utcoffset() is ignored when the operands have the
4054 # same tzinfo member.
4055 class OperandDependentOffset(tzinfo):
4056 def utcoffset(self, t):
4057 if t.minute < 10:
4058 # d0 and d1 equal after adjustment
4059 return timedelta(minutes=t.minute)
4060 else:
4061 # d2 off in the weeds
4062 return timedelta(minutes=59)
4063
4064 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
4065 d0 = base.replace(minute=3)
4066 d1 = base.replace(minute=9)
4067 d2 = base.replace(minute=11)
4068 for x in d0, d1, d2:
4069 for y in d0, d1, d2:
4070 got = x - y
4071 expected = timedelta(minutes=x.minute - y.minute)
4072 self.assertEqual(got, expected)
4073
4074 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
4075 # ignored.
4076 base = cls(8, 9, 10, 11, 12, 13, 14)
4077 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
4078 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
4079 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
4080 for x in d0, d1, d2:
4081 for y in d0, d1, d2:
4082 got = x - y
4083 if (x is d0 or x is d1) and (y is d0 or y is d1):
4084 expected = timedelta(0)
4085 elif x is y is d2:
4086 expected = timedelta(0)
4087 elif x is d2:
4088 expected = timedelta(minutes=(11-59)-0)
4089 else:
4090 assert y is d2
4091 expected = timedelta(minutes=0-(11-59))
4092 self.assertEqual(got, expected)
4093
4094 def test_mixed_compare(self):
4095 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
4096 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
4097 self.assertEqual(t1, t2)
4098 t2 = t2.replace(tzinfo=None)
4099 self.assertEqual(t1, t2)
4100 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
4101 self.assertEqual(t1, t2)
4102 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04004103 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004104
4105 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
4106 class Varies(tzinfo):
4107 def __init__(self):
4108 self.offset = timedelta(minutes=22)
4109 def utcoffset(self, t):
4110 self.offset += timedelta(minutes=1)
4111 return self.offset
4112
4113 v = Varies()
4114 t1 = t2.replace(tzinfo=v)
4115 t2 = t2.replace(tzinfo=v)
4116 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
4117 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
4118 self.assertEqual(t1, t2)
4119
4120 # But if they're not identical, it isn't ignored.
4121 t2 = t2.replace(tzinfo=Varies())
4122 self.assertTrue(t1 < t2) # t1's offset counter still going up
4123
4124 def test_subclass_datetimetz(self):
4125
4126 class C(self.theclass):
4127 theAnswer = 42
4128
4129 def __new__(cls, *args, **kws):
4130 temp = kws.copy()
4131 extra = temp.pop('extra')
4132 result = self.theclass.__new__(cls, *args, **temp)
4133 result.extra = extra
4134 return result
4135
4136 def newmeth(self, start):
4137 return start + self.hour + self.year
4138
4139 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
4140
4141 dt1 = self.theclass(*args)
4142 dt2 = C(*args, **{'extra': 7})
4143
4144 self.assertEqual(dt2.__class__, C)
4145 self.assertEqual(dt2.theAnswer, 42)
4146 self.assertEqual(dt2.extra, 7)
4147 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
4148 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
4149
4150# Pain to set up DST-aware tzinfo classes.
4151
4152def first_sunday_on_or_after(dt):
4153 days_to_go = 6 - dt.weekday()
4154 if days_to_go:
4155 dt += timedelta(days_to_go)
4156 return dt
4157
4158ZERO = timedelta(0)
4159MINUTE = timedelta(minutes=1)
4160HOUR = timedelta(hours=1)
4161DAY = timedelta(days=1)
4162# In the US, DST starts at 2am (standard time) on the first Sunday in April.
4163DSTSTART = datetime(1, 4, 1, 2)
4164# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
4165# which is the first Sunday on or after Oct 25. Because we view 1:MM as
4166# being standard time on that day, there is no spelling in local time of
4167# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
4168DSTEND = datetime(1, 10, 25, 1)
4169
4170class USTimeZone(tzinfo):
4171
4172 def __init__(self, hours, reprname, stdname, dstname):
4173 self.stdoffset = timedelta(hours=hours)
4174 self.reprname = reprname
4175 self.stdname = stdname
4176 self.dstname = dstname
4177
4178 def __repr__(self):
4179 return self.reprname
4180
4181 def tzname(self, dt):
4182 if self.dst(dt):
4183 return self.dstname
4184 else:
4185 return self.stdname
4186
4187 def utcoffset(self, dt):
4188 return self.stdoffset + self.dst(dt)
4189
4190 def dst(self, dt):
4191 if dt is None or dt.tzinfo is None:
4192 # An exception instead may be sensible here, in one or more of
4193 # the cases.
4194 return ZERO
4195 assert dt.tzinfo is self
4196
4197 # Find first Sunday in April.
4198 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
4199 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
4200
4201 # Find last Sunday in October.
4202 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
4203 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
4204
4205 # Can't compare naive to aware objects, so strip the timezone from
4206 # dt first.
4207 if start <= dt.replace(tzinfo=None) < end:
4208 return HOUR
4209 else:
4210 return ZERO
4211
4212Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
4213Central = USTimeZone(-6, "Central", "CST", "CDT")
4214Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
4215Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
4216utc_real = FixedOffset(0, "UTC", 0)
4217# For better test coverage, we want another flavor of UTC that's west of
4218# the Eastern and Pacific timezones.
4219utc_fake = FixedOffset(-12*60, "UTCfake", 0)
4220
4221class TestTimezoneConversions(unittest.TestCase):
4222 # The DST switch times for 2002, in std time.
4223 dston = datetime(2002, 4, 7, 2)
4224 dstoff = datetime(2002, 10, 27, 1)
4225
4226 theclass = datetime
4227
4228 # Check a time that's inside DST.
4229 def checkinside(self, dt, tz, utc, dston, dstoff):
4230 self.assertEqual(dt.dst(), HOUR)
4231
4232 # Conversion to our own timezone is always an identity.
4233 self.assertEqual(dt.astimezone(tz), dt)
4234
4235 asutc = dt.astimezone(utc)
4236 there_and_back = asutc.astimezone(tz)
4237
4238 # Conversion to UTC and back isn't always an identity here,
4239 # because there are redundant spellings (in local time) of
4240 # UTC time when DST begins: the clock jumps from 1:59:59
4241 # to 3:00:00, and a local time of 2:MM:SS doesn't really
4242 # make sense then. The classes above treat 2:MM:SS as
4243 # daylight time then (it's "after 2am"), really an alias
4244 # for 1:MM:SS standard time. The latter form is what
4245 # conversion back from UTC produces.
4246 if dt.date() == dston.date() and dt.hour == 2:
4247 # We're in the redundant hour, and coming back from
4248 # UTC gives the 1:MM:SS standard-time spelling.
4249 self.assertEqual(there_and_back + HOUR, dt)
4250 # Although during was considered to be in daylight
4251 # time, there_and_back is not.
4252 self.assertEqual(there_and_back.dst(), ZERO)
4253 # They're the same times in UTC.
4254 self.assertEqual(there_and_back.astimezone(utc),
4255 dt.astimezone(utc))
4256 else:
4257 # We're not in the redundant hour.
4258 self.assertEqual(dt, there_and_back)
4259
4260 # Because we have a redundant spelling when DST begins, there is
Ezio Melotti3b3499b2011-03-16 11:35:38 +02004261 # (unfortunately) an hour when DST ends that can't be spelled at all in
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004262 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
4263 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
4264 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
4265 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
4266 # expressed in local time. Nevertheless, we want conversion back
4267 # from UTC to mimic the local clock's "repeat an hour" behavior.
4268 nexthour_utc = asutc + HOUR
4269 nexthour_tz = nexthour_utc.astimezone(tz)
4270 if dt.date() == dstoff.date() and dt.hour == 0:
4271 # We're in the hour before the last DST hour. The last DST hour
4272 # is ineffable. We want the conversion back to repeat 1:MM.
4273 self.assertEqual(nexthour_tz, dt.replace(hour=1))
4274 nexthour_utc += HOUR
4275 nexthour_tz = nexthour_utc.astimezone(tz)
4276 self.assertEqual(nexthour_tz, dt.replace(hour=1))
4277 else:
4278 self.assertEqual(nexthour_tz - dt, HOUR)
4279
4280 # Check a time that's outside DST.
4281 def checkoutside(self, dt, tz, utc):
4282 self.assertEqual(dt.dst(), ZERO)
4283
4284 # Conversion to our own timezone is always an identity.
4285 self.assertEqual(dt.astimezone(tz), dt)
4286
4287 # Converting to UTC and back is an identity too.
4288 asutc = dt.astimezone(utc)
4289 there_and_back = asutc.astimezone(tz)
4290 self.assertEqual(dt, there_and_back)
4291
4292 def convert_between_tz_and_utc(self, tz, utc):
4293 dston = self.dston.replace(tzinfo=tz)
4294 # Because 1:MM on the day DST ends is taken as being standard time,
4295 # there is no spelling in tz for the last hour of daylight time.
4296 # For purposes of the test, the last hour of DST is 0:MM, which is
4297 # taken as being daylight time (and 1:MM is taken as being standard
4298 # time).
4299 dstoff = self.dstoff.replace(tzinfo=tz)
4300 for delta in (timedelta(weeks=13),
4301 DAY,
4302 HOUR,
4303 timedelta(minutes=1),
4304 timedelta(microseconds=1)):
4305
4306 self.checkinside(dston, tz, utc, dston, dstoff)
4307 for during in dston + delta, dstoff - delta:
4308 self.checkinside(during, tz, utc, dston, dstoff)
4309
4310 self.checkoutside(dstoff, tz, utc)
4311 for outside in dston - delta, dstoff + delta:
4312 self.checkoutside(outside, tz, utc)
4313
4314 def test_easy(self):
4315 # Despite the name of this test, the endcases are excruciating.
4316 self.convert_between_tz_and_utc(Eastern, utc_real)
4317 self.convert_between_tz_and_utc(Pacific, utc_real)
4318 self.convert_between_tz_and_utc(Eastern, utc_fake)
4319 self.convert_between_tz_and_utc(Pacific, utc_fake)
4320 # The next is really dancing near the edge. It works because
4321 # Pacific and Eastern are far enough apart that their "problem
4322 # hours" don't overlap.
4323 self.convert_between_tz_and_utc(Eastern, Pacific)
4324 self.convert_between_tz_and_utc(Pacific, Eastern)
4325 # OTOH, these fail! Don't enable them. The difficulty is that
4326 # the edge case tests assume that every hour is representable in
4327 # the "utc" class. This is always true for a fixed-offset tzinfo
4328 # class (lke utc_real and utc_fake), but not for Eastern or Central.
4329 # For these adjacent DST-aware time zones, the range of time offsets
4330 # tested ends up creating hours in the one that aren't representable
4331 # in the other. For the same reason, we would see failures in the
4332 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
4333 # offset deltas in convert_between_tz_and_utc().
4334 #
4335 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
4336 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
4337
4338 def test_tricky(self):
4339 # 22:00 on day before daylight starts.
4340 fourback = self.dston - timedelta(hours=4)
4341 ninewest = FixedOffset(-9*60, "-0900", 0)
4342 fourback = fourback.replace(tzinfo=ninewest)
4343 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
4344 # 2", we should get the 3 spelling.
4345 # If we plug 22:00 the day before into Eastern, it "looks like std
4346 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
4347 # to 22:00 lands on 2:00, which makes no sense in local time (the
4348 # local clock jumps from 1 to 3). The point here is to make sure we
4349 # get the 3 spelling.
4350 expected = self.dston.replace(hour=3)
4351 got = fourback.astimezone(Eastern).replace(tzinfo=None)
4352 self.assertEqual(expected, got)
4353
4354 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
4355 # case we want the 1:00 spelling.
4356 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
4357 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
4358 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
4359 # spelling.
4360 expected = self.dston.replace(hour=1)
4361 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
4362 self.assertEqual(expected, got)
4363
4364 # Now on the day DST ends, we want "repeat an hour" behavior.
4365 # UTC 4:MM 5:MM 6:MM 7:MM checking these
4366 # EST 23:MM 0:MM 1:MM 2:MM
4367 # EDT 0:MM 1:MM 2:MM 3:MM
4368 # wall 0:MM 1:MM 1:MM 2:MM against these
4369 for utc in utc_real, utc_fake:
4370 for tz in Eastern, Pacific:
4371 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
4372 # Convert that to UTC.
4373 first_std_hour -= tz.utcoffset(None)
4374 # Adjust for possibly fake UTC.
4375 asutc = first_std_hour + utc.utcoffset(None)
4376 # First UTC hour to convert; this is 4:00 when utc=utc_real &
4377 # tz=Eastern.
4378 asutcbase = asutc.replace(tzinfo=utc)
4379 for tzhour in (0, 1, 1, 2):
4380 expectedbase = self.dstoff.replace(hour=tzhour)
4381 for minute in 0, 30, 59:
4382 expected = expectedbase.replace(minute=minute)
4383 asutc = asutcbase.replace(minute=minute)
4384 astz = asutc.astimezone(tz)
4385 self.assertEqual(astz.replace(tzinfo=None), expected)
4386 asutcbase += HOUR
4387
4388
4389 def test_bogus_dst(self):
4390 class ok(tzinfo):
4391 def utcoffset(self, dt): return HOUR
4392 def dst(self, dt): return HOUR
4393
4394 now = self.theclass.now().replace(tzinfo=utc_real)
4395 # Doesn't blow up.
4396 now.astimezone(ok())
4397
4398 # Does blow up.
4399 class notok(ok):
4400 def dst(self, dt): return None
4401 self.assertRaises(ValueError, now.astimezone, notok())
4402
4403 # Sometimes blow up. In the following, tzinfo.dst()
4404 # implementation may return None or not None depending on
4405 # whether DST is assumed to be in effect. In this situation,
4406 # a ValueError should be raised by astimezone().
4407 class tricky_notok(ok):
4408 def dst(self, dt):
4409 if dt.year == 2000:
4410 return None
4411 else:
4412 return 10*HOUR
4413 dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
4414 self.assertRaises(ValueError, dt.astimezone, tricky_notok())
4415
4416 def test_fromutc(self):
4417 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
4418 now = datetime.utcnow().replace(tzinfo=utc_real)
4419 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
4420 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
4421 enow = Eastern.fromutc(now) # doesn't blow up
4422 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
4423 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
4424 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
4425
4426 # Always converts UTC to standard time.
4427 class FauxUSTimeZone(USTimeZone):
4428 def fromutc(self, dt):
4429 return dt + self.stdoffset
4430 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
4431
4432 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
4433 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
4434 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
4435
4436 # Check around DST start.
4437 start = self.dston.replace(hour=4, tzinfo=Eastern)
4438 fstart = start.replace(tzinfo=FEastern)
4439 for wall in 23, 0, 1, 3, 4, 5:
4440 expected = start.replace(hour=wall)
4441 if wall == 23:
4442 expected -= timedelta(days=1)
4443 got = Eastern.fromutc(start)
4444 self.assertEqual(expected, got)
4445
4446 expected = fstart + FEastern.stdoffset
4447 got = FEastern.fromutc(fstart)
4448 self.assertEqual(expected, got)
4449
4450 # Ensure astimezone() calls fromutc() too.
4451 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
4452 self.assertEqual(expected, got)
4453
4454 start += HOUR
4455 fstart += HOUR
4456
4457 # Check around DST end.
4458 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
4459 fstart = start.replace(tzinfo=FEastern)
4460 for wall in 0, 1, 1, 2, 3, 4:
4461 expected = start.replace(hour=wall)
4462 got = Eastern.fromutc(start)
4463 self.assertEqual(expected, got)
4464
4465 expected = fstart + FEastern.stdoffset
4466 got = FEastern.fromutc(fstart)
4467 self.assertEqual(expected, got)
4468
4469 # Ensure astimezone() calls fromutc() too.
4470 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
4471 self.assertEqual(expected, got)
4472
4473 start += HOUR
4474 fstart += HOUR
4475
4476
4477#############################################################################
4478# oddballs
4479
4480class Oddballs(unittest.TestCase):
4481
4482 def test_bug_1028306(self):
4483 # Trying to compare a date to a datetime should act like a mixed-
4484 # type comparison, despite that datetime is a subclass of date.
4485 as_date = date.today()
4486 as_datetime = datetime.combine(as_date, time())
4487 self.assertTrue(as_date != as_datetime)
4488 self.assertTrue(as_datetime != as_date)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004489 self.assertFalse(as_date == as_datetime)
4490 self.assertFalse(as_datetime == as_date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004491 self.assertRaises(TypeError, lambda: as_date < as_datetime)
4492 self.assertRaises(TypeError, lambda: as_datetime < as_date)
4493 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
4494 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
4495 self.assertRaises(TypeError, lambda: as_date > as_datetime)
4496 self.assertRaises(TypeError, lambda: as_datetime > as_date)
4497 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
4498 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
4499
Raymond Hettinger15f44ab2016-08-30 10:47:49 -07004500 # Nevertheless, comparison should work with the base-class (date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004501 # projection if use of a date method is forced.
4502 self.assertEqual(as_date.__eq__(as_datetime), True)
4503 different_day = (as_date.day + 1) % 20 + 1
4504 as_different = as_datetime.replace(day= different_day)
4505 self.assertEqual(as_date.__eq__(as_different), False)
4506
4507 # And date should compare with other subclasses of date. If a
4508 # subclass wants to stop this, it's up to the subclass to do so.
4509 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
4510 self.assertEqual(as_date, date_sc)
4511 self.assertEqual(date_sc, as_date)
4512
4513 # Ditto for datetimes.
4514 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
4515 as_date.day, 0, 0, 0)
4516 self.assertEqual(as_datetime, datetime_sc)
4517 self.assertEqual(datetime_sc, as_datetime)
4518
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004519 def test_extra_attributes(self):
4520 for x in [date.today(),
4521 time(),
4522 datetime.utcnow(),
4523 timedelta(),
4524 tzinfo(),
4525 timezone(timedelta())]:
4526 with self.assertRaises(AttributeError):
4527 x.abc = 1
4528
4529 def test_check_arg_types(self):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004530 class Number:
4531 def __init__(self, value):
4532 self.value = value
4533 def __int__(self):
4534 return self.value
4535
4536 for xx in [decimal.Decimal(10),
4537 decimal.Decimal('10.9'),
4538 Number(10)]:
4539 self.assertEqual(datetime(10, 10, 10, 10, 10, 10, 10),
4540 datetime(xx, xx, xx, xx, xx, xx, xx))
4541
4542 with self.assertRaisesRegex(TypeError, '^an integer is required '
R David Murray44b548d2016-09-08 13:59:53 -04004543 r'\(got type str\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004544 datetime(10, 10, '10')
4545
4546 f10 = Number(10.9)
4547 with self.assertRaisesRegex(TypeError, '^__int__ returned non-int '
R David Murray44b548d2016-09-08 13:59:53 -04004548 r'\(type float\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004549 datetime(10, 10, f10)
4550
4551 class Float(float):
4552 pass
4553 s10 = Float(10.9)
4554 with self.assertRaisesRegex(TypeError, '^integer argument expected, '
4555 'got float$'):
4556 datetime(10, 10, s10)
4557
4558 with self.assertRaises(TypeError):
4559 datetime(10., 10, 10)
4560 with self.assertRaises(TypeError):
4561 datetime(10, 10., 10)
4562 with self.assertRaises(TypeError):
4563 datetime(10, 10, 10.)
4564 with self.assertRaises(TypeError):
4565 datetime(10, 10, 10, 10.)
4566 with self.assertRaises(TypeError):
4567 datetime(10, 10, 10, 10, 10.)
4568 with self.assertRaises(TypeError):
4569 datetime(10, 10, 10, 10, 10, 10.)
4570 with self.assertRaises(TypeError):
4571 datetime(10, 10, 10, 10, 10, 10, 10.)
4572
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004573#############################################################################
4574# Local Time Disambiguation
4575
4576# An experimental reimplementation of fromutc that respects the "fold" flag.
4577
4578class tzinfo2(tzinfo):
4579
4580 def fromutc(self, dt):
4581 "datetime in UTC -> datetime in local time."
4582
4583 if not isinstance(dt, datetime):
4584 raise TypeError("fromutc() requires a datetime argument")
4585 if dt.tzinfo is not self:
4586 raise ValueError("dt.tzinfo is not self")
4587 # Returned value satisfies
4588 # dt + ldt.utcoffset() = ldt
4589 off0 = dt.replace(fold=0).utcoffset()
4590 off1 = dt.replace(fold=1).utcoffset()
4591 if off0 is None or off1 is None or dt.dst() is None:
4592 raise ValueError
4593 if off0 == off1:
4594 ldt = dt + off0
4595 off1 = ldt.utcoffset()
4596 if off0 == off1:
4597 return ldt
4598 # Now, we discovered both possible offsets, so
4599 # we can just try four possible solutions:
4600 for off in [off0, off1]:
4601 ldt = dt + off
4602 if ldt.utcoffset() == off:
4603 return ldt
4604 ldt = ldt.replace(fold=1)
4605 if ldt.utcoffset() == off:
4606 return ldt
4607
4608 raise ValueError("No suitable local time found")
4609
4610# Reimplementing simplified US timezones to respect the "fold" flag:
4611
4612class USTimeZone2(tzinfo2):
4613
4614 def __init__(self, hours, reprname, stdname, dstname):
4615 self.stdoffset = timedelta(hours=hours)
4616 self.reprname = reprname
4617 self.stdname = stdname
4618 self.dstname = dstname
4619
4620 def __repr__(self):
4621 return self.reprname
4622
4623 def tzname(self, dt):
4624 if self.dst(dt):
4625 return self.dstname
4626 else:
4627 return self.stdname
4628
4629 def utcoffset(self, dt):
4630 return self.stdoffset + self.dst(dt)
4631
4632 def dst(self, dt):
4633 if dt is None or dt.tzinfo is None:
4634 # An exception instead may be sensible here, in one or more of
4635 # the cases.
4636 return ZERO
4637 assert dt.tzinfo is self
4638
4639 # Find first Sunday in April.
4640 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
4641 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
4642
4643 # Find last Sunday in October.
4644 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
4645 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
4646
4647 # Can't compare naive to aware objects, so strip the timezone from
4648 # dt first.
4649 dt = dt.replace(tzinfo=None)
4650 if start + HOUR <= dt < end:
4651 # DST is in effect.
4652 return HOUR
4653 elif end <= dt < end + HOUR:
4654 # Fold (an ambiguous hour): use dt.fold to disambiguate.
4655 return ZERO if dt.fold else HOUR
4656 elif start <= dt < start + HOUR:
4657 # Gap (a non-existent hour): reverse the fold rule.
4658 return HOUR if dt.fold else ZERO
4659 else:
4660 # DST is off.
4661 return ZERO
4662
4663Eastern2 = USTimeZone2(-5, "Eastern2", "EST", "EDT")
4664Central2 = USTimeZone2(-6, "Central2", "CST", "CDT")
4665Mountain2 = USTimeZone2(-7, "Mountain2", "MST", "MDT")
4666Pacific2 = USTimeZone2(-8, "Pacific2", "PST", "PDT")
4667
4668# Europe_Vilnius_1941 tzinfo implementation reproduces the following
4669# 1941 transition from Olson's tzdist:
4670#
4671# Zone NAME GMTOFF RULES FORMAT [UNTIL]
4672# ZoneEurope/Vilnius 1:00 - CET 1940 Aug 3
4673# 3:00 - MSK 1941 Jun 24
4674# 1:00 C-Eur CE%sT 1944 Aug
4675#
4676# $ zdump -v Europe/Vilnius | grep 1941
4677# Europe/Vilnius Mon Jun 23 20:59:59 1941 UTC = Mon Jun 23 23:59:59 1941 MSK isdst=0 gmtoff=10800
4678# Europe/Vilnius Mon Jun 23 21:00:00 1941 UTC = Mon Jun 23 23:00:00 1941 CEST isdst=1 gmtoff=7200
4679
4680class Europe_Vilnius_1941(tzinfo):
4681 def _utc_fold(self):
4682 return [datetime(1941, 6, 23, 21, tzinfo=self), # Mon Jun 23 21:00:00 1941 UTC
4683 datetime(1941, 6, 23, 22, tzinfo=self)] # Mon Jun 23 22:00:00 1941 UTC
4684
4685 def _loc_fold(self):
4686 return [datetime(1941, 6, 23, 23, tzinfo=self), # Mon Jun 23 23:00:00 1941 MSK / CEST
4687 datetime(1941, 6, 24, 0, tzinfo=self)] # Mon Jun 24 00:00:00 1941 CEST
4688
4689 def utcoffset(self, dt):
4690 fold_start, fold_stop = self._loc_fold()
4691 if dt < fold_start:
4692 return 3 * HOUR
4693 if dt < fold_stop:
4694 return (2 if dt.fold else 3) * HOUR
4695 # if dt >= fold_stop
4696 return 2 * HOUR
4697
4698 def dst(self, dt):
4699 fold_start, fold_stop = self._loc_fold()
4700 if dt < fold_start:
4701 return 0 * HOUR
4702 if dt < fold_stop:
4703 return (1 if dt.fold else 0) * HOUR
4704 # if dt >= fold_stop
4705 return 1 * HOUR
4706
4707 def tzname(self, dt):
4708 fold_start, fold_stop = self._loc_fold()
4709 if dt < fold_start:
4710 return 'MSK'
4711 if dt < fold_stop:
4712 return ('MSK', 'CEST')[dt.fold]
4713 # if dt >= fold_stop
4714 return 'CEST'
4715
4716 def fromutc(self, dt):
4717 assert dt.fold == 0
4718 assert dt.tzinfo is self
4719 if dt.year != 1941:
4720 raise NotImplementedError
4721 fold_start, fold_stop = self._utc_fold()
4722 if dt < fold_start:
4723 return dt + 3 * HOUR
4724 if dt < fold_stop:
4725 return (dt + 2 * HOUR).replace(fold=1)
4726 # if dt >= fold_stop
4727 return dt + 2 * HOUR
4728
4729
4730class TestLocalTimeDisambiguation(unittest.TestCase):
4731
4732 def test_vilnius_1941_fromutc(self):
4733 Vilnius = Europe_Vilnius_1941()
4734
4735 gdt = datetime(1941, 6, 23, 20, 59, 59, tzinfo=timezone.utc)
4736 ldt = gdt.astimezone(Vilnius)
4737 self.assertEqual(ldt.strftime("%c %Z%z"),
4738 'Mon Jun 23 23:59:59 1941 MSK+0300')
4739 self.assertEqual(ldt.fold, 0)
4740 self.assertFalse(ldt.dst())
4741
4742 gdt = datetime(1941, 6, 23, 21, tzinfo=timezone.utc)
4743 ldt = gdt.astimezone(Vilnius)
4744 self.assertEqual(ldt.strftime("%c %Z%z"),
4745 'Mon Jun 23 23:00:00 1941 CEST+0200')
4746 self.assertEqual(ldt.fold, 1)
4747 self.assertTrue(ldt.dst())
4748
4749 gdt = datetime(1941, 6, 23, 22, tzinfo=timezone.utc)
4750 ldt = gdt.astimezone(Vilnius)
4751 self.assertEqual(ldt.strftime("%c %Z%z"),
4752 'Tue Jun 24 00:00:00 1941 CEST+0200')
4753 self.assertEqual(ldt.fold, 0)
4754 self.assertTrue(ldt.dst())
4755
4756 def test_vilnius_1941_toutc(self):
4757 Vilnius = Europe_Vilnius_1941()
4758
4759 ldt = datetime(1941, 6, 23, 22, 59, 59, tzinfo=Vilnius)
4760 gdt = ldt.astimezone(timezone.utc)
4761 self.assertEqual(gdt.strftime("%c %Z"),
4762 'Mon Jun 23 19:59:59 1941 UTC')
4763
4764 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius)
4765 gdt = ldt.astimezone(timezone.utc)
4766 self.assertEqual(gdt.strftime("%c %Z"),
4767 'Mon Jun 23 20:59:59 1941 UTC')
4768
4769 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius, fold=1)
4770 gdt = ldt.astimezone(timezone.utc)
4771 self.assertEqual(gdt.strftime("%c %Z"),
4772 'Mon Jun 23 21:59:59 1941 UTC')
4773
4774 ldt = datetime(1941, 6, 24, 0, tzinfo=Vilnius)
4775 gdt = ldt.astimezone(timezone.utc)
4776 self.assertEqual(gdt.strftime("%c %Z"),
4777 'Mon Jun 23 22:00:00 1941 UTC')
4778
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004779 def test_constructors(self):
4780 t = time(0, fold=1)
4781 dt = datetime(1, 1, 1, fold=1)
4782 self.assertEqual(t.fold, 1)
4783 self.assertEqual(dt.fold, 1)
4784 with self.assertRaises(TypeError):
4785 time(0, 0, 0, 0, None, 0)
4786
4787 def test_member(self):
4788 dt = datetime(1, 1, 1, fold=1)
4789 t = dt.time()
4790 self.assertEqual(t.fold, 1)
4791 t = dt.timetz()
4792 self.assertEqual(t.fold, 1)
4793
4794 def test_replace(self):
4795 t = time(0)
4796 dt = datetime(1, 1, 1)
4797 self.assertEqual(t.replace(fold=1).fold, 1)
4798 self.assertEqual(dt.replace(fold=1).fold, 1)
4799 self.assertEqual(t.replace(fold=0).fold, 0)
4800 self.assertEqual(dt.replace(fold=0).fold, 0)
4801 # Check that replacement of other fields does not change "fold".
4802 t = t.replace(fold=1, tzinfo=Eastern)
4803 dt = dt.replace(fold=1, tzinfo=Eastern)
4804 self.assertEqual(t.replace(tzinfo=None).fold, 1)
4805 self.assertEqual(dt.replace(tzinfo=None).fold, 1)
Serhiy Storchaka314d6fc2017-03-31 22:48:16 +03004806 # Out of bounds.
4807 with self.assertRaises(ValueError):
4808 t.replace(fold=2)
4809 with self.assertRaises(ValueError):
4810 dt.replace(fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004811 # Check that fold is a keyword-only argument
4812 with self.assertRaises(TypeError):
4813 t.replace(1, 1, 1, None, 1)
4814 with self.assertRaises(TypeError):
4815 dt.replace(1, 1, 1, 1, 1, 1, 1, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04004816
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004817 def test_comparison(self):
4818 t = time(0)
4819 dt = datetime(1, 1, 1)
4820 self.assertEqual(t, t.replace(fold=1))
4821 self.assertEqual(dt, dt.replace(fold=1))
4822
4823 def test_hash(self):
4824 t = time(0)
4825 dt = datetime(1, 1, 1)
4826 self.assertEqual(hash(t), hash(t.replace(fold=1)))
4827 self.assertEqual(hash(dt), hash(dt.replace(fold=1)))
4828
4829 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4830 def test_fromtimestamp(self):
4831 s = 1414906200
4832 dt0 = datetime.fromtimestamp(s)
4833 dt1 = datetime.fromtimestamp(s + 3600)
4834 self.assertEqual(dt0.fold, 0)
4835 self.assertEqual(dt1.fold, 1)
4836
4837 @support.run_with_tz('Australia/Lord_Howe')
4838 def test_fromtimestamp_lord_howe(self):
4839 tm = _time.localtime(1.4e9)
4840 if _time.strftime('%Z%z', tm) != 'LHST+1030':
4841 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
4842 # $ TZ=Australia/Lord_Howe date -r 1428158700
4843 # Sun Apr 5 01:45:00 LHDT 2015
4844 # $ TZ=Australia/Lord_Howe date -r 1428160500
4845 # Sun Apr 5 01:45:00 LHST 2015
4846 s = 1428158700
4847 t0 = datetime.fromtimestamp(s)
4848 t1 = datetime.fromtimestamp(s + 1800)
4849 self.assertEqual(t0, t1)
4850 self.assertEqual(t0.fold, 0)
4851 self.assertEqual(t1.fold, 1)
4852
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004853 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4854 def test_timestamp(self):
4855 dt0 = datetime(2014, 11, 2, 1, 30)
4856 dt1 = dt0.replace(fold=1)
4857 self.assertEqual(dt0.timestamp() + 3600,
4858 dt1.timestamp())
4859
4860 @support.run_with_tz('Australia/Lord_Howe')
4861 def test_timestamp_lord_howe(self):
4862 tm = _time.localtime(1.4e9)
4863 if _time.strftime('%Z%z', tm) != 'LHST+1030':
4864 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
4865 t = datetime(2015, 4, 5, 1, 45)
4866 s0 = t.replace(fold=0).timestamp()
4867 s1 = t.replace(fold=1).timestamp()
4868 self.assertEqual(s0 + 1800, s1)
4869
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004870 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4871 def test_astimezone(self):
4872 dt0 = datetime(2014, 11, 2, 1, 30)
4873 dt1 = dt0.replace(fold=1)
4874 # Convert both naive instances to aware.
4875 adt0 = dt0.astimezone()
4876 adt1 = dt1.astimezone()
4877 # Check that the first instance in DST zone and the second in STD
4878 self.assertEqual(adt0.tzname(), 'EDT')
4879 self.assertEqual(adt1.tzname(), 'EST')
4880 self.assertEqual(adt0 + HOUR, adt1)
4881 # Aware instances with fixed offset tzinfo's always have fold=0
4882 self.assertEqual(adt0.fold, 0)
4883 self.assertEqual(adt1.fold, 0)
4884
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004885 def test_pickle_fold(self):
4886 t = time(fold=1)
4887 dt = datetime(1, 1, 1, fold=1)
4888 for pickler, unpickler, proto in pickle_choices:
4889 for x in [t, dt]:
4890 s = pickler.dumps(x, proto)
4891 y = unpickler.loads(s)
4892 self.assertEqual(x, y)
4893 self.assertEqual((0 if proto < 4 else x.fold), y.fold)
4894
4895 def test_repr(self):
4896 t = time(fold=1)
4897 dt = datetime(1, 1, 1, fold=1)
4898 self.assertEqual(repr(t), 'datetime.time(0, 0, fold=1)')
4899 self.assertEqual(repr(dt),
4900 'datetime.datetime(1, 1, 1, 0, 0, fold=1)')
4901
4902 def test_dst(self):
4903 # Let's first establish that things work in regular times.
4904 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
4905 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
4906 self.assertEqual(dt_summer.dst(), HOUR)
4907 self.assertEqual(dt_winter.dst(), ZERO)
4908 # The disambiguation flag is ignored
4909 self.assertEqual(dt_summer.replace(fold=1).dst(), HOUR)
4910 self.assertEqual(dt_winter.replace(fold=1).dst(), ZERO)
4911
4912 # Pick local time in the fold.
4913 for minute in [0, 30, 59]:
4914 dt = datetime(2002, 10, 27, 1, minute, tzinfo=Eastern2)
4915 # With fold=0 (the default) it is in DST.
4916 self.assertEqual(dt.dst(), HOUR)
4917 # With fold=1 it is in STD.
4918 self.assertEqual(dt.replace(fold=1).dst(), ZERO)
4919
4920 # Pick local time in the gap.
4921 for minute in [0, 30, 59]:
4922 dt = datetime(2002, 4, 7, 2, minute, tzinfo=Eastern2)
4923 # With fold=0 (the default) it is in STD.
4924 self.assertEqual(dt.dst(), ZERO)
4925 # With fold=1 it is in DST.
4926 self.assertEqual(dt.replace(fold=1).dst(), HOUR)
4927
4928
4929 def test_utcoffset(self):
4930 # Let's first establish that things work in regular times.
4931 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
4932 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
4933 self.assertEqual(dt_summer.utcoffset(), -4 * HOUR)
4934 self.assertEqual(dt_winter.utcoffset(), -5 * HOUR)
4935 # The disambiguation flag is ignored
4936 self.assertEqual(dt_summer.replace(fold=1).utcoffset(), -4 * HOUR)
4937 self.assertEqual(dt_winter.replace(fold=1).utcoffset(), -5 * HOUR)
4938
4939 def test_fromutc(self):
4940 # Let's first establish that things work in regular times.
4941 u_summer = datetime(2002, 10, 27, 6, tzinfo=Eastern2) - timedelta.resolution
4942 u_winter = datetime(2002, 10, 27, 7, tzinfo=Eastern2)
4943 t_summer = Eastern2.fromutc(u_summer)
4944 t_winter = Eastern2.fromutc(u_winter)
4945 self.assertEqual(t_summer, u_summer - 4 * HOUR)
4946 self.assertEqual(t_winter, u_winter - 5 * HOUR)
4947 self.assertEqual(t_summer.fold, 0)
4948 self.assertEqual(t_winter.fold, 0)
4949
4950 # What happens in the fall-back fold?
4951 u = datetime(2002, 10, 27, 5, 30, tzinfo=Eastern2)
4952 t0 = Eastern2.fromutc(u)
4953 u += HOUR
4954 t1 = Eastern2.fromutc(u)
4955 self.assertEqual(t0, t1)
4956 self.assertEqual(t0.fold, 0)
4957 self.assertEqual(t1.fold, 1)
4958 # The tricky part is when u is in the local fold:
4959 u = datetime(2002, 10, 27, 1, 30, tzinfo=Eastern2)
4960 t = Eastern2.fromutc(u)
4961 self.assertEqual((t.day, t.hour), (26, 21))
4962 # .. or gets into the local fold after a standard time adjustment
4963 u = datetime(2002, 10, 27, 6, 30, tzinfo=Eastern2)
4964 t = Eastern2.fromutc(u)
4965 self.assertEqual((t.day, t.hour), (27, 1))
4966
4967 # What happens in the spring-forward gap?
4968 u = datetime(2002, 4, 7, 2, 0, tzinfo=Eastern2)
4969 t = Eastern2.fromutc(u)
4970 self.assertEqual((t.day, t.hour), (6, 21))
4971
4972 def test_mixed_compare_regular(self):
4973 t = datetime(2000, 1, 1, tzinfo=Eastern2)
4974 self.assertEqual(t, t.astimezone(timezone.utc))
4975 t = datetime(2000, 6, 1, tzinfo=Eastern2)
4976 self.assertEqual(t, t.astimezone(timezone.utc))
4977
4978 def test_mixed_compare_fold(self):
4979 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
4980 t_fold_utc = t_fold.astimezone(timezone.utc)
4981 self.assertNotEqual(t_fold, t_fold_utc)
4982
4983 def test_mixed_compare_gap(self):
4984 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
4985 t_gap_utc = t_gap.astimezone(timezone.utc)
4986 self.assertNotEqual(t_gap, t_gap_utc)
4987
4988 def test_hash_aware(self):
4989 t = datetime(2000, 1, 1, tzinfo=Eastern2)
4990 self.assertEqual(hash(t), hash(t.replace(fold=1)))
4991 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
4992 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
4993 self.assertEqual(hash(t_fold), hash(t_fold.replace(fold=1)))
4994 self.assertEqual(hash(t_gap), hash(t_gap.replace(fold=1)))
4995
4996SEC = timedelta(0, 1)
4997
4998def pairs(iterable):
4999 a, b = itertools.tee(iterable)
5000 next(b, None)
5001 return zip(a, b)
5002
5003class ZoneInfo(tzinfo):
5004 zoneroot = '/usr/share/zoneinfo'
5005 def __init__(self, ut, ti):
5006 """
5007
5008 :param ut: array
5009 Array of transition point timestamps
5010 :param ti: list
5011 A list of (offset, isdst, abbr) tuples
5012 :return: None
5013 """
5014 self.ut = ut
5015 self.ti = ti
5016 self.lt = self.invert(ut, ti)
5017
5018 @staticmethod
5019 def invert(ut, ti):
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04005020 lt = (array('q', ut), array('q', ut))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005021 if ut:
5022 offset = ti[0][0] // SEC
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04005023 lt[0][0] += offset
5024 lt[1][0] += offset
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005025 for i in range(1, len(ut)):
5026 lt[0][i] += ti[i-1][0] // SEC
5027 lt[1][i] += ti[i][0] // SEC
5028 return lt
5029
5030 @classmethod
5031 def fromfile(cls, fileobj):
5032 if fileobj.read(4).decode() != "TZif":
5033 raise ValueError("not a zoneinfo file")
5034 fileobj.seek(32)
5035 counts = array('i')
5036 counts.fromfile(fileobj, 3)
5037 if sys.byteorder != 'big':
5038 counts.byteswap()
5039
5040 ut = array('i')
5041 ut.fromfile(fileobj, counts[0])
5042 if sys.byteorder != 'big':
5043 ut.byteswap()
5044
5045 type_indices = array('B')
5046 type_indices.fromfile(fileobj, counts[0])
5047
5048 ttis = []
5049 for i in range(counts[1]):
5050 ttis.append(struct.unpack(">lbb", fileobj.read(6)))
5051
5052 abbrs = fileobj.read(counts[2])
5053
5054 # Convert ttis
5055 for i, (gmtoff, isdst, abbrind) in enumerate(ttis):
5056 abbr = abbrs[abbrind:abbrs.find(0, abbrind)].decode()
5057 ttis[i] = (timedelta(0, gmtoff), isdst, abbr)
5058
5059 ti = [None] * len(ut)
5060 for i, idx in enumerate(type_indices):
5061 ti[i] = ttis[idx]
5062
5063 self = cls(ut, ti)
5064
5065 return self
5066
5067 @classmethod
5068 def fromname(cls, name):
5069 path = os.path.join(cls.zoneroot, name)
5070 with open(path, 'rb') as f:
5071 return cls.fromfile(f)
5072
5073 EPOCHORDINAL = date(1970, 1, 1).toordinal()
5074
5075 def fromutc(self, dt):
5076 """datetime in UTC -> datetime in local time."""
5077
5078 if not isinstance(dt, datetime):
5079 raise TypeError("fromutc() requires a datetime argument")
5080 if dt.tzinfo is not self:
5081 raise ValueError("dt.tzinfo is not self")
5082
5083 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
5084 + dt.hour * 3600
5085 + dt.minute * 60
5086 + dt.second)
5087
5088 if timestamp < self.ut[1]:
5089 tti = self.ti[0]
5090 fold = 0
5091 else:
5092 idx = bisect.bisect_right(self.ut, timestamp)
5093 assert self.ut[idx-1] <= timestamp
5094 assert idx == len(self.ut) or timestamp < self.ut[idx]
5095 tti_prev, tti = self.ti[idx-2:idx]
5096 # Detect fold
5097 shift = tti_prev[0] - tti[0]
5098 fold = (shift > timedelta(0, timestamp - self.ut[idx-1]))
5099 dt += tti[0]
5100 if fold:
5101 return dt.replace(fold=1)
5102 else:
5103 return dt
5104
5105 def _find_ti(self, dt, i):
5106 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
5107 + dt.hour * 3600
5108 + dt.minute * 60
5109 + dt.second)
5110 lt = self.lt[dt.fold]
5111 idx = bisect.bisect_right(lt, timestamp)
5112
5113 return self.ti[max(0, idx - 1)][i]
5114
5115 def utcoffset(self, dt):
5116 return self._find_ti(dt, 0)
5117
5118 def dst(self, dt):
5119 isdst = self._find_ti(dt, 1)
5120 # XXX: We cannot accurately determine the "save" value,
5121 # so let's return 1h whenever DST is in effect. Since
5122 # we don't use dst() in fromutc(), it is unlikely that
5123 # it will be needed for anything more than bool(dst()).
5124 return ZERO if isdst else HOUR
5125
5126 def tzname(self, dt):
5127 return self._find_ti(dt, 2)
5128
5129 @classmethod
5130 def zonenames(cls, zonedir=None):
5131 if zonedir is None:
5132 zonedir = cls.zoneroot
Alexander Belopolsky1b8f26c2016-08-11 11:01:45 -04005133 zone_tab = os.path.join(zonedir, 'zone.tab')
5134 try:
5135 f = open(zone_tab)
5136 except OSError:
5137 return
5138 with f:
5139 for line in f:
5140 line = line.strip()
5141 if line and not line.startswith('#'):
5142 yield line.split()[2]
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005143
5144 @classmethod
5145 def stats(cls, start_year=1):
5146 count = gap_count = fold_count = zeros_count = 0
5147 min_gap = min_fold = timedelta.max
5148 max_gap = max_fold = ZERO
5149 min_gap_datetime = max_gap_datetime = datetime.min
5150 min_gap_zone = max_gap_zone = None
5151 min_fold_datetime = max_fold_datetime = datetime.min
5152 min_fold_zone = max_fold_zone = None
5153 stats_since = datetime(start_year, 1, 1) # Starting from 1970 eliminates a lot of noise
5154 for zonename in cls.zonenames():
5155 count += 1
5156 tz = cls.fromname(zonename)
5157 for dt, shift in tz.transitions():
5158 if dt < stats_since:
5159 continue
5160 if shift > ZERO:
5161 gap_count += 1
5162 if (shift, dt) > (max_gap, max_gap_datetime):
5163 max_gap = shift
5164 max_gap_zone = zonename
5165 max_gap_datetime = dt
5166 if (shift, datetime.max - dt) < (min_gap, datetime.max - min_gap_datetime):
5167 min_gap = shift
5168 min_gap_zone = zonename
5169 min_gap_datetime = dt
5170 elif shift < ZERO:
5171 fold_count += 1
5172 shift = -shift
5173 if (shift, dt) > (max_fold, max_fold_datetime):
5174 max_fold = shift
5175 max_fold_zone = zonename
5176 max_fold_datetime = dt
5177 if (shift, datetime.max - dt) < (min_fold, datetime.max - min_fold_datetime):
5178 min_fold = shift
5179 min_fold_zone = zonename
5180 min_fold_datetime = dt
5181 else:
5182 zeros_count += 1
5183 trans_counts = (gap_count, fold_count, zeros_count)
5184 print("Number of zones: %5d" % count)
5185 print("Number of transitions: %5d = %d (gaps) + %d (folds) + %d (zeros)" %
5186 ((sum(trans_counts),) + trans_counts))
5187 print("Min gap: %16s at %s in %s" % (min_gap, min_gap_datetime, min_gap_zone))
5188 print("Max gap: %16s at %s in %s" % (max_gap, max_gap_datetime, max_gap_zone))
5189 print("Min fold: %16s at %s in %s" % (min_fold, min_fold_datetime, min_fold_zone))
5190 print("Max fold: %16s at %s in %s" % (max_fold, max_fold_datetime, max_fold_zone))
5191
5192
5193 def transitions(self):
5194 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
5195 shift = ti[0] - prev_ti[0]
5196 yield datetime.utcfromtimestamp(t), shift
5197
5198 def nondst_folds(self):
5199 """Find all folds with the same value of isdst on both sides of the transition."""
5200 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
5201 shift = ti[0] - prev_ti[0]
5202 if shift < ZERO and ti[1] == prev_ti[1]:
5203 yield datetime.utcfromtimestamp(t), -shift, prev_ti[2], ti[2]
5204
5205 @classmethod
5206 def print_all_nondst_folds(cls, same_abbr=False, start_year=1):
5207 count = 0
5208 for zonename in cls.zonenames():
5209 tz = cls.fromname(zonename)
5210 for dt, shift, prev_abbr, abbr in tz.nondst_folds():
5211 if dt.year < start_year or same_abbr and prev_abbr != abbr:
5212 continue
5213 count += 1
5214 print("%3d) %-30s %s %10s %5s -> %s" %
5215 (count, zonename, dt, shift, prev_abbr, abbr))
5216
5217 def folds(self):
5218 for t, shift in self.transitions():
5219 if shift < ZERO:
5220 yield t, -shift
5221
5222 def gaps(self):
5223 for t, shift in self.transitions():
5224 if shift > ZERO:
5225 yield t, shift
5226
5227 def zeros(self):
5228 for t, shift in self.transitions():
5229 if not shift:
5230 yield t
5231
5232
5233class ZoneInfoTest(unittest.TestCase):
5234 zonename = 'America/New_York'
5235
5236 def setUp(self):
5237 if sys.platform == "win32":
5238 self.skipTest("Skipping zoneinfo tests on Windows")
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04005239 try:
5240 self.tz = ZoneInfo.fromname(self.zonename)
5241 except FileNotFoundError as err:
5242 self.skipTest("Skipping %s: %s" % (self.zonename, err))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005243
5244 def assertEquivDatetimes(self, a, b):
5245 self.assertEqual((a.replace(tzinfo=None), a.fold, id(a.tzinfo)),
5246 (b.replace(tzinfo=None), b.fold, id(b.tzinfo)))
5247
5248 def test_folds(self):
5249 tz = self.tz
5250 for dt, shift in tz.folds():
5251 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
5252 udt = dt + x
5253 ldt = tz.fromutc(udt.replace(tzinfo=tz))
5254 self.assertEqual(ldt.fold, 1)
5255 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
5256 self.assertEquivDatetimes(adt, ldt)
5257 utcoffset = ldt.utcoffset()
5258 self.assertEqual(ldt.replace(tzinfo=None), udt + utcoffset)
5259 # Round trip
5260 self.assertEquivDatetimes(ldt.astimezone(timezone.utc),
5261 udt.replace(tzinfo=timezone.utc))
5262
5263
5264 for x in [-timedelta.resolution, shift]:
5265 udt = dt + x
5266 udt = udt.replace(tzinfo=tz)
5267 ldt = tz.fromutc(udt)
5268 self.assertEqual(ldt.fold, 0)
5269
5270 def test_gaps(self):
5271 tz = self.tz
5272 for dt, shift in tz.gaps():
5273 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
5274 udt = dt + x
5275 udt = udt.replace(tzinfo=tz)
5276 ldt = tz.fromutc(udt)
5277 self.assertEqual(ldt.fold, 0)
5278 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
5279 self.assertEquivDatetimes(adt, ldt)
5280 utcoffset = ldt.utcoffset()
5281 self.assertEqual(ldt.replace(tzinfo=None), udt.replace(tzinfo=None) + utcoffset)
5282 # Create a local time inside the gap
5283 ldt = tz.fromutc(dt.replace(tzinfo=tz)) - shift + x
5284 self.assertLess(ldt.replace(fold=1).utcoffset(),
5285 ldt.replace(fold=0).utcoffset(),
5286 "At %s." % ldt)
5287
5288 for x in [-timedelta.resolution, shift]:
5289 udt = dt + x
5290 ldt = tz.fromutc(udt.replace(tzinfo=tz))
5291 self.assertEqual(ldt.fold, 0)
5292
5293 def test_system_transitions(self):
5294 if ('Riyadh8' in self.zonename or
5295 # From tzdata NEWS file:
5296 # The files solar87, solar88, and solar89 are no longer distributed.
5297 # They were a negative experiment - that is, a demonstration that
5298 # tz data can represent solar time only with some difficulty and error.
5299 # Their presence in the distribution caused confusion, as Riyadh
5300 # civil time was generally not solar time in those years.
5301 self.zonename.startswith('right/')):
5302 self.skipTest("Skipping %s" % self.zonename)
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04005303 tz = self.tz
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005304 TZ = os.environ.get('TZ')
5305 os.environ['TZ'] = self.zonename
5306 try:
5307 _time.tzset()
5308 for udt, shift in tz.transitions():
Alexander Belopolsky10c2dd22016-08-12 19:08:15 -04005309 if udt.year >= 2037:
5310 # System support for times around the end of 32-bit time_t
5311 # and later is flaky on many systems.
5312 break
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005313 s0 = (udt - datetime(1970, 1, 1)) // SEC
5314 ss = shift // SEC # shift seconds
5315 for x in [-40 * 3600, -20*3600, -1, 0,
5316 ss - 1, ss + 20 * 3600, ss + 40 * 3600]:
5317 s = s0 + x
5318 sdt = datetime.fromtimestamp(s)
5319 tzdt = datetime.fromtimestamp(s, tz).replace(tzinfo=None)
5320 self.assertEquivDatetimes(sdt, tzdt)
5321 s1 = sdt.timestamp()
5322 self.assertEqual(s, s1)
5323 if ss > 0: # gap
5324 # Create local time inside the gap
5325 dt = datetime.fromtimestamp(s0) - shift / 2
5326 ts0 = dt.timestamp()
5327 ts1 = dt.replace(fold=1).timestamp()
5328 self.assertEqual(ts0, s0 + ss / 2)
5329 self.assertEqual(ts1, s0 - ss / 2)
5330 finally:
5331 if TZ is None:
5332 del os.environ['TZ']
5333 else:
5334 os.environ['TZ'] = TZ
5335 _time.tzset()
5336
5337
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04005338class ZoneInfoCompleteTest(unittest.TestSuite):
5339 def __init__(self):
5340 tests = []
5341 if is_resource_enabled('tzdata'):
5342 for name in ZoneInfo.zonenames():
5343 Test = type('ZoneInfoTest[%s]' % name, (ZoneInfoTest,), {})
5344 Test.zonename = name
5345 for method in dir(Test):
5346 if method.startswith('test_'):
5347 tests.append(Test(method))
5348 super().__init__(tests)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005349
5350# Iran had a sub-minute UTC offset before 1946.
5351class IranTest(ZoneInfoTest):
Alexander Belopolsky07e2a0a2016-07-30 11:41:02 -04005352 zonename = 'Asia/Tehran'
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005353
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04005354def load_tests(loader, standard_tests, pattern):
5355 standard_tests.addTest(ZoneInfoCompleteTest())
5356 return standard_tests
5357
5358
Alexander Belopolskycf86e362010-07-23 19:25:47 +00005359if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -05005360 unittest.main()