blob: 53de0658773c7bac09d1920c9ff0cfb41989c5b6 [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
Paul Ganssle3df85402018-10-22 12:32:52 -040016import re
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -040017import struct
Alexander Belopolskycf86e362010-07-23 19:25:47 +000018import unittest
19
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -040020from array import array
21
Alexander Belopolskycf86e362010-07-23 19:25:47 +000022from operator import lt, le, gt, ge, eq, ne, truediv, floordiv, mod
23
24from test import support
25
26import datetime as datetime_module
27from datetime import MINYEAR, MAXYEAR
28from datetime import timedelta
29from datetime import tzinfo
30from datetime import time
31from datetime import timezone
32from datetime import date, datetime
33import time as _time
34
Paul Ganssle04af5b12018-01-24 17:29:30 -050035import _testcapi
36
Alexander Belopolskycf86e362010-07-23 19:25:47 +000037# Needed by test_datetime
38import _strptime
39#
40
Serhiy Storchaka8452ca12018-12-07 13:42:10 +020041pickle_loads = {pickle.loads, pickle._loads}
Alexander Belopolskycf86e362010-07-23 19:25:47 +000042
43pickle_choices = [(pickle, pickle, proto)
44 for proto in range(pickle.HIGHEST_PROTOCOL + 1)]
45assert len(pickle_choices) == pickle.HIGHEST_PROTOCOL + 1
46
47# An arbitrary collection of objects of non-datetime types, for testing
48# mixed-type comparisons.
49OTHERSTUFF = (10, 34.5, "abc", {}, [], ())
50
51
52# XXX Copied from test_float.
53INF = float("inf")
54NAN = float("nan")
55
Alexander Belopolskycf86e362010-07-23 19:25:47 +000056#############################################################################
57# module tests
58
59class TestModule(unittest.TestCase):
60
61 def test_constants(self):
62 datetime = datetime_module
63 self.assertEqual(datetime.MINYEAR, 1)
64 self.assertEqual(datetime.MAXYEAR, 9999)
65
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -040066 def test_name_cleanup(self):
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +020067 if '_Pure' in self.__class__.__name__:
68 self.skipTest('Only run for Fast C implementation')
69
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -040070 datetime = datetime_module
71 names = set(name for name in dir(datetime)
72 if not name.startswith('__') and not name.endswith('__'))
73 allowed = set(['MAXYEAR', 'MINYEAR', 'date', 'datetime',
74 'datetime_CAPI', 'time', 'timedelta', 'timezone',
Ammar Askar96d1e692018-07-25 09:54:58 -070075 'tzinfo', 'sys'])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -040076 self.assertEqual(names - allowed, set([]))
77
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -050078 def test_divide_and_round(self):
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +020079 if '_Fast' in self.__class__.__name__:
80 self.skipTest('Only run for Pure Python implementation')
81
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -050082 dar = datetime_module._divide_and_round
83
84 self.assertEqual(dar(-10, -3), 3)
85 self.assertEqual(dar(5, -2), -2)
86
87 # four cases: (2 signs of a) x (2 signs of b)
88 self.assertEqual(dar(7, 3), 2)
89 self.assertEqual(dar(-7, 3), -2)
90 self.assertEqual(dar(7, -3), -2)
91 self.assertEqual(dar(-7, -3), 2)
92
93 # ties to even - eight cases:
94 # (2 signs of a) x (2 signs of b) x (even / odd quotient)
95 self.assertEqual(dar(10, 4), 2)
96 self.assertEqual(dar(-10, 4), -2)
97 self.assertEqual(dar(10, -4), -2)
98 self.assertEqual(dar(-10, -4), 2)
99
100 self.assertEqual(dar(6, 4), 2)
101 self.assertEqual(dar(-6, 4), -2)
102 self.assertEqual(dar(6, -4), -2)
103 self.assertEqual(dar(-6, -4), 2)
104
105
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000106#############################################################################
107# tzinfo tests
108
109class FixedOffset(tzinfo):
110
111 def __init__(self, offset, name, dstoffset=42):
112 if isinstance(offset, int):
113 offset = timedelta(minutes=offset)
114 if isinstance(dstoffset, int):
115 dstoffset = timedelta(minutes=dstoffset)
116 self.__offset = offset
117 self.__name = name
118 self.__dstoffset = dstoffset
119 def __repr__(self):
120 return self.__name.lower()
121 def utcoffset(self, dt):
122 return self.__offset
123 def tzname(self, dt):
124 return self.__name
125 def dst(self, dt):
126 return self.__dstoffset
127
128class PicklableFixedOffset(FixedOffset):
129
130 def __init__(self, offset=None, name=None, dstoffset=None):
131 FixedOffset.__init__(self, offset, name, dstoffset)
132
Berker Peksage3385b42016-03-19 13:16:32 +0200133 def __getstate__(self):
134 return self.__dict__
135
Raymond Hettinger5a2146a2014-07-25 14:59:48 -0700136class _TZInfo(tzinfo):
137 def utcoffset(self, datetime_module):
138 return random.random()
139
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000140class TestTZInfo(unittest.TestCase):
141
Raymond Hettinger5a2146a2014-07-25 14:59:48 -0700142 def test_refcnt_crash_bug_22044(self):
143 tz1 = _TZInfo()
144 dt1 = datetime(2014, 7, 21, 11, 32, 3, 0, tz1)
145 with self.assertRaises(TypeError):
146 dt1.utcoffset()
147
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000148 def test_non_abstractness(self):
149 # In order to allow subclasses to get pickled, the C implementation
150 # wasn't able to get away with having __init__ raise
151 # NotImplementedError.
152 useless = tzinfo()
153 dt = datetime.max
154 self.assertRaises(NotImplementedError, useless.tzname, dt)
155 self.assertRaises(NotImplementedError, useless.utcoffset, dt)
156 self.assertRaises(NotImplementedError, useless.dst, dt)
157
158 def test_subclass_must_override(self):
159 class NotEnough(tzinfo):
160 def __init__(self, offset, name):
161 self.__offset = offset
162 self.__name = name
163 self.assertTrue(issubclass(NotEnough, tzinfo))
164 ne = NotEnough(3, "NotByALongShot")
165 self.assertIsInstance(ne, tzinfo)
166
167 dt = datetime.now()
168 self.assertRaises(NotImplementedError, ne.tzname, dt)
169 self.assertRaises(NotImplementedError, ne.utcoffset, dt)
170 self.assertRaises(NotImplementedError, ne.dst, dt)
171
172 def test_normal(self):
173 fo = FixedOffset(3, "Three")
174 self.assertIsInstance(fo, tzinfo)
175 for dt in datetime.now(), None:
176 self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3))
177 self.assertEqual(fo.tzname(dt), "Three")
178 self.assertEqual(fo.dst(dt), timedelta(minutes=42))
179
180 def test_pickling_base(self):
181 # There's no point to pickling tzinfo objects on their own (they
182 # carry no data), but they need to be picklable anyway else
183 # concrete subclasses can't be pickled.
184 orig = tzinfo.__new__(tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200185 self.assertIs(type(orig), tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000186 for pickler, unpickler, proto in pickle_choices:
187 green = pickler.dumps(orig, proto)
188 derived = unpickler.loads(green)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200189 self.assertIs(type(derived), tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000190
191 def test_pickling_subclass(self):
192 # Make sure we can pickle/unpickle an instance of a subclass.
193 offset = timedelta(minutes=-300)
194 for otype, args in [
195 (PicklableFixedOffset, (offset, 'cookie')),
196 (timezone, (offset,)),
197 (timezone, (offset, "EST"))]:
198 orig = otype(*args)
199 oname = orig.tzname(None)
200 self.assertIsInstance(orig, tzinfo)
201 self.assertIs(type(orig), otype)
202 self.assertEqual(orig.utcoffset(None), offset)
203 self.assertEqual(orig.tzname(None), oname)
204 for pickler, unpickler, proto in pickle_choices:
205 green = pickler.dumps(orig, proto)
206 derived = unpickler.loads(green)
207 self.assertIsInstance(derived, tzinfo)
208 self.assertIs(type(derived), otype)
209 self.assertEqual(derived.utcoffset(None), offset)
210 self.assertEqual(derived.tzname(None), oname)
211
Alexander Belopolskyc79447b2015-09-27 21:41:55 -0400212 def test_issue23600(self):
213 DSTDIFF = DSTOFFSET = timedelta(hours=1)
214
215 class UKSummerTime(tzinfo):
216 """Simple time zone which pretends to always be in summer time, since
217 that's what shows the failure.
218 """
219
220 def utcoffset(self, dt):
221 return DSTOFFSET
222
223 def dst(self, dt):
224 return DSTDIFF
225
226 def tzname(self, dt):
227 return 'UKSummerTime'
228
229 tz = UKSummerTime()
230 u = datetime(2014, 4, 26, 12, 1, tzinfo=tz)
231 t = tz.fromutc(u)
232 self.assertEqual(t - t.utcoffset(), u)
233
234
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000235class TestTimeZone(unittest.TestCase):
236
237 def setUp(self):
238 self.ACDT = timezone(timedelta(hours=9.5), 'ACDT')
239 self.EST = timezone(-timedelta(hours=5), 'EST')
240 self.DT = datetime(2010, 1, 1)
241
242 def test_str(self):
243 for tz in [self.ACDT, self.EST, timezone.utc,
244 timezone.min, timezone.max]:
245 self.assertEqual(str(tz), tz.tzname(None))
246
247 def test_repr(self):
248 datetime = datetime_module
249 for tz in [self.ACDT, self.EST, timezone.utc,
250 timezone.min, timezone.max]:
251 # test round-trip
252 tzrep = repr(tz)
253 self.assertEqual(tz, eval(tzrep))
254
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000255 def test_class_members(self):
256 limit = timedelta(hours=23, minutes=59)
257 self.assertEqual(timezone.utc.utcoffset(None), ZERO)
258 self.assertEqual(timezone.min.utcoffset(None), -limit)
259 self.assertEqual(timezone.max.utcoffset(None), limit)
260
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000261 def test_constructor(self):
Alexander Belopolsky1bcbaab2010-10-14 17:03:51 +0000262 self.assertIs(timezone.utc, timezone(timedelta(0)))
263 self.assertIsNot(timezone.utc, timezone(timedelta(0), 'UTC'))
264 self.assertEqual(timezone.utc, timezone(timedelta(0), 'UTC'))
Alexander Belopolsky018d3532017-07-31 10:26:50 -0400265 for subminute in [timedelta(microseconds=1), timedelta(seconds=1)]:
266 tz = timezone(subminute)
267 self.assertNotEqual(tz.utcoffset(None) % timedelta(minutes=1), 0)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000268 # invalid offsets
Alexander Belopolsky018d3532017-07-31 10:26:50 -0400269 for invalid in [timedelta(1, 1), timedelta(1)]:
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000270 self.assertRaises(ValueError, timezone, invalid)
271 self.assertRaises(ValueError, timezone, -invalid)
272
273 with self.assertRaises(TypeError): timezone(None)
274 with self.assertRaises(TypeError): timezone(42)
275 with self.assertRaises(TypeError): timezone(ZERO, None)
276 with self.assertRaises(TypeError): timezone(ZERO, 42)
277 with self.assertRaises(TypeError): timezone(ZERO, 'ABC', 'extra')
278
279 def test_inheritance(self):
280 self.assertIsInstance(timezone.utc, tzinfo)
281 self.assertIsInstance(self.EST, tzinfo)
282
283 def test_utcoffset(self):
284 dummy = self.DT
285 for h in [0, 1.5, 12]:
286 offset = h * HOUR
287 self.assertEqual(offset, timezone(offset).utcoffset(dummy))
288 self.assertEqual(-offset, timezone(-offset).utcoffset(dummy))
289
290 with self.assertRaises(TypeError): self.EST.utcoffset('')
291 with self.assertRaises(TypeError): self.EST.utcoffset(5)
292
293
294 def test_dst(self):
Raymond Hettinger7beae8a2011-01-06 05:34:17 +0000295 self.assertIsNone(timezone.utc.dst(self.DT))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000296
297 with self.assertRaises(TypeError): self.EST.dst('')
298 with self.assertRaises(TypeError): self.EST.dst(5)
299
300 def test_tzname(self):
Alexander Belopolsky7827a5b2015-09-06 13:07:21 -0400301 self.assertEqual('UTC', timezone.utc.tzname(None))
302 self.assertEqual('UTC', timezone(ZERO).tzname(None))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000303 self.assertEqual('UTC-05:00', timezone(-5 * HOUR).tzname(None))
304 self.assertEqual('UTC+09:30', timezone(9.5 * HOUR).tzname(None))
305 self.assertEqual('UTC-00:01', timezone(timedelta(minutes=-1)).tzname(None))
306 self.assertEqual('XYZ', timezone(-5 * HOUR, 'XYZ').tzname(None))
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +0300307 # bpo-34482: Check that surrogates are handled properly.
308 self.assertEqual('\ud800', timezone(ZERO, '\ud800').tzname(None))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000309
Alexander Belopolsky018d3532017-07-31 10:26:50 -0400310 # Sub-minute offsets:
311 self.assertEqual('UTC+01:06:40', timezone(timedelta(0, 4000)).tzname(None))
312 self.assertEqual('UTC-01:06:40',
313 timezone(-timedelta(0, 4000)).tzname(None))
314 self.assertEqual('UTC+01:06:40.000001',
315 timezone(timedelta(0, 4000, 1)).tzname(None))
316 self.assertEqual('UTC-01:06:40.000001',
317 timezone(-timedelta(0, 4000, 1)).tzname(None))
318
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000319 with self.assertRaises(TypeError): self.EST.tzname('')
320 with self.assertRaises(TypeError): self.EST.tzname(5)
321
322 def test_fromutc(self):
323 with self.assertRaises(ValueError):
324 timezone.utc.fromutc(self.DT)
325 with self.assertRaises(TypeError):
326 timezone.utc.fromutc('not datetime')
327 for tz in [self.EST, self.ACDT, Eastern]:
328 utctime = self.DT.replace(tzinfo=tz)
329 local = tz.fromutc(utctime)
330 self.assertEqual(local - utctime, tz.utcoffset(local))
331 self.assertEqual(local,
332 self.DT.replace(tzinfo=timezone.utc))
333
334 def test_comparison(self):
335 self.assertNotEqual(timezone(ZERO), timezone(HOUR))
336 self.assertEqual(timezone(HOUR), timezone(HOUR))
337 self.assertEqual(timezone(-5 * HOUR), timezone(-5 * HOUR, 'EST'))
338 with self.assertRaises(TypeError): timezone(ZERO) < timezone(ZERO)
339 self.assertIn(timezone(ZERO), {timezone(ZERO)})
Georg Brandl0085a242012-09-22 09:23:12 +0200340 self.assertTrue(timezone(ZERO) != None)
341 self.assertFalse(timezone(ZERO) == None)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000342
343 def test_aware_datetime(self):
344 # test that timezone instances can be used by datetime
345 t = datetime(1, 1, 1)
346 for tz in [timezone.min, timezone.max, timezone.utc]:
347 self.assertEqual(tz.tzname(t),
348 t.replace(tzinfo=tz).tzname())
349 self.assertEqual(tz.utcoffset(t),
350 t.replace(tzinfo=tz).utcoffset())
351 self.assertEqual(tz.dst(t),
352 t.replace(tzinfo=tz).dst())
353
Serhiy Storchakae28209f2015-11-16 11:12:58 +0200354 def test_pickle(self):
355 for tz in self.ACDT, self.EST, timezone.min, timezone.max:
356 for pickler, unpickler, proto in pickle_choices:
357 tz_copy = unpickler.loads(pickler.dumps(tz, proto))
358 self.assertEqual(tz_copy, tz)
359 tz = timezone.utc
360 for pickler, unpickler, proto in pickle_choices:
361 tz_copy = unpickler.loads(pickler.dumps(tz, proto))
362 self.assertIs(tz_copy, tz)
363
364 def test_copy(self):
365 for tz in self.ACDT, self.EST, timezone.min, timezone.max:
366 tz_copy = copy.copy(tz)
367 self.assertEqual(tz_copy, tz)
368 tz = timezone.utc
369 tz_copy = copy.copy(tz)
370 self.assertIs(tz_copy, tz)
371
372 def test_deepcopy(self):
373 for tz in self.ACDT, self.EST, timezone.min, timezone.max:
374 tz_copy = copy.deepcopy(tz)
375 self.assertEqual(tz_copy, tz)
376 tz = timezone.utc
377 tz_copy = copy.deepcopy(tz)
378 self.assertIs(tz_copy, tz)
379
380
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000381#############################################################################
Ezio Melotti85a86292013-08-17 16:57:41 +0300382# Base class for testing a particular aspect of timedelta, time, date and
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000383# datetime comparisons.
384
385class HarmlessMixedComparison:
386 # Test that __eq__ and __ne__ don't complain for mixed-type comparisons.
387
388 # Subclasses must define 'theclass', and theclass(1, 1, 1) must be a
389 # legit constructor.
390
391 def test_harmless_mixed_comparison(self):
392 me = self.theclass(1, 1, 1)
393
394 self.assertFalse(me == ())
395 self.assertTrue(me != ())
396 self.assertFalse(() == me)
397 self.assertTrue(() != me)
398
399 self.assertIn(me, [1, 20, [], me])
400 self.assertIn([], [me, 1, 20, []])
401
402 def test_harmful_mixed_comparison(self):
403 me = self.theclass(1, 1, 1)
404
405 self.assertRaises(TypeError, lambda: me < ())
406 self.assertRaises(TypeError, lambda: me <= ())
407 self.assertRaises(TypeError, lambda: me > ())
408 self.assertRaises(TypeError, lambda: me >= ())
409
410 self.assertRaises(TypeError, lambda: () < me)
411 self.assertRaises(TypeError, lambda: () <= me)
412 self.assertRaises(TypeError, lambda: () > me)
413 self.assertRaises(TypeError, lambda: () >= me)
414
415#############################################################################
416# timedelta tests
417
418class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
419
420 theclass = timedelta
421
422 def test_constructor(self):
423 eq = self.assertEqual
424 td = timedelta
425
426 # Check keyword args to constructor
427 eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
428 milliseconds=0, microseconds=0))
429 eq(td(1), td(days=1))
430 eq(td(0, 1), td(seconds=1))
431 eq(td(0, 0, 1), td(microseconds=1))
432 eq(td(weeks=1), td(days=7))
433 eq(td(days=1), td(hours=24))
434 eq(td(hours=1), td(minutes=60))
435 eq(td(minutes=1), td(seconds=60))
436 eq(td(seconds=1), td(milliseconds=1000))
437 eq(td(milliseconds=1), td(microseconds=1000))
438
439 # Check float args to constructor
440 eq(td(weeks=1.0/7), td(days=1))
441 eq(td(days=1.0/24), td(hours=1))
442 eq(td(hours=1.0/60), td(minutes=1))
443 eq(td(minutes=1.0/60), td(seconds=1))
444 eq(td(seconds=0.001), td(milliseconds=1))
445 eq(td(milliseconds=0.001), td(microseconds=1))
446
447 def test_computations(self):
448 eq = self.assertEqual
449 td = timedelta
450
451 a = td(7) # One week
452 b = td(0, 60) # One minute
453 c = td(0, 0, 1000) # One millisecond
454 eq(a+b+c, td(7, 60, 1000))
455 eq(a-b, td(6, 24*3600 - 60))
456 eq(b.__rsub__(a), td(6, 24*3600 - 60))
457 eq(-a, td(-7))
458 eq(+a, td(7))
459 eq(-b, td(-1, 24*3600 - 60))
460 eq(-c, td(-1, 24*3600 - 1, 999000))
461 eq(abs(a), a)
462 eq(abs(-a), a)
463 eq(td(6, 24*3600), a)
464 eq(td(0, 0, 60*1000000), b)
465 eq(a*10, td(70))
466 eq(a*10, 10*a)
467 eq(a*10, 10*a)
468 eq(b*10, td(0, 600))
469 eq(10*b, td(0, 600))
470 eq(b*10, td(0, 600))
471 eq(c*10, td(0, 0, 10000))
472 eq(10*c, td(0, 0, 10000))
473 eq(c*10, td(0, 0, 10000))
474 eq(a*-1, -a)
475 eq(b*-2, -b-b)
476 eq(c*-2, -c+-c)
477 eq(b*(60*24), (b*60)*24)
478 eq(b*(60*24), (60*b)*24)
479 eq(c*1000, td(0, 1))
480 eq(1000*c, td(0, 1))
481 eq(a//7, td(1))
482 eq(b//10, td(0, 6))
483 eq(c//1000, td(0, 0, 1))
484 eq(a//10, td(0, 7*24*360))
485 eq(a//3600000, td(0, 0, 7*24*1000))
486 eq(a/0.5, td(14))
487 eq(b/0.5, td(0, 120))
488 eq(a/7, td(1))
489 eq(b/10, td(0, 6))
490 eq(c/1000, td(0, 0, 1))
491 eq(a/10, td(0, 7*24*360))
492 eq(a/3600000, td(0, 0, 7*24*1000))
493
494 # Multiplication by float
495 us = td(microseconds=1)
496 eq((3*us) * 0.5, 2*us)
497 eq((5*us) * 0.5, 2*us)
498 eq(0.5 * (3*us), 2*us)
499 eq(0.5 * (5*us), 2*us)
500 eq((-3*us) * 0.5, -2*us)
501 eq((-5*us) * 0.5, -2*us)
502
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -0500503 # Issue #23521
504 eq(td(seconds=1) * 0.123456, td(microseconds=123456))
505 eq(td(seconds=1) * 0.6112295, td(microseconds=611229))
506
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000507 # Division by int and float
508 eq((3*us) / 2, 2*us)
509 eq((5*us) / 2, 2*us)
510 eq((-3*us) / 2.0, -2*us)
511 eq((-5*us) / 2.0, -2*us)
512 eq((3*us) / -2, -2*us)
513 eq((5*us) / -2, -2*us)
514 eq((3*us) / -2.0, -2*us)
515 eq((5*us) / -2.0, -2*us)
516 for i in range(-10, 10):
517 eq((i*us/3)//us, round(i/3))
518 for i in range(-10, 10):
519 eq((i*us/-3)//us, round(i/-3))
520
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -0500521 # Issue #23521
522 eq(td(seconds=1) / (1 / 0.6112295), td(microseconds=611229))
523
Alexander Belopolskyb6f5ec72011-04-05 20:07:38 -0400524 # Issue #11576
525 eq(td(999999999, 86399, 999999) - td(999999999, 86399, 999998),
526 td(0, 0, 1))
527 eq(td(999999999, 1, 1) - td(999999999, 1, 0),
528 td(0, 0, 1))
529
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000530 def test_disallowed_computations(self):
531 a = timedelta(42)
532
533 # Add/sub ints or floats should be illegal
534 for i in 1, 1.0:
535 self.assertRaises(TypeError, lambda: a+i)
536 self.assertRaises(TypeError, lambda: a-i)
537 self.assertRaises(TypeError, lambda: i+a)
538 self.assertRaises(TypeError, lambda: i-a)
539
540 # Division of int by timedelta doesn't make sense.
541 # Division by zero doesn't make sense.
542 zero = 0
543 self.assertRaises(TypeError, lambda: zero // a)
544 self.assertRaises(ZeroDivisionError, lambda: a // zero)
545 self.assertRaises(ZeroDivisionError, lambda: a / zero)
546 self.assertRaises(ZeroDivisionError, lambda: a / 0.0)
547 self.assertRaises(TypeError, lambda: a / '')
548
Eric Smith3ab08ca2010-12-04 15:17:38 +0000549 @support.requires_IEEE_754
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000550 def test_disallowed_special(self):
551 a = timedelta(42)
552 self.assertRaises(ValueError, a.__mul__, NAN)
553 self.assertRaises(ValueError, a.__truediv__, NAN)
554
555 def test_basic_attributes(self):
556 days, seconds, us = 1, 7, 31
557 td = timedelta(days, seconds, us)
558 self.assertEqual(td.days, days)
559 self.assertEqual(td.seconds, seconds)
560 self.assertEqual(td.microseconds, us)
561
562 def test_total_seconds(self):
563 td = timedelta(days=365)
564 self.assertEqual(td.total_seconds(), 31536000.0)
565 for total_seconds in [123456.789012, -123456.789012, 0.123456, 0, 1e6]:
566 td = timedelta(seconds=total_seconds)
567 self.assertEqual(td.total_seconds(), total_seconds)
568 # Issue8644: Test that td.total_seconds() has the same
569 # accuracy as td / timedelta(seconds=1).
570 for ms in [-1, -2, -123]:
571 td = timedelta(microseconds=ms)
572 self.assertEqual(td.total_seconds(), td / timedelta(seconds=1))
573
574 def test_carries(self):
575 t1 = timedelta(days=100,
576 weeks=-7,
577 hours=-24*(100-49),
578 minutes=-3,
579 seconds=12,
580 microseconds=(3*60 - 12) * 1e6 + 1)
581 t2 = timedelta(microseconds=1)
582 self.assertEqual(t1, t2)
583
584 def test_hash_equality(self):
585 t1 = timedelta(days=100,
586 weeks=-7,
587 hours=-24*(100-49),
588 minutes=-3,
589 seconds=12,
590 microseconds=(3*60 - 12) * 1000000)
591 t2 = timedelta()
592 self.assertEqual(hash(t1), hash(t2))
593
594 t1 += timedelta(weeks=7)
595 t2 += timedelta(days=7*7)
596 self.assertEqual(t1, t2)
597 self.assertEqual(hash(t1), hash(t2))
598
599 d = {t1: 1}
600 d[t2] = 2
601 self.assertEqual(len(d), 1)
602 self.assertEqual(d[t1], 2)
603
604 def test_pickling(self):
605 args = 12, 34, 56
606 orig = timedelta(*args)
607 for pickler, unpickler, proto in pickle_choices:
608 green = pickler.dumps(orig, proto)
609 derived = unpickler.loads(green)
610 self.assertEqual(orig, derived)
611
612 def test_compare(self):
613 t1 = timedelta(2, 3, 4)
614 t2 = timedelta(2, 3, 4)
615 self.assertEqual(t1, t2)
616 self.assertTrue(t1 <= t2)
617 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200618 self.assertFalse(t1 != t2)
619 self.assertFalse(t1 < t2)
620 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000621
622 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
623 t2 = timedelta(*args) # this is larger than t1
624 self.assertTrue(t1 < t2)
625 self.assertTrue(t2 > t1)
626 self.assertTrue(t1 <= t2)
627 self.assertTrue(t2 >= t1)
628 self.assertTrue(t1 != t2)
629 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200630 self.assertFalse(t1 == t2)
631 self.assertFalse(t2 == t1)
632 self.assertFalse(t1 > t2)
633 self.assertFalse(t2 < t1)
634 self.assertFalse(t1 >= t2)
635 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000636
637 for badarg in OTHERSTUFF:
638 self.assertEqual(t1 == badarg, False)
639 self.assertEqual(t1 != badarg, True)
640 self.assertEqual(badarg == t1, False)
641 self.assertEqual(badarg != t1, True)
642
643 self.assertRaises(TypeError, lambda: t1 <= badarg)
644 self.assertRaises(TypeError, lambda: t1 < badarg)
645 self.assertRaises(TypeError, lambda: t1 > badarg)
646 self.assertRaises(TypeError, lambda: t1 >= badarg)
647 self.assertRaises(TypeError, lambda: badarg <= t1)
648 self.assertRaises(TypeError, lambda: badarg < t1)
649 self.assertRaises(TypeError, lambda: badarg > t1)
650 self.assertRaises(TypeError, lambda: badarg >= t1)
651
652 def test_str(self):
653 td = timedelta
654 eq = self.assertEqual
655
656 eq(str(td(1)), "1 day, 0:00:00")
657 eq(str(td(-1)), "-1 day, 0:00:00")
658 eq(str(td(2)), "2 days, 0:00:00")
659 eq(str(td(-2)), "-2 days, 0:00:00")
660
661 eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
662 eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
663 eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
664 "-210 days, 23:12:34")
665
666 eq(str(td(milliseconds=1)), "0:00:00.001000")
667 eq(str(td(microseconds=3)), "0:00:00.000003")
668
669 eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
670 microseconds=999999)),
671 "999999999 days, 23:59:59.999999")
672
673 def test_repr(self):
674 name = 'datetime.' + self.theclass.__name__
675 self.assertEqual(repr(self.theclass(1)),
Utkarsh Upadhyaycc5a65c2017-07-25 23:51:33 +0200676 "%s(days=1)" % name)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000677 self.assertEqual(repr(self.theclass(10, 2)),
Utkarsh Upadhyaycc5a65c2017-07-25 23:51:33 +0200678 "%s(days=10, seconds=2)" % name)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000679 self.assertEqual(repr(self.theclass(-10, 2, 400000)),
Utkarsh Upadhyaycc5a65c2017-07-25 23:51:33 +0200680 "%s(days=-10, seconds=2, microseconds=400000)" % name)
681 self.assertEqual(repr(self.theclass(seconds=60)),
682 "%s(seconds=60)" % name)
683 self.assertEqual(repr(self.theclass()),
684 "%s(0)" % name)
685 self.assertEqual(repr(self.theclass(microseconds=100)),
686 "%s(microseconds=100)" % name)
687 self.assertEqual(repr(self.theclass(days=1, microseconds=100)),
688 "%s(days=1, microseconds=100)" % name)
689 self.assertEqual(repr(self.theclass(seconds=1, microseconds=100)),
690 "%s(seconds=1, microseconds=100)" % name)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000691
692 def test_roundtrip(self):
693 for td in (timedelta(days=999999999, hours=23, minutes=59,
694 seconds=59, microseconds=999999),
695 timedelta(days=-999999999),
696 timedelta(days=-999999999, seconds=1),
697 timedelta(days=1, seconds=2, microseconds=3)):
698
699 # Verify td -> string -> td identity.
700 s = repr(td)
701 self.assertTrue(s.startswith('datetime.'))
702 s = s[9:]
703 td2 = eval(s)
704 self.assertEqual(td, td2)
705
706 # Verify identity via reconstructing from pieces.
707 td2 = timedelta(td.days, td.seconds, td.microseconds)
708 self.assertEqual(td, td2)
709
710 def test_resolution_info(self):
711 self.assertIsInstance(timedelta.min, timedelta)
712 self.assertIsInstance(timedelta.max, timedelta)
713 self.assertIsInstance(timedelta.resolution, timedelta)
714 self.assertTrue(timedelta.max > timedelta.min)
715 self.assertEqual(timedelta.min, timedelta(-999999999))
716 self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
717 self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
718
719 def test_overflow(self):
720 tiny = timedelta.resolution
721
722 td = timedelta.min + tiny
723 td -= tiny # no problem
724 self.assertRaises(OverflowError, td.__sub__, tiny)
725 self.assertRaises(OverflowError, td.__add__, -tiny)
726
727 td = timedelta.max - tiny
728 td += tiny # no problem
729 self.assertRaises(OverflowError, td.__add__, tiny)
730 self.assertRaises(OverflowError, td.__sub__, -tiny)
731
732 self.assertRaises(OverflowError, lambda: -timedelta.max)
733
734 day = timedelta(1)
735 self.assertRaises(OverflowError, day.__mul__, 10**9)
736 self.assertRaises(OverflowError, day.__mul__, 1e9)
737 self.assertRaises(OverflowError, day.__truediv__, 1e-20)
738 self.assertRaises(OverflowError, day.__truediv__, 1e-10)
739 self.assertRaises(OverflowError, day.__truediv__, 9e-10)
740
Eric Smith3ab08ca2010-12-04 15:17:38 +0000741 @support.requires_IEEE_754
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000742 def _test_overflow_special(self):
743 day = timedelta(1)
744 self.assertRaises(OverflowError, day.__mul__, INF)
745 self.assertRaises(OverflowError, day.__mul__, -INF)
746
747 def test_microsecond_rounding(self):
748 td = timedelta
749 eq = self.assertEqual
750
751 # Single-field rounding.
752 eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
753 eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
Victor Stinner69cc4872015-09-08 23:58:54 +0200754 eq(td(milliseconds=0.5/1000), td(microseconds=0))
755 eq(td(milliseconds=-0.5/1000), td(microseconds=-0))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000756 eq(td(milliseconds=0.6/1000), td(microseconds=1))
757 eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
Victor Stinner69cc4872015-09-08 23:58:54 +0200758 eq(td(milliseconds=1.5/1000), td(microseconds=2))
759 eq(td(milliseconds=-1.5/1000), td(microseconds=-2))
760 eq(td(seconds=0.5/10**6), td(microseconds=0))
761 eq(td(seconds=-0.5/10**6), td(microseconds=-0))
762 eq(td(seconds=1/2**7), td(microseconds=7812))
763 eq(td(seconds=-1/2**7), td(microseconds=-7812))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000764
765 # Rounding due to contributions from more than one field.
766 us_per_hour = 3600e6
767 us_per_day = us_per_hour * 24
768 eq(td(days=.4/us_per_day), td(0))
769 eq(td(hours=.2/us_per_hour), td(0))
Victor Stinnercd5d7652015-09-09 01:09:21 +0200770 eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000771
772 eq(td(days=-.4/us_per_day), td(0))
773 eq(td(hours=-.2/us_per_hour), td(0))
774 eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
775
Victor Stinner69cc4872015-09-08 23:58:54 +0200776 # Test for a patch in Issue 8860
777 eq(td(microseconds=0.5), 0.5*td(microseconds=1.0))
778 eq(td(microseconds=0.5)//td.resolution, 0.5*td.resolution//td.resolution)
779
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000780 def test_massive_normalization(self):
781 td = timedelta(microseconds=-1)
782 self.assertEqual((td.days, td.seconds, td.microseconds),
783 (-1, 24*3600-1, 999999))
784
785 def test_bool(self):
786 self.assertTrue(timedelta(1))
787 self.assertTrue(timedelta(0, 1))
788 self.assertTrue(timedelta(0, 0, 1))
789 self.assertTrue(timedelta(microseconds=1))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200790 self.assertFalse(timedelta(0))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000791
792 def test_subclass_timedelta(self):
793
794 class T(timedelta):
795 @staticmethod
796 def from_td(td):
797 return T(td.days, td.seconds, td.microseconds)
798
799 def as_hours(self):
800 sum = (self.days * 24 +
801 self.seconds / 3600.0 +
802 self.microseconds / 3600e6)
803 return round(sum)
804
805 t1 = T(days=1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200806 self.assertIs(type(t1), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000807 self.assertEqual(t1.as_hours(), 24)
808
809 t2 = T(days=-1, seconds=-3600)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200810 self.assertIs(type(t2), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000811 self.assertEqual(t2.as_hours(), -25)
812
813 t3 = t1 + t2
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200814 self.assertIs(type(t3), timedelta)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000815 t4 = T.from_td(t3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200816 self.assertIs(type(t4), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000817 self.assertEqual(t3.days, t4.days)
818 self.assertEqual(t3.seconds, t4.seconds)
819 self.assertEqual(t3.microseconds, t4.microseconds)
820 self.assertEqual(str(t3), str(t4))
821 self.assertEqual(t4.as_hours(), -1)
822
Paul Ganssle89427cd2019-02-04 14:42:04 -0500823 def test_subclass_date(self):
824 class DateSubclass(date):
825 pass
826
827 d1 = DateSubclass(2018, 1, 5)
828 td = timedelta(days=1)
829
830 tests = [
831 ('add', lambda d, t: d + t, DateSubclass(2018, 1, 6)),
832 ('radd', lambda d, t: t + d, DateSubclass(2018, 1, 6)),
833 ('sub', lambda d, t: d - t, DateSubclass(2018, 1, 4)),
834 ]
835
836 for name, func, expected in tests:
837 with self.subTest(name):
838 act = func(d1, td)
839 self.assertEqual(act, expected)
840 self.assertIsInstance(act, DateSubclass)
841
842 def test_subclass_datetime(self):
843 class DateTimeSubclass(datetime):
844 pass
845
846 d1 = DateTimeSubclass(2018, 1, 5, 12, 30)
847 td = timedelta(days=1, minutes=30)
848
849 tests = [
850 ('add', lambda d, t: d + t, DateTimeSubclass(2018, 1, 6, 13)),
851 ('radd', lambda d, t: t + d, DateTimeSubclass(2018, 1, 6, 13)),
852 ('sub', lambda d, t: d - t, DateTimeSubclass(2018, 1, 4, 12)),
853 ]
854
855 for name, func, expected in tests:
856 with self.subTest(name):
857 act = func(d1, td)
858 self.assertEqual(act, expected)
859 self.assertIsInstance(act, DateTimeSubclass)
860
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000861 def test_division(self):
862 t = timedelta(hours=1, minutes=24, seconds=19)
863 second = timedelta(seconds=1)
864 self.assertEqual(t / second, 5059.0)
865 self.assertEqual(t // second, 5059)
866
867 t = timedelta(minutes=2, seconds=30)
868 minute = timedelta(minutes=1)
869 self.assertEqual(t / minute, 2.5)
870 self.assertEqual(t // minute, 2)
871
872 zerotd = timedelta(0)
873 self.assertRaises(ZeroDivisionError, truediv, t, zerotd)
874 self.assertRaises(ZeroDivisionError, floordiv, t, zerotd)
875
876 # self.assertRaises(TypeError, truediv, t, 2)
877 # note: floor division of a timedelta by an integer *is*
878 # currently permitted.
879
880 def test_remainder(self):
881 t = timedelta(minutes=2, seconds=30)
882 minute = timedelta(minutes=1)
883 r = t % minute
884 self.assertEqual(r, timedelta(seconds=30))
885
886 t = timedelta(minutes=-2, seconds=30)
887 r = t % minute
888 self.assertEqual(r, timedelta(seconds=30))
889
890 zerotd = timedelta(0)
891 self.assertRaises(ZeroDivisionError, mod, t, zerotd)
892
893 self.assertRaises(TypeError, mod, t, 10)
894
895 def test_divmod(self):
896 t = timedelta(minutes=2, seconds=30)
897 minute = timedelta(minutes=1)
898 q, r = divmod(t, minute)
899 self.assertEqual(q, 2)
900 self.assertEqual(r, timedelta(seconds=30))
901
902 t = timedelta(minutes=-2, seconds=30)
903 q, r = divmod(t, minute)
904 self.assertEqual(q, -2)
905 self.assertEqual(r, timedelta(seconds=30))
906
907 zerotd = timedelta(0)
908 self.assertRaises(ZeroDivisionError, divmod, t, zerotd)
909
910 self.assertRaises(TypeError, divmod, t, 10)
911
Oren Milman865e4b42017-09-19 15:58:11 +0300912 def test_issue31293(self):
913 # The interpreter shouldn't crash in case a timedelta is divided or
914 # multiplied by a float with a bad as_integer_ratio() method.
915 def get_bad_float(bad_ratio):
916 class BadFloat(float):
917 def as_integer_ratio(self):
918 return bad_ratio
919 return BadFloat()
920
921 with self.assertRaises(TypeError):
922 timedelta() / get_bad_float(1 << 1000)
923 with self.assertRaises(TypeError):
924 timedelta() * get_bad_float(1 << 1000)
925
926 for bad_ratio in [(), (42, ), (1, 2, 3)]:
927 with self.assertRaises(ValueError):
928 timedelta() / get_bad_float(bad_ratio)
929 with self.assertRaises(ValueError):
930 timedelta() * get_bad_float(bad_ratio)
931
Serhiy Storchaka4ffd4652017-10-23 17:12:28 +0300932 def test_issue31752(self):
933 # The interpreter shouldn't crash because divmod() returns negative
934 # remainder.
935 class BadInt(int):
936 def __mul__(self, other):
937 return Prod()
Serhiy Storchaka3ec0f492018-11-20 20:41:09 +0200938 def __rmul__(self, other):
939 return Prod()
940 def __floordiv__(self, other):
941 return Prod()
942 def __rfloordiv__(self, other):
943 return Prod()
Serhiy Storchaka4ffd4652017-10-23 17:12:28 +0300944
945 class Prod:
Serhiy Storchaka3ec0f492018-11-20 20:41:09 +0200946 def __add__(self, other):
947 return Sum()
Serhiy Storchaka4ffd4652017-10-23 17:12:28 +0300948 def __radd__(self, other):
949 return Sum()
950
951 class Sum(int):
952 def __divmod__(self, other):
Serhiy Storchaka3ec0f492018-11-20 20:41:09 +0200953 return divmodresult
Serhiy Storchaka4ffd4652017-10-23 17:12:28 +0300954
Serhiy Storchaka3ec0f492018-11-20 20:41:09 +0200955 for divmodresult in [None, (), (0, 1, 2), (0, -1)]:
956 with self.subTest(divmodresult=divmodresult):
957 # The following examples should not crash.
958 try:
959 timedelta(microseconds=BadInt(1))
960 except TypeError:
961 pass
962 try:
963 timedelta(hours=BadInt(1))
964 except TypeError:
965 pass
966 try:
967 timedelta(weeks=BadInt(1))
968 except (TypeError, ValueError):
969 pass
970 try:
971 timedelta(1) * BadInt(1)
972 except (TypeError, ValueError):
973 pass
974 try:
975 BadInt(1) * timedelta(1)
976 except TypeError:
977 pass
978 try:
979 timedelta(1) // BadInt(1)
980 except TypeError:
981 pass
Serhiy Storchaka4ffd4652017-10-23 17:12:28 +0300982
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000983
984#############################################################################
985# date tests
986
987class TestDateOnly(unittest.TestCase):
988 # Tests here won't pass if also run on datetime objects, so don't
989 # subclass this to test datetimes too.
990
991 def test_delta_non_days_ignored(self):
992 dt = date(2000, 1, 2)
993 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
994 microseconds=5)
995 days = timedelta(delta.days)
996 self.assertEqual(days, timedelta(1))
997
998 dt2 = dt + delta
999 self.assertEqual(dt2, dt + days)
1000
1001 dt2 = delta + dt
1002 self.assertEqual(dt2, dt + days)
1003
1004 dt2 = dt - delta
1005 self.assertEqual(dt2, dt - days)
1006
1007 delta = -delta
1008 days = timedelta(delta.days)
1009 self.assertEqual(days, timedelta(-2))
1010
1011 dt2 = dt + delta
1012 self.assertEqual(dt2, dt + days)
1013
1014 dt2 = delta + dt
1015 self.assertEqual(dt2, dt + days)
1016
1017 dt2 = dt - delta
1018 self.assertEqual(dt2, dt - days)
1019
1020class SubclassDate(date):
1021 sub_var = 1
1022
1023class TestDate(HarmlessMixedComparison, unittest.TestCase):
1024 # Tests here should pass for both dates and datetimes, except for a
1025 # few tests that TestDateTime overrides.
1026
1027 theclass = date
1028
1029 def test_basic_attributes(self):
1030 dt = self.theclass(2002, 3, 1)
1031 self.assertEqual(dt.year, 2002)
1032 self.assertEqual(dt.month, 3)
1033 self.assertEqual(dt.day, 1)
1034
1035 def test_roundtrip(self):
1036 for dt in (self.theclass(1, 2, 3),
1037 self.theclass.today()):
1038 # Verify dt -> string -> date identity.
1039 s = repr(dt)
1040 self.assertTrue(s.startswith('datetime.'))
1041 s = s[9:]
1042 dt2 = eval(s)
1043 self.assertEqual(dt, dt2)
1044
1045 # Verify identity via reconstructing from pieces.
1046 dt2 = self.theclass(dt.year, dt.month, dt.day)
1047 self.assertEqual(dt, dt2)
1048
1049 def test_ordinal_conversions(self):
1050 # Check some fixed values.
1051 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
1052 (1, 12, 31, 365),
1053 (2, 1, 1, 366),
1054 # first example from "Calendrical Calculations"
1055 (1945, 11, 12, 710347)]:
1056 d = self.theclass(y, m, d)
1057 self.assertEqual(n, d.toordinal())
1058 fromord = self.theclass.fromordinal(n)
1059 self.assertEqual(d, fromord)
1060 if hasattr(fromord, "hour"):
1061 # if we're checking something fancier than a date, verify
1062 # the extra fields have been zeroed out
1063 self.assertEqual(fromord.hour, 0)
1064 self.assertEqual(fromord.minute, 0)
1065 self.assertEqual(fromord.second, 0)
1066 self.assertEqual(fromord.microsecond, 0)
1067
1068 # Check first and last days of year spottily across the whole
1069 # range of years supported.
1070 for year in range(MINYEAR, MAXYEAR+1, 7):
1071 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
1072 d = self.theclass(year, 1, 1)
1073 n = d.toordinal()
1074 d2 = self.theclass.fromordinal(n)
1075 self.assertEqual(d, d2)
1076 # Verify that moving back a day gets to the end of year-1.
1077 if year > 1:
1078 d = self.theclass.fromordinal(n-1)
1079 d2 = self.theclass(year-1, 12, 31)
1080 self.assertEqual(d, d2)
1081 self.assertEqual(d2.toordinal(), n-1)
1082
1083 # Test every day in a leap-year and a non-leap year.
1084 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
1085 for year, isleap in (2000, True), (2002, False):
1086 n = self.theclass(year, 1, 1).toordinal()
1087 for month, maxday in zip(range(1, 13), dim):
1088 if month == 2 and isleap:
1089 maxday += 1
1090 for day in range(1, maxday+1):
1091 d = self.theclass(year, month, day)
1092 self.assertEqual(d.toordinal(), n)
1093 self.assertEqual(d, self.theclass.fromordinal(n))
1094 n += 1
1095
1096 def test_extreme_ordinals(self):
1097 a = self.theclass.min
1098 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
1099 aord = a.toordinal()
1100 b = a.fromordinal(aord)
1101 self.assertEqual(a, b)
1102
1103 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
1104
1105 b = a + timedelta(days=1)
1106 self.assertEqual(b.toordinal(), aord + 1)
1107 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
1108
1109 a = self.theclass.max
1110 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
1111 aord = a.toordinal()
1112 b = a.fromordinal(aord)
1113 self.assertEqual(a, b)
1114
1115 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
1116
1117 b = a - timedelta(days=1)
1118 self.assertEqual(b.toordinal(), aord - 1)
1119 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
1120
1121 def test_bad_constructor_arguments(self):
1122 # bad years
1123 self.theclass(MINYEAR, 1, 1) # no exception
1124 self.theclass(MAXYEAR, 1, 1) # no exception
1125 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1126 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1127 # bad months
1128 self.theclass(2000, 1, 1) # no exception
1129 self.theclass(2000, 12, 1) # no exception
1130 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1131 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1132 # bad days
1133 self.theclass(2000, 2, 29) # no exception
1134 self.theclass(2004, 2, 29) # no exception
1135 self.theclass(2400, 2, 29) # no exception
1136 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1137 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1138 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1139 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1140 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1141 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1142
1143 def test_hash_equality(self):
1144 d = self.theclass(2000, 12, 31)
1145 # same thing
1146 e = self.theclass(2000, 12, 31)
1147 self.assertEqual(d, e)
1148 self.assertEqual(hash(d), hash(e))
1149
1150 dic = {d: 1}
1151 dic[e] = 2
1152 self.assertEqual(len(dic), 1)
1153 self.assertEqual(dic[d], 2)
1154 self.assertEqual(dic[e], 2)
1155
1156 d = self.theclass(2001, 1, 1)
1157 # same thing
1158 e = self.theclass(2001, 1, 1)
1159 self.assertEqual(d, e)
1160 self.assertEqual(hash(d), hash(e))
1161
1162 dic = {d: 1}
1163 dic[e] = 2
1164 self.assertEqual(len(dic), 1)
1165 self.assertEqual(dic[d], 2)
1166 self.assertEqual(dic[e], 2)
1167
1168 def test_computations(self):
1169 a = self.theclass(2002, 1, 31)
1170 b = self.theclass(1956, 1, 31)
1171 c = self.theclass(2001,2,1)
1172
1173 diff = a-b
1174 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1175 self.assertEqual(diff.seconds, 0)
1176 self.assertEqual(diff.microseconds, 0)
1177
1178 day = timedelta(1)
1179 week = timedelta(7)
1180 a = self.theclass(2002, 3, 2)
1181 self.assertEqual(a + day, self.theclass(2002, 3, 3))
1182 self.assertEqual(day + a, self.theclass(2002, 3, 3))
1183 self.assertEqual(a - day, self.theclass(2002, 3, 1))
1184 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
1185 self.assertEqual(a + week, self.theclass(2002, 3, 9))
1186 self.assertEqual(a - week, self.theclass(2002, 2, 23))
1187 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
1188 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
1189 self.assertEqual((a + week) - a, week)
1190 self.assertEqual((a + day) - a, day)
1191 self.assertEqual((a - week) - a, -week)
1192 self.assertEqual((a - day) - a, -day)
1193 self.assertEqual(a - (a + week), -week)
1194 self.assertEqual(a - (a + day), -day)
1195 self.assertEqual(a - (a - week), week)
1196 self.assertEqual(a - (a - day), day)
1197 self.assertEqual(c - (c - day), day)
1198
1199 # Add/sub ints or floats should be illegal
1200 for i in 1, 1.0:
1201 self.assertRaises(TypeError, lambda: a+i)
1202 self.assertRaises(TypeError, lambda: a-i)
1203 self.assertRaises(TypeError, lambda: i+a)
1204 self.assertRaises(TypeError, lambda: i-a)
1205
1206 # delta - date is senseless.
1207 self.assertRaises(TypeError, lambda: day - a)
1208 # mixing date and (delta or date) via * or // is senseless
1209 self.assertRaises(TypeError, lambda: day * a)
1210 self.assertRaises(TypeError, lambda: a * day)
1211 self.assertRaises(TypeError, lambda: day // a)
1212 self.assertRaises(TypeError, lambda: a // day)
1213 self.assertRaises(TypeError, lambda: a * a)
1214 self.assertRaises(TypeError, lambda: a // a)
1215 # date + date is senseless
1216 self.assertRaises(TypeError, lambda: a + a)
1217
1218 def test_overflow(self):
1219 tiny = self.theclass.resolution
1220
1221 for delta in [tiny, timedelta(1), timedelta(2)]:
1222 dt = self.theclass.min + delta
1223 dt -= delta # no problem
1224 self.assertRaises(OverflowError, dt.__sub__, delta)
1225 self.assertRaises(OverflowError, dt.__add__, -delta)
1226
1227 dt = self.theclass.max - delta
1228 dt += delta # no problem
1229 self.assertRaises(OverflowError, dt.__add__, delta)
1230 self.assertRaises(OverflowError, dt.__sub__, -delta)
1231
1232 def test_fromtimestamp(self):
1233 import time
1234
1235 # Try an arbitrary fixed value.
1236 year, month, day = 1999, 9, 19
1237 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
1238 d = self.theclass.fromtimestamp(ts)
1239 self.assertEqual(d.year, year)
1240 self.assertEqual(d.month, month)
1241 self.assertEqual(d.day, day)
1242
1243 def test_insane_fromtimestamp(self):
1244 # It's possible that some platform maps time_t to double,
1245 # and that this test will fail there. This test should
1246 # exempt such platforms (provided they return reasonable
1247 # results!).
1248 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001249 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001250 insane)
1251
1252 def test_today(self):
1253 import time
1254
1255 # We claim that today() is like fromtimestamp(time.time()), so
1256 # prove it.
1257 for dummy in range(3):
1258 today = self.theclass.today()
1259 ts = time.time()
1260 todayagain = self.theclass.fromtimestamp(ts)
1261 if today == todayagain:
1262 break
1263 # There are several legit reasons that could fail:
1264 # 1. It recently became midnight, between the today() and the
1265 # time() calls.
1266 # 2. The platform time() has such fine resolution that we'll
1267 # never get the same value twice.
1268 # 3. The platform time() has poor resolution, and we just
1269 # happened to call today() right before a resolution quantum
1270 # boundary.
1271 # 4. The system clock got fiddled between calls.
1272 # In any case, wait a little while and try again.
1273 time.sleep(0.1)
1274
1275 # It worked or it didn't. If it didn't, assume it's reason #2, and
1276 # let the test pass if they're within half a second of each other.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001277 if today != todayagain:
1278 self.assertAlmostEqual(todayagain, today,
1279 delta=timedelta(seconds=0.5))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001280
1281 def test_weekday(self):
1282 for i in range(7):
1283 # March 4, 2002 is a Monday
1284 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
1285 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
1286 # January 2, 1956 is a Monday
1287 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
1288 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
1289
1290 def test_isocalendar(self):
1291 # Check examples from
1292 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1293 for i in range(7):
1294 d = self.theclass(2003, 12, 22+i)
1295 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
1296 d = self.theclass(2003, 12, 29) + timedelta(i)
1297 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
1298 d = self.theclass(2004, 1, 5+i)
1299 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
1300 d = self.theclass(2009, 12, 21+i)
1301 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
1302 d = self.theclass(2009, 12, 28) + timedelta(i)
1303 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
1304 d = self.theclass(2010, 1, 4+i)
1305 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
1306
1307 def test_iso_long_years(self):
1308 # Calculate long ISO years and compare to table from
1309 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1310 ISO_LONG_YEARS_TABLE = """
1311 4 32 60 88
1312 9 37 65 93
1313 15 43 71 99
1314 20 48 76
1315 26 54 82
1316
1317 105 133 161 189
1318 111 139 167 195
1319 116 144 172
1320 122 150 178
1321 128 156 184
1322
1323 201 229 257 285
1324 207 235 263 291
1325 212 240 268 296
1326 218 246 274
1327 224 252 280
1328
1329 303 331 359 387
1330 308 336 364 392
1331 314 342 370 398
1332 320 348 376
1333 325 353 381
1334 """
1335 iso_long_years = sorted(map(int, ISO_LONG_YEARS_TABLE.split()))
1336 L = []
1337 for i in range(400):
1338 d = self.theclass(2000+i, 12, 31)
1339 d1 = self.theclass(1600+i, 12, 31)
1340 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
1341 if d.isocalendar()[1] == 53:
1342 L.append(i)
1343 self.assertEqual(L, iso_long_years)
1344
1345 def test_isoformat(self):
1346 t = self.theclass(2, 3, 2)
1347 self.assertEqual(t.isoformat(), "0002-03-02")
1348
1349 def test_ctime(self):
1350 t = self.theclass(2002, 3, 2)
1351 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
1352
1353 def test_strftime(self):
1354 t = self.theclass(2005, 3, 2)
1355 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
1356 self.assertEqual(t.strftime(""), "") # SF bug #761337
1357 self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784
1358
1359 self.assertRaises(TypeError, t.strftime) # needs an arg
1360 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
1361 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
1362
1363 # test that unicode input is allowed (issue 2782)
1364 self.assertEqual(t.strftime("%m"), "03")
1365
1366 # A naive object replaces %z and %Z w/ empty strings.
1367 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1368
1369 #make sure that invalid format specifiers are handled correctly
1370 #self.assertRaises(ValueError, t.strftime, "%e")
1371 #self.assertRaises(ValueError, t.strftime, "%")
1372 #self.assertRaises(ValueError, t.strftime, "%#")
1373
1374 #oh well, some systems just ignore those invalid ones.
Martin Panter46f50722016-05-26 05:35:26 +00001375 #at least, exercise them to make sure that no crashes
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001376 #are generated
1377 for f in ["%e", "%", "%#"]:
1378 try:
1379 t.strftime(f)
1380 except ValueError:
1381 pass
1382
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03001383 # bpo-34482: Check that surrogates don't cause a crash.
1384 try:
1385 t.strftime('%y\ud800%m')
1386 except UnicodeEncodeError:
1387 pass
1388
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001389 #check that this standard extension works
1390 t.strftime("%f")
1391
MichaelSaah454b3d42019-01-14 05:23:39 -05001392 def test_strftime_trailing_percent(self):
1393 # bpo-35066: make sure trailing '%' doesn't cause
1394 # datetime's strftime to complain
1395 t = self.theclass(2005, 3, 2)
1396 try:
1397 _time.strftime('%')
1398 except ValueError:
1399 self.skipTest('time module does not support trailing %')
1400 self.assertEqual(t.strftime('%'), '%')
1401 self.assertEqual(t.strftime("m:%m d:%d y:%y %"), "m:03 d:02 y:05 %")
1402
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001403 def test_format(self):
1404 dt = self.theclass(2007, 9, 10)
1405 self.assertEqual(dt.__format__(''), str(dt))
1406
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02001407 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001408 dt.__format__(123)
1409
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001410 # check that a derived class's __str__() gets called
1411 class A(self.theclass):
1412 def __str__(self):
1413 return 'A'
1414 a = A(2007, 9, 10)
1415 self.assertEqual(a.__format__(''), 'A')
1416
1417 # check that a derived class's strftime gets called
1418 class B(self.theclass):
1419 def strftime(self, format_spec):
1420 return 'B'
1421 b = B(2007, 9, 10)
1422 self.assertEqual(b.__format__(''), str(dt))
1423
1424 for fmt in ["m:%m d:%d y:%y",
1425 "m:%m d:%d y:%y H:%H M:%M S:%S",
1426 "%z %Z",
1427 ]:
1428 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1429 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1430 self.assertEqual(b.__format__(fmt), 'B')
1431
1432 def test_resolution_info(self):
1433 # XXX: Should min and max respect subclassing?
1434 if issubclass(self.theclass, datetime):
1435 expected_class = datetime
1436 else:
1437 expected_class = date
1438 self.assertIsInstance(self.theclass.min, expected_class)
1439 self.assertIsInstance(self.theclass.max, expected_class)
1440 self.assertIsInstance(self.theclass.resolution, timedelta)
1441 self.assertTrue(self.theclass.max > self.theclass.min)
1442
1443 def test_extreme_timedelta(self):
1444 big = self.theclass.max - self.theclass.min
1445 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
1446 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
1447 # n == 315537897599999999 ~= 2**58.13
1448 justasbig = timedelta(0, 0, n)
1449 self.assertEqual(big, justasbig)
1450 self.assertEqual(self.theclass.min + big, self.theclass.max)
1451 self.assertEqual(self.theclass.max - big, self.theclass.min)
1452
1453 def test_timetuple(self):
1454 for i in range(7):
1455 # January 2, 1956 is a Monday (0)
1456 d = self.theclass(1956, 1, 2+i)
1457 t = d.timetuple()
1458 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
1459 # February 1, 1956 is a Wednesday (2)
1460 d = self.theclass(1956, 2, 1+i)
1461 t = d.timetuple()
1462 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
1463 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
1464 # of the year.
1465 d = self.theclass(1956, 3, 1+i)
1466 t = d.timetuple()
1467 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
1468 self.assertEqual(t.tm_year, 1956)
1469 self.assertEqual(t.tm_mon, 3)
1470 self.assertEqual(t.tm_mday, 1+i)
1471 self.assertEqual(t.tm_hour, 0)
1472 self.assertEqual(t.tm_min, 0)
1473 self.assertEqual(t.tm_sec, 0)
1474 self.assertEqual(t.tm_wday, (3+i)%7)
1475 self.assertEqual(t.tm_yday, 61+i)
1476 self.assertEqual(t.tm_isdst, -1)
1477
1478 def test_pickling(self):
1479 args = 6, 7, 23
1480 orig = self.theclass(*args)
1481 for pickler, unpickler, proto in pickle_choices:
1482 green = pickler.dumps(orig, proto)
1483 derived = unpickler.loads(green)
1484 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02001485 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001486
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02001487 def test_compat_unpickle(self):
1488 tests = [
1489 b"cdatetime\ndate\n(S'\\x07\\xdf\\x0b\\x1b'\ntR.",
1490 b'cdatetime\ndate\n(U\x04\x07\xdf\x0b\x1btR.',
1491 b'\x80\x02cdatetime\ndate\nU\x04\x07\xdf\x0b\x1b\x85R.',
1492 ]
1493 args = 2015, 11, 27
1494 expected = self.theclass(*args)
1495 for data in tests:
1496 for loads in pickle_loads:
1497 derived = loads(data, encoding='latin1')
1498 self.assertEqual(derived, expected)
1499
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001500 def test_compare(self):
1501 t1 = self.theclass(2, 3, 4)
1502 t2 = self.theclass(2, 3, 4)
1503 self.assertEqual(t1, t2)
1504 self.assertTrue(t1 <= t2)
1505 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001506 self.assertFalse(t1 != t2)
1507 self.assertFalse(t1 < t2)
1508 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001509
1510 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
1511 t2 = self.theclass(*args) # this is larger than t1
1512 self.assertTrue(t1 < t2)
1513 self.assertTrue(t2 > t1)
1514 self.assertTrue(t1 <= t2)
1515 self.assertTrue(t2 >= t1)
1516 self.assertTrue(t1 != t2)
1517 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001518 self.assertFalse(t1 == t2)
1519 self.assertFalse(t2 == t1)
1520 self.assertFalse(t1 > t2)
1521 self.assertFalse(t2 < t1)
1522 self.assertFalse(t1 >= t2)
1523 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001524
1525 for badarg in OTHERSTUFF:
1526 self.assertEqual(t1 == badarg, False)
1527 self.assertEqual(t1 != badarg, True)
1528 self.assertEqual(badarg == t1, False)
1529 self.assertEqual(badarg != t1, True)
1530
1531 self.assertRaises(TypeError, lambda: t1 < badarg)
1532 self.assertRaises(TypeError, lambda: t1 > badarg)
1533 self.assertRaises(TypeError, lambda: t1 >= badarg)
1534 self.assertRaises(TypeError, lambda: badarg <= t1)
1535 self.assertRaises(TypeError, lambda: badarg < t1)
1536 self.assertRaises(TypeError, lambda: badarg > t1)
1537 self.assertRaises(TypeError, lambda: badarg >= t1)
1538
1539 def test_mixed_compare(self):
1540 our = self.theclass(2000, 4, 5)
1541
1542 # Our class can be compared for equality to other classes
1543 self.assertEqual(our == 1, False)
1544 self.assertEqual(1 == our, False)
1545 self.assertEqual(our != 1, True)
1546 self.assertEqual(1 != our, True)
1547
1548 # But the ordering is undefined
1549 self.assertRaises(TypeError, lambda: our < 1)
1550 self.assertRaises(TypeError, lambda: 1 < our)
1551
1552 # Repeat those tests with a different class
1553
1554 class SomeClass:
1555 pass
1556
1557 their = SomeClass()
1558 self.assertEqual(our == their, False)
1559 self.assertEqual(their == our, False)
1560 self.assertEqual(our != their, True)
1561 self.assertEqual(their != our, True)
1562 self.assertRaises(TypeError, lambda: our < their)
1563 self.assertRaises(TypeError, lambda: their < our)
1564
1565 # However, if the other class explicitly defines ordering
1566 # relative to our class, it is allowed to do so
1567
1568 class LargerThanAnything:
1569 def __lt__(self, other):
1570 return False
1571 def __le__(self, other):
1572 return isinstance(other, LargerThanAnything)
1573 def __eq__(self, other):
1574 return isinstance(other, LargerThanAnything)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001575 def __gt__(self, other):
1576 return not isinstance(other, LargerThanAnything)
1577 def __ge__(self, other):
1578 return True
1579
1580 their = LargerThanAnything()
1581 self.assertEqual(our == their, False)
1582 self.assertEqual(their == our, False)
1583 self.assertEqual(our != their, True)
1584 self.assertEqual(their != our, True)
1585 self.assertEqual(our < their, True)
1586 self.assertEqual(their < our, False)
1587
1588 def test_bool(self):
1589 # All dates are considered true.
1590 self.assertTrue(self.theclass.min)
1591 self.assertTrue(self.theclass.max)
1592
Alexander Belopolsky89da3492011-05-02 13:14:24 -04001593 def test_strftime_y2k(self):
1594 for y in (1, 49, 70, 99, 100, 999, 1000, 1970):
Florent Xicluna49ce0682011-11-01 12:56:14 +01001595 d = self.theclass(y, 1, 1)
1596 # Issue 13305: For years < 1000, the value is not always
1597 # padded to 4 digits across platforms. The C standard
1598 # assumes year >= 1900, so it does not specify the number
1599 # of digits.
1600 if d.strftime("%Y") != '%04d' % y:
1601 # Year 42 returns '42', not padded
1602 self.assertEqual(d.strftime("%Y"), '%d' % y)
Victor Stinner15a83e82016-03-12 08:16:48 +01001603 # '0042' is obtained anyway
1604 self.assertEqual(d.strftime("%4Y"), '%04d' % y)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001605
1606 def test_replace(self):
1607 cls = self.theclass
1608 args = [1, 2, 3]
1609 base = cls(*args)
1610 self.assertEqual(base, base.replace())
1611
1612 i = 0
1613 for name, newval in (("year", 2),
1614 ("month", 3),
1615 ("day", 4)):
1616 newargs = args[:]
1617 newargs[i] = newval
1618 expected = cls(*newargs)
1619 got = base.replace(**{name: newval})
1620 self.assertEqual(expected, got)
1621 i += 1
1622
1623 # Out of bounds.
1624 base = cls(2000, 2, 29)
1625 self.assertRaises(ValueError, base.replace, year=2001)
1626
Paul Ganssle191e9932017-11-09 16:34:29 -05001627 def test_subclass_replace(self):
1628 class DateSubclass(self.theclass):
1629 pass
1630
1631 dt = DateSubclass(2012, 1, 1)
1632 self.assertIs(type(dt.replace(year=2013)), DateSubclass)
1633
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001634 def test_subclass_date(self):
1635
1636 class C(self.theclass):
1637 theAnswer = 42
1638
1639 def __new__(cls, *args, **kws):
1640 temp = kws.copy()
1641 extra = temp.pop('extra')
1642 result = self.theclass.__new__(cls, *args, **temp)
1643 result.extra = extra
1644 return result
1645
1646 def newmeth(self, start):
1647 return start + self.year + self.month
1648
1649 args = 2003, 4, 14
1650
1651 dt1 = self.theclass(*args)
1652 dt2 = C(*args, **{'extra': 7})
1653
1654 self.assertEqual(dt2.__class__, C)
1655 self.assertEqual(dt2.theAnswer, 42)
1656 self.assertEqual(dt2.extra, 7)
1657 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1658 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1659
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05001660 def test_subclass_alternate_constructors(self):
1661 # Test that alternate constructors call the constructor
1662 class DateSubclass(self.theclass):
1663 def __new__(cls, *args, **kwargs):
1664 result = self.theclass.__new__(cls, *args, **kwargs)
1665 result.extra = 7
1666
1667 return result
1668
1669 args = (2003, 4, 14)
1670 d_ord = 731319 # Equivalent ordinal date
1671 d_isoformat = '2003-04-14' # Equivalent isoformat()
1672
1673 base_d = DateSubclass(*args)
1674 self.assertIsInstance(base_d, DateSubclass)
1675 self.assertEqual(base_d.extra, 7)
1676
1677 # Timestamp depends on time zone, so we'll calculate the equivalent here
1678 ts = datetime.combine(base_d, time(0)).timestamp()
1679
1680 test_cases = [
1681 ('fromordinal', (d_ord,)),
1682 ('fromtimestamp', (ts,)),
1683 ('fromisoformat', (d_isoformat,)),
1684 ]
1685
1686 for constr_name, constr_args in test_cases:
1687 for base_obj in (DateSubclass, base_d):
1688 # Test both the classmethod and method
1689 with self.subTest(base_obj_type=type(base_obj),
1690 constr_name=constr_name):
1691 constr = getattr(base_obj, constr_name)
1692
1693 dt = constr(*constr_args)
1694
1695 # Test that it creates the right subclass
1696 self.assertIsInstance(dt, DateSubclass)
1697
1698 # Test that it's equal to the base object
1699 self.assertEqual(dt, base_d)
1700
1701 # Test that it called the constructor
1702 self.assertEqual(dt.extra, 7)
1703
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001704 def test_pickling_subclass_date(self):
1705
1706 args = 6, 7, 23
1707 orig = SubclassDate(*args)
1708 for pickler, unpickler, proto in pickle_choices:
1709 green = pickler.dumps(orig, proto)
1710 derived = unpickler.loads(green)
1711 self.assertEqual(orig, derived)
1712
1713 def test_backdoor_resistance(self):
1714 # For fast unpickling, the constructor accepts a pickle byte string.
1715 # This is a low-overhead backdoor. A user can (by intent or
1716 # mistake) pass a string directly, which (if it's the right length)
1717 # will get treated like a pickle, and bypass the normal sanity
1718 # checks in the constructor. This can create insane objects.
1719 # The constructor doesn't want to burn the time to validate all
1720 # fields, but does check the month field. This stops, e.g.,
1721 # datetime.datetime('1995-03-25') from yielding an insane object.
1722 base = b'1995-03-25'
1723 if not issubclass(self.theclass, datetime):
1724 base = base[:4]
1725 for month_byte in b'9', b'\0', b'\r', b'\xff':
1726 self.assertRaises(TypeError, self.theclass,
1727 base[:2] + month_byte + base[3:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001728 if issubclass(self.theclass, datetime):
1729 # Good bytes, but bad tzinfo:
1730 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
1731 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001732
1733 for ord_byte in range(1, 13):
1734 # This shouldn't blow up because of the month byte alone. If
1735 # the implementation changes to do more-careful checking, it may
1736 # blow up because other fields are insane.
1737 self.theclass(base[:2] + bytes([ord_byte]) + base[3:])
1738
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001739 def test_fromisoformat(self):
1740 # Test that isoformat() is reversible
1741 base_dates = [
1742 (1, 1, 1),
1743 (1000, 2, 14),
1744 (1900, 1, 1),
1745 (2000, 2, 29),
1746 (2004, 11, 12),
1747 (2004, 4, 3),
1748 (2017, 5, 30)
1749 ]
1750
1751 for dt_tuple in base_dates:
1752 dt = self.theclass(*dt_tuple)
1753 dt_str = dt.isoformat()
1754 with self.subTest(dt_str=dt_str):
1755 dt_rt = self.theclass.fromisoformat(dt.isoformat())
1756
1757 self.assertEqual(dt, dt_rt)
1758
1759 def test_fromisoformat_subclass(self):
1760 class DateSubclass(self.theclass):
1761 pass
1762
1763 dt = DateSubclass(2014, 12, 14)
1764
1765 dt_rt = DateSubclass.fromisoformat(dt.isoformat())
1766
1767 self.assertIsInstance(dt_rt, DateSubclass)
1768
1769 def test_fromisoformat_fails(self):
1770 # Test that fromisoformat() fails on invalid values
1771 bad_strs = [
1772 '', # Empty string
Paul Ganssle096329f2018-08-23 11:06:20 -04001773 '\ud800', # bpo-34454: Surrogate code point
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001774 '009-03-04', # Not 10 characters
1775 '123456789', # Not a date
1776 '200a-12-04', # Invalid character in year
1777 '2009-1a-04', # Invalid character in month
1778 '2009-12-0a', # Invalid character in day
1779 '2009-01-32', # Invalid day
1780 '2009-02-29', # Invalid leap day
1781 '20090228', # Valid ISO8601 output not from isoformat()
Paul Ganssle096329f2018-08-23 11:06:20 -04001782 '2009\ud80002\ud80028', # Separators are surrogate codepoints
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001783 ]
1784
1785 for bad_str in bad_strs:
1786 with self.assertRaises(ValueError):
1787 self.theclass.fromisoformat(bad_str)
1788
1789 def test_fromisoformat_fails_typeerror(self):
1790 # Test that fromisoformat fails when passed the wrong type
1791 import io
1792
1793 bad_types = [b'2009-03-01', None, io.StringIO('2009-03-01')]
1794 for bad_type in bad_types:
1795 with self.assertRaises(TypeError):
1796 self.theclass.fromisoformat(bad_type)
1797
Paul Ganssle88c09372019-04-29 09:22:03 -04001798 def test_fromisocalendar(self):
1799 # For each test case, assert that fromisocalendar is the
1800 # inverse of the isocalendar function
1801 dates = [
1802 (2016, 4, 3),
1803 (2005, 1, 2), # (2004, 53, 7)
1804 (2008, 12, 30), # (2009, 1, 2)
1805 (2010, 1, 2), # (2009, 53, 6)
1806 (2009, 12, 31), # (2009, 53, 4)
1807 (1900, 1, 1), # Unusual non-leap year (year % 100 == 0)
1808 (1900, 12, 31),
1809 (2000, 1, 1), # Unusual leap year (year % 400 == 0)
1810 (2000, 12, 31),
1811 (2004, 1, 1), # Leap year
1812 (2004, 12, 31),
1813 (1, 1, 1),
1814 (9999, 12, 31),
1815 (MINYEAR, 1, 1),
1816 (MAXYEAR, 12, 31),
1817 ]
1818
1819 for datecomps in dates:
1820 with self.subTest(datecomps=datecomps):
1821 dobj = self.theclass(*datecomps)
1822 isocal = dobj.isocalendar()
1823
1824 d_roundtrip = self.theclass.fromisocalendar(*isocal)
1825
1826 self.assertEqual(dobj, d_roundtrip)
1827
1828 def test_fromisocalendar_value_errors(self):
1829 isocals = [
1830 (2019, 0, 1),
1831 (2019, -1, 1),
1832 (2019, 54, 1),
1833 (2019, 1, 0),
1834 (2019, 1, -1),
1835 (2019, 1, 8),
1836 (2019, 53, 1),
1837 (10000, 1, 1),
1838 (0, 1, 1),
1839 (9999999, 1, 1),
1840 (2<<32, 1, 1),
1841 (2019, 2<<32, 1),
1842 (2019, 1, 2<<32),
1843 ]
1844
1845 for isocal in isocals:
1846 with self.subTest(isocal=isocal):
1847 with self.assertRaises(ValueError):
1848 self.theclass.fromisocalendar(*isocal)
1849
1850 def test_fromisocalendar_type_errors(self):
1851 err_txformers = [
1852 str,
1853 float,
1854 lambda x: None,
1855 ]
1856
1857 # Take a valid base tuple and transform it to contain one argument
1858 # with the wrong type. Repeat this for each argument, e.g.
1859 # [("2019", 1, 1), (2019, "1", 1), (2019, 1, "1"), ...]
1860 isocals = []
1861 base = (2019, 1, 1)
1862 for i in range(3):
1863 for txformer in err_txformers:
1864 err_val = list(base)
1865 err_val[i] = txformer(err_val[i])
1866 isocals.append(tuple(err_val))
1867
1868 for isocal in isocals:
1869 with self.subTest(isocal=isocal):
1870 with self.assertRaises(TypeError):
1871 self.theclass.fromisocalendar(*isocal)
1872
1873
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001874#############################################################################
1875# datetime tests
1876
1877class SubclassDatetime(datetime):
1878 sub_var = 1
1879
1880class TestDateTime(TestDate):
1881
1882 theclass = datetime
1883
1884 def test_basic_attributes(self):
1885 dt = self.theclass(2002, 3, 1, 12, 0)
1886 self.assertEqual(dt.year, 2002)
1887 self.assertEqual(dt.month, 3)
1888 self.assertEqual(dt.day, 1)
1889 self.assertEqual(dt.hour, 12)
1890 self.assertEqual(dt.minute, 0)
1891 self.assertEqual(dt.second, 0)
1892 self.assertEqual(dt.microsecond, 0)
1893
1894 def test_basic_attributes_nonzero(self):
1895 # Make sure all attributes are non-zero so bugs in
1896 # bit-shifting access show up.
1897 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1898 self.assertEqual(dt.year, 2002)
1899 self.assertEqual(dt.month, 3)
1900 self.assertEqual(dt.day, 1)
1901 self.assertEqual(dt.hour, 12)
1902 self.assertEqual(dt.minute, 59)
1903 self.assertEqual(dt.second, 59)
1904 self.assertEqual(dt.microsecond, 8000)
1905
1906 def test_roundtrip(self):
1907 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1908 self.theclass.now()):
1909 # Verify dt -> string -> datetime identity.
1910 s = repr(dt)
1911 self.assertTrue(s.startswith('datetime.'))
1912 s = s[9:]
1913 dt2 = eval(s)
1914 self.assertEqual(dt, dt2)
1915
1916 # Verify identity via reconstructing from pieces.
1917 dt2 = self.theclass(dt.year, dt.month, dt.day,
1918 dt.hour, dt.minute, dt.second,
1919 dt.microsecond)
1920 self.assertEqual(dt, dt2)
1921
1922 def test_isoformat(self):
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001923 t = self.theclass(1, 2, 3, 4, 5, 1, 123)
1924 self.assertEqual(t.isoformat(), "0001-02-03T04:05:01.000123")
1925 self.assertEqual(t.isoformat('T'), "0001-02-03T04:05:01.000123")
1926 self.assertEqual(t.isoformat(' '), "0001-02-03 04:05:01.000123")
1927 self.assertEqual(t.isoformat('\x00'), "0001-02-03\x0004:05:01.000123")
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03001928 # bpo-34482: Check that surrogates are handled properly.
1929 self.assertEqual(t.isoformat('\ud800'),
1930 "0001-02-03\ud80004:05:01.000123")
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001931 self.assertEqual(t.isoformat(timespec='hours'), "0001-02-03T04")
1932 self.assertEqual(t.isoformat(timespec='minutes'), "0001-02-03T04:05")
1933 self.assertEqual(t.isoformat(timespec='seconds'), "0001-02-03T04:05:01")
1934 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1935 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000123")
1936 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01.000123")
1937 self.assertEqual(t.isoformat(sep=' ', timespec='minutes'), "0001-02-03 04:05")
1938 self.assertRaises(ValueError, t.isoformat, timespec='foo')
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03001939 # bpo-34482: Check that surrogates are handled properly.
1940 self.assertRaises(ValueError, t.isoformat, timespec='\ud800')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001941 # str is ISO format with the separator forced to a blank.
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001942 self.assertEqual(str(t), "0001-02-03 04:05:01.000123")
1943
1944 t = self.theclass(1, 2, 3, 4, 5, 1, 999500, tzinfo=timezone.utc)
1945 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999+00:00")
1946
1947 t = self.theclass(1, 2, 3, 4, 5, 1, 999500)
1948 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999")
1949
1950 t = self.theclass(1, 2, 3, 4, 5, 1)
1951 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01")
1952 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1953 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000000")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001954
1955 t = self.theclass(2, 3, 2)
1956 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1957 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1958 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1959 # str is ISO format with the separator forced to a blank.
1960 self.assertEqual(str(t), "0002-03-02 00:00:00")
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001961 # ISO format with timezone
1962 tz = FixedOffset(timedelta(seconds=16), 'XXX')
1963 t = self.theclass(2, 3, 2, tzinfo=tz)
1964 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00+00:00:16")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001965
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001966 def test_isoformat_timezone(self):
1967 tzoffsets = [
1968 ('05:00', timedelta(hours=5)),
1969 ('02:00', timedelta(hours=2)),
1970 ('06:27', timedelta(hours=6, minutes=27)),
1971 ('12:32:30', timedelta(hours=12, minutes=32, seconds=30)),
1972 ('02:04:09.123456', timedelta(hours=2, minutes=4, seconds=9, microseconds=123456))
1973 ]
1974
1975 tzinfos = [
1976 ('', None),
1977 ('+00:00', timezone.utc),
1978 ('+00:00', timezone(timedelta(0))),
1979 ]
1980
1981 tzinfos += [
1982 (prefix + expected, timezone(sign * td))
1983 for expected, td in tzoffsets
1984 for prefix, sign in [('-', -1), ('+', 1)]
1985 ]
1986
1987 dt_base = self.theclass(2016, 4, 1, 12, 37, 9)
1988 exp_base = '2016-04-01T12:37:09'
1989
1990 for exp_tz, tzi in tzinfos:
1991 dt = dt_base.replace(tzinfo=tzi)
1992 exp = exp_base + exp_tz
1993 with self.subTest(tzi=tzi):
1994 assert dt.isoformat() == exp
1995
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001996 def test_format(self):
1997 dt = self.theclass(2007, 9, 10, 4, 5, 1, 123)
1998 self.assertEqual(dt.__format__(''), str(dt))
1999
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02002000 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002001 dt.__format__(123)
2002
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002003 # check that a derived class's __str__() gets called
2004 class A(self.theclass):
2005 def __str__(self):
2006 return 'A'
2007 a = A(2007, 9, 10, 4, 5, 1, 123)
2008 self.assertEqual(a.__format__(''), 'A')
2009
2010 # check that a derived class's strftime gets called
2011 class B(self.theclass):
2012 def strftime(self, format_spec):
2013 return 'B'
2014 b = B(2007, 9, 10, 4, 5, 1, 123)
2015 self.assertEqual(b.__format__(''), str(dt))
2016
2017 for fmt in ["m:%m d:%d y:%y",
2018 "m:%m d:%d y:%y H:%H M:%M S:%S",
2019 "%z %Z",
2020 ]:
2021 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
2022 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
2023 self.assertEqual(b.__format__(fmt), 'B')
2024
2025 def test_more_ctime(self):
2026 # Test fields that TestDate doesn't touch.
2027 import time
2028
2029 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
2030 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
2031 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
2032 # out. The difference is that t.ctime() produces " 2" for the day,
2033 # but platform ctime() produces "02" for the day. According to
2034 # C99, t.ctime() is correct here.
2035 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
2036
2037 # So test a case where that difference doesn't matter.
2038 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
2039 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
2040
2041 def test_tz_independent_comparing(self):
2042 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
2043 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
2044 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
2045 self.assertEqual(dt1, dt3)
2046 self.assertTrue(dt2 > dt3)
2047
2048 # Make sure comparison doesn't forget microseconds, and isn't done
2049 # via comparing a float timestamp (an IEEE double doesn't have enough
Leo Ariasc3d95082018-02-03 18:36:10 -06002050 # precision to span microsecond resolution across years 1 through 9999,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002051 # so comparing via timestamp necessarily calls some distinct values
2052 # equal).
2053 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
2054 us = timedelta(microseconds=1)
2055 dt2 = dt1 + us
2056 self.assertEqual(dt2 - dt1, us)
2057 self.assertTrue(dt1 < dt2)
2058
2059 def test_strftime_with_bad_tzname_replace(self):
2060 # verify ok if tzinfo.tzname().replace() returns a non-string
2061 class MyTzInfo(FixedOffset):
2062 def tzname(self, dt):
2063 class MyStr(str):
2064 def replace(self, *args):
2065 return None
2066 return MyStr('name')
2067 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
2068 self.assertRaises(TypeError, t.strftime, '%Z')
2069
2070 def test_bad_constructor_arguments(self):
2071 # bad years
2072 self.theclass(MINYEAR, 1, 1) # no exception
2073 self.theclass(MAXYEAR, 1, 1) # no exception
2074 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
2075 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
2076 # bad months
2077 self.theclass(2000, 1, 1) # no exception
2078 self.theclass(2000, 12, 1) # no exception
2079 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
2080 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
2081 # bad days
2082 self.theclass(2000, 2, 29) # no exception
2083 self.theclass(2004, 2, 29) # no exception
2084 self.theclass(2400, 2, 29) # no exception
2085 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
2086 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
2087 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
2088 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
2089 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
2090 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
2091 # bad hours
2092 self.theclass(2000, 1, 31, 0) # no exception
2093 self.theclass(2000, 1, 31, 23) # no exception
2094 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
2095 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
2096 # bad minutes
2097 self.theclass(2000, 1, 31, 23, 0) # no exception
2098 self.theclass(2000, 1, 31, 23, 59) # no exception
2099 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
2100 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
2101 # bad seconds
2102 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
2103 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
2104 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
2105 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
2106 # bad microseconds
2107 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
2108 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
2109 self.assertRaises(ValueError, self.theclass,
2110 2000, 1, 31, 23, 59, 59, -1)
2111 self.assertRaises(ValueError, self.theclass,
2112 2000, 1, 31, 23, 59, 59,
2113 1000000)
Alexander Belopolsky47649ab2016-08-08 17:05:40 -04002114 # bad fold
2115 self.assertRaises(ValueError, self.theclass,
2116 2000, 1, 31, fold=-1)
2117 self.assertRaises(ValueError, self.theclass,
2118 2000, 1, 31, fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002119 # Positional fold:
2120 self.assertRaises(TypeError, self.theclass,
2121 2000, 1, 31, 23, 59, 59, 0, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04002122
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002123 def test_hash_equality(self):
2124 d = self.theclass(2000, 12, 31, 23, 30, 17)
2125 e = self.theclass(2000, 12, 31, 23, 30, 17)
2126 self.assertEqual(d, e)
2127 self.assertEqual(hash(d), hash(e))
2128
2129 dic = {d: 1}
2130 dic[e] = 2
2131 self.assertEqual(len(dic), 1)
2132 self.assertEqual(dic[d], 2)
2133 self.assertEqual(dic[e], 2)
2134
2135 d = self.theclass(2001, 1, 1, 0, 5, 17)
2136 e = self.theclass(2001, 1, 1, 0, 5, 17)
2137 self.assertEqual(d, e)
2138 self.assertEqual(hash(d), hash(e))
2139
2140 dic = {d: 1}
2141 dic[e] = 2
2142 self.assertEqual(len(dic), 1)
2143 self.assertEqual(dic[d], 2)
2144 self.assertEqual(dic[e], 2)
2145
2146 def test_computations(self):
2147 a = self.theclass(2002, 1, 31)
2148 b = self.theclass(1956, 1, 31)
2149 diff = a-b
2150 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
2151 self.assertEqual(diff.seconds, 0)
2152 self.assertEqual(diff.microseconds, 0)
2153 a = self.theclass(2002, 3, 2, 17, 6)
2154 millisec = timedelta(0, 0, 1000)
2155 hour = timedelta(0, 3600)
2156 day = timedelta(1)
2157 week = timedelta(7)
2158 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
2159 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
2160 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
2161 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
2162 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
2163 self.assertEqual(a - hour, a + -hour)
2164 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
2165 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
2166 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
2167 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
2168 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
2169 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
2170 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
2171 self.assertEqual((a + week) - a, week)
2172 self.assertEqual((a + day) - a, day)
2173 self.assertEqual((a + hour) - a, hour)
2174 self.assertEqual((a + millisec) - a, millisec)
2175 self.assertEqual((a - week) - a, -week)
2176 self.assertEqual((a - day) - a, -day)
2177 self.assertEqual((a - hour) - a, -hour)
2178 self.assertEqual((a - millisec) - a, -millisec)
2179 self.assertEqual(a - (a + week), -week)
2180 self.assertEqual(a - (a + day), -day)
2181 self.assertEqual(a - (a + hour), -hour)
2182 self.assertEqual(a - (a + millisec), -millisec)
2183 self.assertEqual(a - (a - week), week)
2184 self.assertEqual(a - (a - day), day)
2185 self.assertEqual(a - (a - hour), hour)
2186 self.assertEqual(a - (a - millisec), millisec)
2187 self.assertEqual(a + (week + day + hour + millisec),
2188 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
2189 self.assertEqual(a + (week + day + hour + millisec),
2190 (((a + week) + day) + hour) + millisec)
2191 self.assertEqual(a - (week + day + hour + millisec),
2192 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
2193 self.assertEqual(a - (week + day + hour + millisec),
2194 (((a - week) - day) - hour) - millisec)
2195 # Add/sub ints or floats should be illegal
2196 for i in 1, 1.0:
2197 self.assertRaises(TypeError, lambda: a+i)
2198 self.assertRaises(TypeError, lambda: a-i)
2199 self.assertRaises(TypeError, lambda: i+a)
2200 self.assertRaises(TypeError, lambda: i-a)
2201
2202 # delta - datetime is senseless.
2203 self.assertRaises(TypeError, lambda: day - a)
2204 # mixing datetime and (delta or datetime) via * or // is senseless
2205 self.assertRaises(TypeError, lambda: day * a)
2206 self.assertRaises(TypeError, lambda: a * day)
2207 self.assertRaises(TypeError, lambda: day // a)
2208 self.assertRaises(TypeError, lambda: a // day)
2209 self.assertRaises(TypeError, lambda: a * a)
2210 self.assertRaises(TypeError, lambda: a // a)
2211 # datetime + datetime is senseless
2212 self.assertRaises(TypeError, lambda: a + a)
2213
2214 def test_pickling(self):
2215 args = 6, 7, 23, 20, 59, 1, 64**2
2216 orig = self.theclass(*args)
2217 for pickler, unpickler, proto in pickle_choices:
2218 green = pickler.dumps(orig, proto)
2219 derived = unpickler.loads(green)
2220 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02002221 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002222
2223 def test_more_pickling(self):
2224 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
Serhiy Storchakabad12572014-12-15 14:03:42 +02002225 for proto in range(pickle.HIGHEST_PROTOCOL + 1):
2226 s = pickle.dumps(a, proto)
2227 b = pickle.loads(s)
2228 self.assertEqual(b.year, 2003)
2229 self.assertEqual(b.month, 2)
2230 self.assertEqual(b.day, 7)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002231
2232 def test_pickling_subclass_datetime(self):
2233 args = 6, 7, 23, 20, 59, 1, 64**2
2234 orig = SubclassDatetime(*args)
2235 for pickler, unpickler, proto in pickle_choices:
2236 green = pickler.dumps(orig, proto)
2237 derived = unpickler.loads(green)
2238 self.assertEqual(orig, derived)
2239
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02002240 def test_compat_unpickle(self):
2241 tests = [
2242 b'cdatetime\ndatetime\n('
2243 b"S'\\x07\\xdf\\x0b\\x1b\\x14;\\x01\\x00\\x10\\x00'\ntR.",
2244
2245 b'cdatetime\ndatetime\n('
2246 b'U\n\x07\xdf\x0b\x1b\x14;\x01\x00\x10\x00tR.',
2247
2248 b'\x80\x02cdatetime\ndatetime\n'
2249 b'U\n\x07\xdf\x0b\x1b\x14;\x01\x00\x10\x00\x85R.',
2250 ]
2251 args = 2015, 11, 27, 20, 59, 1, 64**2
2252 expected = self.theclass(*args)
2253 for data in tests:
2254 for loads in pickle_loads:
2255 derived = loads(data, encoding='latin1')
2256 self.assertEqual(derived, expected)
2257
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002258 def test_more_compare(self):
2259 # The test_compare() inherited from TestDate covers the error cases.
2260 # We just want to test lexicographic ordering on the members datetime
2261 # has that date lacks.
2262 args = [2000, 11, 29, 20, 58, 16, 999998]
2263 t1 = self.theclass(*args)
2264 t2 = self.theclass(*args)
2265 self.assertEqual(t1, t2)
2266 self.assertTrue(t1 <= t2)
2267 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002268 self.assertFalse(t1 != t2)
2269 self.assertFalse(t1 < t2)
2270 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002271
2272 for i in range(len(args)):
2273 newargs = args[:]
2274 newargs[i] = args[i] + 1
2275 t2 = self.theclass(*newargs) # this is larger than t1
2276 self.assertTrue(t1 < t2)
2277 self.assertTrue(t2 > t1)
2278 self.assertTrue(t1 <= t2)
2279 self.assertTrue(t2 >= t1)
2280 self.assertTrue(t1 != t2)
2281 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002282 self.assertFalse(t1 == t2)
2283 self.assertFalse(t2 == t1)
2284 self.assertFalse(t1 > t2)
2285 self.assertFalse(t2 < t1)
2286 self.assertFalse(t1 >= t2)
2287 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002288
2289
2290 # A helper for timestamp constructor tests.
2291 def verify_field_equality(self, expected, got):
2292 self.assertEqual(expected.tm_year, got.year)
2293 self.assertEqual(expected.tm_mon, got.month)
2294 self.assertEqual(expected.tm_mday, got.day)
2295 self.assertEqual(expected.tm_hour, got.hour)
2296 self.assertEqual(expected.tm_min, got.minute)
2297 self.assertEqual(expected.tm_sec, got.second)
2298
2299 def test_fromtimestamp(self):
2300 import time
2301
2302 ts = time.time()
2303 expected = time.localtime(ts)
2304 got = self.theclass.fromtimestamp(ts)
2305 self.verify_field_equality(expected, got)
2306
2307 def test_utcfromtimestamp(self):
2308 import time
2309
2310 ts = time.time()
2311 expected = time.gmtime(ts)
2312 got = self.theclass.utcfromtimestamp(ts)
2313 self.verify_field_equality(expected, got)
2314
Alexander Belopolskya4415142012-06-08 12:33:09 -04002315 # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in
2316 # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0).
2317 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
2318 def test_timestamp_naive(self):
2319 t = self.theclass(1970, 1, 1)
2320 self.assertEqual(t.timestamp(), 18000.0)
2321 t = self.theclass(1970, 1, 1, 1, 2, 3, 4)
2322 self.assertEqual(t.timestamp(),
2323 18000.0 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002324 # Missing hour
2325 t0 = self.theclass(2012, 3, 11, 2, 30)
2326 t1 = t0.replace(fold=1)
2327 self.assertEqual(self.theclass.fromtimestamp(t1.timestamp()),
2328 t0 - timedelta(hours=1))
2329 self.assertEqual(self.theclass.fromtimestamp(t0.timestamp()),
2330 t1 + timedelta(hours=1))
Alexander Belopolskya4415142012-06-08 12:33:09 -04002331 # Ambiguous hour defaults to DST
2332 t = self.theclass(2012, 11, 4, 1, 30)
2333 self.assertEqual(self.theclass.fromtimestamp(t.timestamp()), t)
2334
2335 # Timestamp may raise an overflow error on some platforms
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002336 # XXX: Do we care to support the first and last year?
2337 for t in [self.theclass(2,1,1), self.theclass(9998,12,12)]:
Alexander Belopolskya4415142012-06-08 12:33:09 -04002338 try:
2339 s = t.timestamp()
2340 except OverflowError:
2341 pass
2342 else:
2343 self.assertEqual(self.theclass.fromtimestamp(s), t)
2344
2345 def test_timestamp_aware(self):
2346 t = self.theclass(1970, 1, 1, tzinfo=timezone.utc)
2347 self.assertEqual(t.timestamp(), 0.0)
2348 t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone.utc)
2349 self.assertEqual(t.timestamp(),
2350 3600 + 2*60 + 3 + 4*1e-6)
2351 t = self.theclass(1970, 1, 1, 1, 2, 3, 4,
2352 tzinfo=timezone(timedelta(hours=-5), 'EST'))
2353 self.assertEqual(t.timestamp(),
2354 18000 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002355
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002356 @support.run_with_tz('MSK-03') # Something east of Greenwich
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002357 def test_microsecond_rounding(self):
Victor Stinner5ebfe422015-09-18 14:52:15 +02002358 for fts in [self.theclass.fromtimestamp,
2359 self.theclass.utcfromtimestamp]:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002360 zero = fts(0)
2361 self.assertEqual(zero.second, 0)
2362 self.assertEqual(zero.microsecond, 0)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002363 one = fts(1e-6)
Victor Stinner8050ca92012-03-14 00:17:05 +01002364 try:
2365 minus_one = fts(-1e-6)
2366 except OSError:
2367 # localtime(-1) and gmtime(-1) is not supported on Windows
2368 pass
2369 else:
2370 self.assertEqual(minus_one.second, 59)
2371 self.assertEqual(minus_one.microsecond, 999999)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002372
Victor Stinner8050ca92012-03-14 00:17:05 +01002373 t = fts(-1e-8)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002374 self.assertEqual(t, zero)
Victor Stinner8050ca92012-03-14 00:17:05 +01002375 t = fts(-9e-7)
2376 self.assertEqual(t, minus_one)
2377 t = fts(-1e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002378 self.assertEqual(t, zero)
Victor Stinner8820a352015-09-05 10:50:20 +02002379 t = fts(-1/2**7)
2380 self.assertEqual(t.second, 59)
Victor Stinner7667f582015-09-09 01:02:23 +02002381 self.assertEqual(t.microsecond, 992188)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002382
2383 t = fts(1e-7)
2384 self.assertEqual(t, zero)
2385 t = fts(9e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002386 self.assertEqual(t, one)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002387 t = fts(0.99999949)
2388 self.assertEqual(t.second, 0)
2389 self.assertEqual(t.microsecond, 999999)
2390 t = fts(0.9999999)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002391 self.assertEqual(t.second, 1)
2392 self.assertEqual(t.microsecond, 0)
Victor Stinneradfefa52015-09-04 23:57:25 +02002393 t = fts(1/2**7)
2394 self.assertEqual(t.second, 0)
Victor Stinner7667f582015-09-09 01:02:23 +02002395 self.assertEqual(t.microsecond, 7812)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002396
Victor Stinnerb67f0962017-02-10 10:34:02 +01002397 def test_timestamp_limits(self):
2398 # minimum timestamp
2399 min_dt = self.theclass.min.replace(tzinfo=timezone.utc)
2400 min_ts = min_dt.timestamp()
Victor Stinner6f37e362017-02-10 11:45:14 +01002401 try:
2402 # date 0001-01-01 00:00:00+00:00: timestamp=-62135596800
2403 self.assertEqual(self.theclass.fromtimestamp(min_ts, tz=timezone.utc),
2404 min_dt)
Victor Stinner2a35c932017-02-10 12:37:21 +01002405 except (OverflowError, OSError) as exc:
2406 # the date 0001-01-01 doesn't fit into 32-bit time_t,
2407 # or platform doesn't support such very old date
Victor Stinner6f37e362017-02-10 11:45:14 +01002408 self.skipTest(str(exc))
Victor Stinnerb67f0962017-02-10 10:34:02 +01002409
2410 # maximum timestamp: set seconds to zero to avoid rounding issues
2411 max_dt = self.theclass.max.replace(tzinfo=timezone.utc,
2412 second=0, microsecond=0)
2413 max_ts = max_dt.timestamp()
2414 # date 9999-12-31 23:59:00+00:00: timestamp 253402300740
2415 self.assertEqual(self.theclass.fromtimestamp(max_ts, tz=timezone.utc),
2416 max_dt)
2417
2418 # number of seconds greater than 1 year: make sure that the new date
2419 # is not valid in datetime.datetime limits
2420 delta = 3600 * 24 * 400
2421
2422 # too small
2423 ts = min_ts - delta
2424 # converting a Python int to C time_t can raise a OverflowError,
2425 # especially on 32-bit platforms.
2426 with self.assertRaises((ValueError, OverflowError)):
2427 self.theclass.fromtimestamp(ts)
2428 with self.assertRaises((ValueError, OverflowError)):
2429 self.theclass.utcfromtimestamp(ts)
2430
2431 # too big
2432 ts = max_dt.timestamp() + delta
2433 with self.assertRaises((ValueError, OverflowError)):
2434 self.theclass.fromtimestamp(ts)
2435 with self.assertRaises((ValueError, OverflowError)):
2436 self.theclass.utcfromtimestamp(ts)
2437
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002438 def test_insane_fromtimestamp(self):
2439 # It's possible that some platform maps time_t to double,
2440 # and that this test will fail there. This test should
2441 # exempt such platforms (provided they return reasonable
2442 # results!).
2443 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002444 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002445 insane)
2446
2447 def test_insane_utcfromtimestamp(self):
2448 # It's possible that some platform maps time_t to double,
2449 # and that this test will fail there. This test should
2450 # exempt such platforms (provided they return reasonable
2451 # results!).
2452 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002453 self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002454 insane)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002455
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002456 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2457 def test_negative_float_fromtimestamp(self):
2458 # The result is tz-dependent; at least test that this doesn't
2459 # fail (like it did before bug 1646728 was fixed).
2460 self.theclass.fromtimestamp(-1.05)
2461
2462 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2463 def test_negative_float_utcfromtimestamp(self):
2464 d = self.theclass.utcfromtimestamp(-1.05)
2465 self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
2466
2467 def test_utcnow(self):
2468 import time
2469
2470 # Call it a success if utcnow() and utcfromtimestamp() are within
2471 # a second of each other.
2472 tolerance = timedelta(seconds=1)
2473 for dummy in range(3):
2474 from_now = self.theclass.utcnow()
2475 from_timestamp = self.theclass.utcfromtimestamp(time.time())
2476 if abs(from_timestamp - from_now) <= tolerance:
2477 break
2478 # Else try again a few times.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002479 self.assertLessEqual(abs(from_timestamp - from_now), tolerance)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002480
2481 def test_strptime(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002482 string = '2004-12-01 13:02:47.197'
2483 format = '%Y-%m-%d %H:%M:%S.%f'
2484 expected = _strptime._strptime_datetime(self.theclass, string, format)
2485 got = self.theclass.strptime(string, format)
2486 self.assertEqual(expected, got)
2487 self.assertIs(type(expected), self.theclass)
2488 self.assertIs(type(got), self.theclass)
2489
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03002490 # bpo-34482: Check that surrogates are handled properly.
2491 inputs = [
2492 ('2004-12-01\ud80013:02:47.197', '%Y-%m-%d\ud800%H:%M:%S.%f'),
2493 ('2004\ud80012-01 13:02:47.197', '%Y\ud800%m-%d %H:%M:%S.%f'),
2494 ('2004-12-01 13:02\ud80047.197', '%Y-%m-%d %H:%M\ud800%S.%f'),
2495 ]
2496 for string, format in inputs:
2497 with self.subTest(string=string, format=format):
2498 expected = _strptime._strptime_datetime(self.theclass, string,
2499 format)
2500 got = self.theclass.strptime(string, format)
2501 self.assertEqual(expected, got)
2502
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002503 strptime = self.theclass.strptime
Mike Gleen6b9c2042019-06-18 19:14:57 +01002504
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002505 self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
2506 self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
Mario Corchero32318932017-10-26 01:35:41 +01002507 self.assertEqual(
2508 strptime("-00:02:01.000003", "%z").utcoffset(),
2509 -timedelta(minutes=2, seconds=1, microseconds=3)
2510 )
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002511 # Only local timezone and UTC are supported
2512 for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
2513 (-_time.timezone, _time.tzname[0])):
2514 if tzseconds < 0:
2515 sign = '-'
2516 seconds = -tzseconds
2517 else:
2518 sign ='+'
2519 seconds = tzseconds
2520 hours, minutes = divmod(seconds//60, 60)
2521 dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
Martin Panterfca22322015-11-16 09:22:19 +00002522 dt = strptime(dtstr, "%z %Z")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002523 self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
2524 self.assertEqual(dt.tzname(), tzname)
2525 # Can produce inconsistent datetime
2526 dtstr, fmt = "+1234 UTC", "%z %Z"
2527 dt = strptime(dtstr, fmt)
2528 self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
2529 self.assertEqual(dt.tzname(), 'UTC')
2530 # yet will roundtrip
2531 self.assertEqual(dt.strftime(fmt), dtstr)
2532
2533 # Produce naive datetime if no %z is provided
2534 self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
2535
2536 with self.assertRaises(ValueError): strptime("-2400", "%z")
2537 with self.assertRaises(ValueError): strptime("-000", "%z")
2538
Mike Gleen6b9c2042019-06-18 19:14:57 +01002539 def test_strptime_single_digit(self):
2540 # bpo-34903: Check that single digit dates and times are allowed.
2541
2542 strptime = self.theclass.strptime
2543
2544 with self.assertRaises(ValueError):
2545 # %y does require two digits.
2546 newdate = strptime('01/02/3 04:05:06', '%d/%m/%y %H:%M:%S')
2547 dt1 = self.theclass(2003, 2, 1, 4, 5, 6)
2548 dt2 = self.theclass(2003, 1, 2, 4, 5, 6)
2549 dt3 = self.theclass(2003, 2, 1, 0, 0, 0)
2550 dt4 = self.theclass(2003, 1, 25, 0, 0, 0)
2551 inputs = [
2552 ('%d', '1/02/03 4:5:6', '%d/%m/%y %H:%M:%S', dt1),
2553 ('%m', '01/2/03 4:5:6', '%d/%m/%y %H:%M:%S', dt1),
2554 ('%H', '01/02/03 4:05:06', '%d/%m/%y %H:%M:%S', dt1),
2555 ('%M', '01/02/03 04:5:06', '%d/%m/%y %H:%M:%S', dt1),
2556 ('%S', '01/02/03 04:05:6', '%d/%m/%y %H:%M:%S', dt1),
2557 ('%j', '2/03 04am:05:06', '%j/%y %I%p:%M:%S',dt2),
2558 ('%I', '02/03 4am:05:06', '%j/%y %I%p:%M:%S',dt2),
2559 ('%w', '6/04/03', '%w/%U/%y', dt3),
2560 # %u requires a single digit.
2561 ('%W', '6/4/2003', '%u/%W/%Y', dt3),
2562 ('%V', '6/4/2003', '%u/%V/%G', dt4),
2563 ]
2564 for reason, string, format, target in inputs:
2565 reason = 'test single digit ' + reason
2566 with self.subTest(reason=reason,
2567 string=string,
2568 format=format,
2569 target=target):
2570 newdate = strptime(string, format)
2571 self.assertEqual(newdate, target, msg=reason)
2572
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002573 def test_more_timetuple(self):
2574 # This tests fields beyond those tested by the TestDate.test_timetuple.
2575 t = self.theclass(2004, 12, 31, 6, 22, 33)
2576 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
2577 self.assertEqual(t.timetuple(),
2578 (t.year, t.month, t.day,
2579 t.hour, t.minute, t.second,
2580 t.weekday(),
2581 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
2582 -1))
2583 tt = t.timetuple()
2584 self.assertEqual(tt.tm_year, t.year)
2585 self.assertEqual(tt.tm_mon, t.month)
2586 self.assertEqual(tt.tm_mday, t.day)
2587 self.assertEqual(tt.tm_hour, t.hour)
2588 self.assertEqual(tt.tm_min, t.minute)
2589 self.assertEqual(tt.tm_sec, t.second)
2590 self.assertEqual(tt.tm_wday, t.weekday())
2591 self.assertEqual(tt.tm_yday, t.toordinal() -
2592 date(t.year, 1, 1).toordinal() + 1)
2593 self.assertEqual(tt.tm_isdst, -1)
2594
2595 def test_more_strftime(self):
2596 # This tests fields beyond those tested by the TestDate.test_strftime.
2597 t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
2598 self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
2599 "12 31 04 000047 33 22 06 366")
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04002600 for (s, us), z in [((33, 123), "33.000123"), ((33, 0), "33"),]:
2601 tz = timezone(-timedelta(hours=2, seconds=s, microseconds=us))
2602 t = t.replace(tzinfo=tz)
2603 self.assertEqual(t.strftime("%z"), "-0200" + z)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002604
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03002605 # bpo-34482: Check that surrogates don't cause a crash.
2606 try:
2607 t.strftime('%y\ud800%m %H\ud800%M')
2608 except UnicodeEncodeError:
2609 pass
2610
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002611 def test_extract(self):
2612 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2613 self.assertEqual(dt.date(), date(2002, 3, 4))
2614 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2615
2616 def test_combine(self):
2617 d = date(2002, 3, 4)
2618 t = time(18, 45, 3, 1234)
2619 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2620 combine = self.theclass.combine
2621 dt = combine(d, t)
2622 self.assertEqual(dt, expected)
2623
2624 dt = combine(time=t, date=d)
2625 self.assertEqual(dt, expected)
2626
2627 self.assertEqual(d, dt.date())
2628 self.assertEqual(t, dt.time())
2629 self.assertEqual(dt, combine(dt.date(), dt.time()))
2630
2631 self.assertRaises(TypeError, combine) # need an arg
2632 self.assertRaises(TypeError, combine, d) # need two args
2633 self.assertRaises(TypeError, combine, t, d) # args reversed
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002634 self.assertRaises(TypeError, combine, d, t, 1) # wrong tzinfo type
2635 self.assertRaises(TypeError, combine, d, t, 1, 2) # too many args
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002636 self.assertRaises(TypeError, combine, "date", "time") # wrong types
2637 self.assertRaises(TypeError, combine, d, "time") # wrong type
2638 self.assertRaises(TypeError, combine, "date", t) # wrong type
2639
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002640 # tzinfo= argument
2641 dt = combine(d, t, timezone.utc)
2642 self.assertIs(dt.tzinfo, timezone.utc)
2643 dt = combine(d, t, tzinfo=timezone.utc)
2644 self.assertIs(dt.tzinfo, timezone.utc)
2645 t = time()
2646 dt = combine(dt, t)
2647 self.assertEqual(dt.date(), d)
2648 self.assertEqual(dt.time(), t)
2649
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002650 def test_replace(self):
2651 cls = self.theclass
2652 args = [1, 2, 3, 4, 5, 6, 7]
2653 base = cls(*args)
2654 self.assertEqual(base, base.replace())
2655
2656 i = 0
2657 for name, newval in (("year", 2),
2658 ("month", 3),
2659 ("day", 4),
2660 ("hour", 5),
2661 ("minute", 6),
2662 ("second", 7),
2663 ("microsecond", 8)):
2664 newargs = args[:]
2665 newargs[i] = newval
2666 expected = cls(*newargs)
2667 got = base.replace(**{name: newval})
2668 self.assertEqual(expected, got)
2669 i += 1
2670
2671 # Out of bounds.
2672 base = cls(2000, 2, 29)
2673 self.assertRaises(ValueError, base.replace, year=2001)
2674
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002675 @support.run_with_tz('EDT4')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002676 def test_astimezone(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002677 dt = self.theclass.now()
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002678 f = FixedOffset(44, "0044")
2679 dt_utc = dt.replace(tzinfo=timezone(timedelta(hours=-4), 'EDT'))
2680 self.assertEqual(dt.astimezone(), dt_utc) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002681 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
2682 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002683 dt_f = dt.replace(tzinfo=f) + timedelta(hours=4, minutes=44)
2684 self.assertEqual(dt.astimezone(f), dt_f) # naive
2685 self.assertEqual(dt.astimezone(tz=f), dt_f) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002686
2687 class Bogus(tzinfo):
2688 def utcoffset(self, dt): return None
2689 def dst(self, dt): return timedelta(0)
2690 bog = Bogus()
2691 self.assertRaises(ValueError, dt.astimezone, bog) # naive
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002692 self.assertEqual(dt.replace(tzinfo=bog).astimezone(f), dt_f)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002693
2694 class AlsoBogus(tzinfo):
2695 def utcoffset(self, dt): return timedelta(0)
2696 def dst(self, dt): return None
2697 alsobog = AlsoBogus()
2698 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
2699
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002700 class Broken(tzinfo):
2701 def utcoffset(self, dt): return 1
2702 def dst(self, dt): return 1
2703 broken = Broken()
2704 dt_broken = dt.replace(tzinfo=broken)
2705 with self.assertRaises(TypeError):
2706 dt_broken.astimezone()
2707
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002708 def test_subclass_datetime(self):
2709
2710 class C(self.theclass):
2711 theAnswer = 42
2712
2713 def __new__(cls, *args, **kws):
2714 temp = kws.copy()
2715 extra = temp.pop('extra')
2716 result = self.theclass.__new__(cls, *args, **temp)
2717 result.extra = extra
2718 return result
2719
2720 def newmeth(self, start):
2721 return start + self.year + self.month + self.second
2722
2723 args = 2003, 4, 14, 12, 13, 41
2724
2725 dt1 = self.theclass(*args)
2726 dt2 = C(*args, **{'extra': 7})
2727
2728 self.assertEqual(dt2.__class__, C)
2729 self.assertEqual(dt2.theAnswer, 42)
2730 self.assertEqual(dt2.extra, 7)
2731 self.assertEqual(dt1.toordinal(), dt2.toordinal())
2732 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
2733 dt1.second - 7)
2734
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002735 def test_subclass_alternate_constructors_datetime(self):
2736 # Test that alternate constructors call the constructor
2737 class DateTimeSubclass(self.theclass):
2738 def __new__(cls, *args, **kwargs):
2739 result = self.theclass.__new__(cls, *args, **kwargs)
2740 result.extra = 7
2741
2742 return result
2743
2744 args = (2003, 4, 14, 12, 30, 15, 123456)
2745 d_isoformat = '2003-04-14T12:30:15.123456' # Equivalent isoformat()
2746 utc_ts = 1050323415.123456 # UTC timestamp
2747
2748 base_d = DateTimeSubclass(*args)
2749 self.assertIsInstance(base_d, DateTimeSubclass)
2750 self.assertEqual(base_d.extra, 7)
2751
2752 # Timestamp depends on time zone, so we'll calculate the equivalent here
2753 ts = base_d.timestamp()
2754
2755 test_cases = [
Paul Ganssle89427cd2019-02-04 14:42:04 -05002756 ('fromtimestamp', (ts,), base_d),
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002757 # See https://bugs.python.org/issue32417
Paul Ganssle89427cd2019-02-04 14:42:04 -05002758 ('fromtimestamp', (ts, timezone.utc),
2759 base_d.astimezone(timezone.utc)),
2760 ('utcfromtimestamp', (utc_ts,), base_d),
2761 ('fromisoformat', (d_isoformat,), base_d),
2762 ('strptime', (d_isoformat, '%Y-%m-%dT%H:%M:%S.%f'), base_d),
2763 ('combine', (date(*args[0:3]), time(*args[3:])), base_d),
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002764 ]
2765
Paul Ganssle89427cd2019-02-04 14:42:04 -05002766 for constr_name, constr_args, expected in test_cases:
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002767 for base_obj in (DateTimeSubclass, base_d):
2768 # Test both the classmethod and method
2769 with self.subTest(base_obj_type=type(base_obj),
2770 constr_name=constr_name):
Paul Ganssle89427cd2019-02-04 14:42:04 -05002771 constructor = getattr(base_obj, constr_name)
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002772
Paul Ganssle89427cd2019-02-04 14:42:04 -05002773 dt = constructor(*constr_args)
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002774
2775 # Test that it creates the right subclass
2776 self.assertIsInstance(dt, DateTimeSubclass)
2777
2778 # Test that it's equal to the base object
Paul Ganssle89427cd2019-02-04 14:42:04 -05002779 self.assertEqual(dt, expected)
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002780
2781 # Test that it called the constructor
2782 self.assertEqual(dt.extra, 7)
2783
Paul Ganssle89427cd2019-02-04 14:42:04 -05002784 def test_subclass_now(self):
2785 # Test that alternate constructors call the constructor
2786 class DateTimeSubclass(self.theclass):
2787 def __new__(cls, *args, **kwargs):
2788 result = self.theclass.__new__(cls, *args, **kwargs)
2789 result.extra = 7
2790
2791 return result
2792
2793 test_cases = [
2794 ('now', 'now', {}),
2795 ('utcnow', 'utcnow', {}),
2796 ('now_utc', 'now', {'tz': timezone.utc}),
2797 ('now_fixed', 'now', {'tz': timezone(timedelta(hours=-5), "EST")}),
2798 ]
2799
2800 for name, meth_name, kwargs in test_cases:
2801 with self.subTest(name):
2802 constr = getattr(DateTimeSubclass, meth_name)
2803 dt = constr(**kwargs)
2804
2805 self.assertIsInstance(dt, DateTimeSubclass)
2806 self.assertEqual(dt.extra, 7)
2807
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002808 def test_fromisoformat_datetime(self):
2809 # Test that isoformat() is reversible
2810 base_dates = [
2811 (1, 1, 1),
2812 (1900, 1, 1),
2813 (2004, 11, 12),
2814 (2017, 5, 30)
2815 ]
2816
2817 base_times = [
2818 (0, 0, 0, 0),
2819 (0, 0, 0, 241000),
2820 (0, 0, 0, 234567),
2821 (12, 30, 45, 234567)
2822 ]
2823
2824 separators = [' ', 'T']
2825
2826 tzinfos = [None, timezone.utc,
2827 timezone(timedelta(hours=-5)),
2828 timezone(timedelta(hours=2))]
2829
2830 dts = [self.theclass(*date_tuple, *time_tuple, tzinfo=tzi)
2831 for date_tuple in base_dates
2832 for time_tuple in base_times
2833 for tzi in tzinfos]
2834
2835 for dt in dts:
2836 for sep in separators:
2837 dtstr = dt.isoformat(sep=sep)
2838
2839 with self.subTest(dtstr=dtstr):
2840 dt_rt = self.theclass.fromisoformat(dtstr)
2841 self.assertEqual(dt, dt_rt)
2842
2843 def test_fromisoformat_timezone(self):
2844 base_dt = self.theclass(2014, 12, 30, 12, 30, 45, 217456)
2845
2846 tzoffsets = [
2847 timedelta(hours=5), timedelta(hours=2),
2848 timedelta(hours=6, minutes=27),
2849 timedelta(hours=12, minutes=32, seconds=30),
2850 timedelta(hours=2, minutes=4, seconds=9, microseconds=123456)
2851 ]
2852
2853 tzoffsets += [-1 * td for td in tzoffsets]
2854
2855 tzinfos = [None, timezone.utc,
2856 timezone(timedelta(hours=0))]
2857
2858 tzinfos += [timezone(td) for td in tzoffsets]
2859
2860 for tzi in tzinfos:
2861 dt = base_dt.replace(tzinfo=tzi)
2862 dtstr = dt.isoformat()
2863
2864 with self.subTest(tstr=dtstr):
2865 dt_rt = self.theclass.fromisoformat(dtstr)
2866 assert dt == dt_rt, dt_rt
2867
2868 def test_fromisoformat_separators(self):
2869 separators = [
2870 ' ', 'T', '\u007f', # 1-bit widths
2871 '\u0080', 'ʁ', # 2-bit widths
2872 'ᛇ', '時', # 3-bit widths
Paul Ganssle096329f2018-08-23 11:06:20 -04002873 '🐍', # 4-bit widths
2874 '\ud800', # bpo-34454: Surrogate code point
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002875 ]
2876
2877 for sep in separators:
2878 dt = self.theclass(2018, 1, 31, 23, 59, 47, 124789)
2879 dtstr = dt.isoformat(sep=sep)
2880
2881 with self.subTest(dtstr=dtstr):
2882 dt_rt = self.theclass.fromisoformat(dtstr)
2883 self.assertEqual(dt, dt_rt)
2884
2885 def test_fromisoformat_ambiguous(self):
2886 # Test strings like 2018-01-31+12:15 (where +12:15 is not a time zone)
2887 separators = ['+', '-']
2888 for sep in separators:
2889 dt = self.theclass(2018, 1, 31, 12, 15)
2890 dtstr = dt.isoformat(sep=sep)
2891
2892 with self.subTest(dtstr=dtstr):
2893 dt_rt = self.theclass.fromisoformat(dtstr)
2894 self.assertEqual(dt, dt_rt)
2895
2896 def test_fromisoformat_timespecs(self):
2897 datetime_bases = [
2898 (2009, 12, 4, 8, 17, 45, 123456),
2899 (2009, 12, 4, 8, 17, 45, 0)]
2900
2901 tzinfos = [None, timezone.utc,
2902 timezone(timedelta(hours=-5)),
2903 timezone(timedelta(hours=2)),
2904 timezone(timedelta(hours=6, minutes=27))]
2905
2906 timespecs = ['hours', 'minutes', 'seconds',
2907 'milliseconds', 'microseconds']
2908
2909 for ip, ts in enumerate(timespecs):
2910 for tzi in tzinfos:
2911 for dt_tuple in datetime_bases:
2912 if ts == 'milliseconds':
2913 new_microseconds = 1000 * (dt_tuple[6] // 1000)
2914 dt_tuple = dt_tuple[0:6] + (new_microseconds,)
2915
2916 dt = self.theclass(*(dt_tuple[0:(4 + ip)]), tzinfo=tzi)
2917 dtstr = dt.isoformat(timespec=ts)
2918 with self.subTest(dtstr=dtstr):
2919 dt_rt = self.theclass.fromisoformat(dtstr)
2920 self.assertEqual(dt, dt_rt)
2921
2922 def test_fromisoformat_fails_datetime(self):
2923 # Test that fromisoformat() fails on invalid values
2924 bad_strs = [
2925 '', # Empty string
Paul Ganssle096329f2018-08-23 11:06:20 -04002926 '\ud800', # bpo-34454: Surrogate code point
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002927 '2009.04-19T03', # Wrong first separator
2928 '2009-04.19T03', # Wrong second separator
2929 '2009-04-19T0a', # Invalid hours
2930 '2009-04-19T03:1a:45', # Invalid minutes
2931 '2009-04-19T03:15:4a', # Invalid seconds
2932 '2009-04-19T03;15:45', # Bad first time separator
2933 '2009-04-19T03:15;45', # Bad second time separator
2934 '2009-04-19T03:15:4500:00', # Bad time zone separator
2935 '2009-04-19T03:15:45.2345', # Too many digits for milliseconds
2936 '2009-04-19T03:15:45.1234567', # Too many digits for microseconds
2937 '2009-04-19T03:15:45.123456+24:30', # Invalid time zone offset
2938 '2009-04-19T03:15:45.123456-24:30', # Invalid negative offset
2939 '2009-04-10ᛇᛇᛇᛇᛇ12:15', # Too many unicode separators
Paul Ganssle096329f2018-08-23 11:06:20 -04002940 '2009-04\ud80010T12:15', # Surrogate char in date
2941 '2009-04-10T12\ud80015', # Surrogate char in time
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002942 '2009-04-19T1', # Incomplete hours
2943 '2009-04-19T12:3', # Incomplete minutes
2944 '2009-04-19T12:30:4', # Incomplete seconds
2945 '2009-04-19T12:', # Ends with time separator
2946 '2009-04-19T12:30:', # Ends with time separator
2947 '2009-04-19T12:30:45.', # Ends with time separator
2948 '2009-04-19T12:30:45.123456+', # Ends with timzone separator
2949 '2009-04-19T12:30:45.123456-', # Ends with timzone separator
2950 '2009-04-19T12:30:45.123456-05:00a', # Extra text
2951 '2009-04-19T12:30:45.123-05:00a', # Extra text
2952 '2009-04-19T12:30:45-05:00a', # Extra text
2953 ]
2954
2955 for bad_str in bad_strs:
2956 with self.subTest(bad_str=bad_str):
2957 with self.assertRaises(ValueError):
2958 self.theclass.fromisoformat(bad_str)
2959
Paul Ganssle3df85402018-10-22 12:32:52 -04002960 def test_fromisoformat_fails_surrogate(self):
2961 # Test that when fromisoformat() fails with a surrogate character as
2962 # the separator, the error message contains the original string
2963 dtstr = "2018-01-03\ud80001:0113"
2964
2965 with self.assertRaisesRegex(ValueError, re.escape(repr(dtstr))):
2966 self.theclass.fromisoformat(dtstr)
2967
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002968 def test_fromisoformat_utc(self):
2969 dt_str = '2014-04-19T13:21:13+00:00'
2970 dt = self.theclass.fromisoformat(dt_str)
2971
2972 self.assertIs(dt.tzinfo, timezone.utc)
2973
2974 def test_fromisoformat_subclass(self):
2975 class DateTimeSubclass(self.theclass):
2976 pass
2977
2978 dt = DateTimeSubclass(2014, 12, 14, 9, 30, 45, 457390,
2979 tzinfo=timezone(timedelta(hours=10, minutes=45)))
2980
2981 dt_rt = DateTimeSubclass.fromisoformat(dt.isoformat())
2982
2983 self.assertEqual(dt, dt_rt)
2984 self.assertIsInstance(dt_rt, DateTimeSubclass)
2985
2986
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002987class TestSubclassDateTime(TestDateTime):
2988 theclass = SubclassDatetime
2989 # Override tests not designed for subclass
Zachary Ware9fe6d862013-12-08 00:20:35 -06002990 @unittest.skip('not appropriate for subclasses')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002991 def test_roundtrip(self):
2992 pass
2993
2994class SubclassTime(time):
2995 sub_var = 1
2996
2997class TestTime(HarmlessMixedComparison, unittest.TestCase):
2998
2999 theclass = time
3000
3001 def test_basic_attributes(self):
3002 t = self.theclass(12, 0)
3003 self.assertEqual(t.hour, 12)
3004 self.assertEqual(t.minute, 0)
3005 self.assertEqual(t.second, 0)
3006 self.assertEqual(t.microsecond, 0)
3007
3008 def test_basic_attributes_nonzero(self):
3009 # Make sure all attributes are non-zero so bugs in
3010 # bit-shifting access show up.
3011 t = self.theclass(12, 59, 59, 8000)
3012 self.assertEqual(t.hour, 12)
3013 self.assertEqual(t.minute, 59)
3014 self.assertEqual(t.second, 59)
3015 self.assertEqual(t.microsecond, 8000)
3016
3017 def test_roundtrip(self):
3018 t = self.theclass(1, 2, 3, 4)
3019
3020 # Verify t -> string -> time identity.
3021 s = repr(t)
3022 self.assertTrue(s.startswith('datetime.'))
3023 s = s[9:]
3024 t2 = eval(s)
3025 self.assertEqual(t, t2)
3026
3027 # Verify identity via reconstructing from pieces.
3028 t2 = self.theclass(t.hour, t.minute, t.second,
3029 t.microsecond)
3030 self.assertEqual(t, t2)
3031
3032 def test_comparing(self):
3033 args = [1, 2, 3, 4]
3034 t1 = self.theclass(*args)
3035 t2 = self.theclass(*args)
3036 self.assertEqual(t1, t2)
3037 self.assertTrue(t1 <= t2)
3038 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003039 self.assertFalse(t1 != t2)
3040 self.assertFalse(t1 < t2)
3041 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003042
3043 for i in range(len(args)):
3044 newargs = args[:]
3045 newargs[i] = args[i] + 1
3046 t2 = self.theclass(*newargs) # this is larger than t1
3047 self.assertTrue(t1 < t2)
3048 self.assertTrue(t2 > t1)
3049 self.assertTrue(t1 <= t2)
3050 self.assertTrue(t2 >= t1)
3051 self.assertTrue(t1 != t2)
3052 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003053 self.assertFalse(t1 == t2)
3054 self.assertFalse(t2 == t1)
3055 self.assertFalse(t1 > t2)
3056 self.assertFalse(t2 < t1)
3057 self.assertFalse(t1 >= t2)
3058 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003059
3060 for badarg in OTHERSTUFF:
3061 self.assertEqual(t1 == badarg, False)
3062 self.assertEqual(t1 != badarg, True)
3063 self.assertEqual(badarg == t1, False)
3064 self.assertEqual(badarg != t1, True)
3065
3066 self.assertRaises(TypeError, lambda: t1 <= badarg)
3067 self.assertRaises(TypeError, lambda: t1 < badarg)
3068 self.assertRaises(TypeError, lambda: t1 > badarg)
3069 self.assertRaises(TypeError, lambda: t1 >= badarg)
3070 self.assertRaises(TypeError, lambda: badarg <= t1)
3071 self.assertRaises(TypeError, lambda: badarg < t1)
3072 self.assertRaises(TypeError, lambda: badarg > t1)
3073 self.assertRaises(TypeError, lambda: badarg >= t1)
3074
3075 def test_bad_constructor_arguments(self):
3076 # bad hours
3077 self.theclass(0, 0) # no exception
3078 self.theclass(23, 0) # no exception
3079 self.assertRaises(ValueError, self.theclass, -1, 0)
3080 self.assertRaises(ValueError, self.theclass, 24, 0)
3081 # bad minutes
3082 self.theclass(23, 0) # no exception
3083 self.theclass(23, 59) # no exception
3084 self.assertRaises(ValueError, self.theclass, 23, -1)
3085 self.assertRaises(ValueError, self.theclass, 23, 60)
3086 # bad seconds
3087 self.theclass(23, 59, 0) # no exception
3088 self.theclass(23, 59, 59) # no exception
3089 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
3090 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
3091 # bad microseconds
3092 self.theclass(23, 59, 59, 0) # no exception
3093 self.theclass(23, 59, 59, 999999) # no exception
3094 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
3095 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
3096
3097 def test_hash_equality(self):
3098 d = self.theclass(23, 30, 17)
3099 e = self.theclass(23, 30, 17)
3100 self.assertEqual(d, e)
3101 self.assertEqual(hash(d), hash(e))
3102
3103 dic = {d: 1}
3104 dic[e] = 2
3105 self.assertEqual(len(dic), 1)
3106 self.assertEqual(dic[d], 2)
3107 self.assertEqual(dic[e], 2)
3108
3109 d = self.theclass(0, 5, 17)
3110 e = self.theclass(0, 5, 17)
3111 self.assertEqual(d, e)
3112 self.assertEqual(hash(d), hash(e))
3113
3114 dic = {d: 1}
3115 dic[e] = 2
3116 self.assertEqual(len(dic), 1)
3117 self.assertEqual(dic[d], 2)
3118 self.assertEqual(dic[e], 2)
3119
3120 def test_isoformat(self):
3121 t = self.theclass(4, 5, 1, 123)
3122 self.assertEqual(t.isoformat(), "04:05:01.000123")
3123 self.assertEqual(t.isoformat(), str(t))
3124
3125 t = self.theclass()
3126 self.assertEqual(t.isoformat(), "00:00:00")
3127 self.assertEqual(t.isoformat(), str(t))
3128
3129 t = self.theclass(microsecond=1)
3130 self.assertEqual(t.isoformat(), "00:00:00.000001")
3131 self.assertEqual(t.isoformat(), str(t))
3132
3133 t = self.theclass(microsecond=10)
3134 self.assertEqual(t.isoformat(), "00:00:00.000010")
3135 self.assertEqual(t.isoformat(), str(t))
3136
3137 t = self.theclass(microsecond=100)
3138 self.assertEqual(t.isoformat(), "00:00:00.000100")
3139 self.assertEqual(t.isoformat(), str(t))
3140
3141 t = self.theclass(microsecond=1000)
3142 self.assertEqual(t.isoformat(), "00:00:00.001000")
3143 self.assertEqual(t.isoformat(), str(t))
3144
3145 t = self.theclass(microsecond=10000)
3146 self.assertEqual(t.isoformat(), "00:00:00.010000")
3147 self.assertEqual(t.isoformat(), str(t))
3148
3149 t = self.theclass(microsecond=100000)
3150 self.assertEqual(t.isoformat(), "00:00:00.100000")
3151 self.assertEqual(t.isoformat(), str(t))
3152
Alexander Belopolskya2998a62016-03-06 14:58:43 -05003153 t = self.theclass(hour=12, minute=34, second=56, microsecond=123456)
3154 self.assertEqual(t.isoformat(timespec='hours'), "12")
3155 self.assertEqual(t.isoformat(timespec='minutes'), "12:34")
3156 self.assertEqual(t.isoformat(timespec='seconds'), "12:34:56")
3157 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.123")
3158 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.123456")
3159 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56.123456")
3160 self.assertRaises(ValueError, t.isoformat, timespec='monkey')
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03003161 # bpo-34482: Check that surrogates are handled properly.
3162 self.assertRaises(ValueError, t.isoformat, timespec='\ud800')
Alexander Belopolskya2998a62016-03-06 14:58:43 -05003163
3164 t = self.theclass(hour=12, minute=34, second=56, microsecond=999500)
3165 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.999")
3166
3167 t = self.theclass(hour=12, minute=34, second=56, microsecond=0)
3168 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.000")
3169 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.000000")
3170 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56")
3171
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003172 def test_isoformat_timezone(self):
3173 tzoffsets = [
3174 ('05:00', timedelta(hours=5)),
3175 ('02:00', timedelta(hours=2)),
3176 ('06:27', timedelta(hours=6, minutes=27)),
3177 ('12:32:30', timedelta(hours=12, minutes=32, seconds=30)),
3178 ('02:04:09.123456', timedelta(hours=2, minutes=4, seconds=9, microseconds=123456))
3179 ]
3180
3181 tzinfos = [
3182 ('', None),
3183 ('+00:00', timezone.utc),
3184 ('+00:00', timezone(timedelta(0))),
3185 ]
3186
3187 tzinfos += [
3188 (prefix + expected, timezone(sign * td))
3189 for expected, td in tzoffsets
3190 for prefix, sign in [('-', -1), ('+', 1)]
3191 ]
3192
3193 t_base = self.theclass(12, 37, 9)
3194 exp_base = '12:37:09'
3195
3196 for exp_tz, tzi in tzinfos:
3197 t = t_base.replace(tzinfo=tzi)
3198 exp = exp_base + exp_tz
3199 with self.subTest(tzi=tzi):
3200 assert t.isoformat() == exp
3201
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003202 def test_1653736(self):
3203 # verify it doesn't accept extra keyword arguments
3204 t = self.theclass(second=1)
3205 self.assertRaises(TypeError, t.isoformat, foo=3)
3206
3207 def test_strftime(self):
3208 t = self.theclass(1, 2, 3, 4)
3209 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
3210 # A naive object replaces %z and %Z with empty strings.
3211 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
3212
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03003213 # bpo-34482: Check that surrogates don't cause a crash.
3214 try:
3215 t.strftime('%H\ud800%M')
3216 except UnicodeEncodeError:
3217 pass
3218
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003219 def test_format(self):
3220 t = self.theclass(1, 2, 3, 4)
3221 self.assertEqual(t.__format__(''), str(t))
3222
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02003223 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04003224 t.__format__(123)
3225
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003226 # check that a derived class's __str__() gets called
3227 class A(self.theclass):
3228 def __str__(self):
3229 return 'A'
3230 a = A(1, 2, 3, 4)
3231 self.assertEqual(a.__format__(''), 'A')
3232
3233 # check that a derived class's strftime gets called
3234 class B(self.theclass):
3235 def strftime(self, format_spec):
3236 return 'B'
3237 b = B(1, 2, 3, 4)
3238 self.assertEqual(b.__format__(''), str(t))
3239
3240 for fmt in ['%H %M %S',
3241 ]:
3242 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
3243 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
3244 self.assertEqual(b.__format__(fmt), 'B')
3245
3246 def test_str(self):
3247 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
3248 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
3249 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
3250 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
3251 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
3252
3253 def test_repr(self):
3254 name = 'datetime.' + self.theclass.__name__
3255 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
3256 "%s(1, 2, 3, 4)" % name)
3257 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
3258 "%s(10, 2, 3, 4000)" % name)
3259 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
3260 "%s(0, 2, 3, 400000)" % name)
3261 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
3262 "%s(12, 2, 3)" % name)
3263 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
3264 "%s(23, 15)" % name)
3265
3266 def test_resolution_info(self):
3267 self.assertIsInstance(self.theclass.min, self.theclass)
3268 self.assertIsInstance(self.theclass.max, self.theclass)
3269 self.assertIsInstance(self.theclass.resolution, timedelta)
3270 self.assertTrue(self.theclass.max > self.theclass.min)
3271
3272 def test_pickling(self):
3273 args = 20, 59, 16, 64**2
3274 orig = self.theclass(*args)
3275 for pickler, unpickler, proto in pickle_choices:
3276 green = pickler.dumps(orig, proto)
3277 derived = unpickler.loads(green)
3278 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003279 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003280
3281 def test_pickling_subclass_time(self):
3282 args = 20, 59, 16, 64**2
3283 orig = SubclassTime(*args)
3284 for pickler, unpickler, proto in pickle_choices:
3285 green = pickler.dumps(orig, proto)
3286 derived = unpickler.loads(green)
3287 self.assertEqual(orig, derived)
3288
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02003289 def test_compat_unpickle(self):
3290 tests = [
3291 b"cdatetime\ntime\n(S'\\x14;\\x10\\x00\\x10\\x00'\ntR.",
3292 b'cdatetime\ntime\n(U\x06\x14;\x10\x00\x10\x00tR.',
3293 b'\x80\x02cdatetime\ntime\nU\x06\x14;\x10\x00\x10\x00\x85R.',
3294 ]
3295 args = 20, 59, 16, 64**2
3296 expected = self.theclass(*args)
3297 for data in tests:
3298 for loads in pickle_loads:
3299 derived = loads(data, encoding='latin1')
3300 self.assertEqual(derived, expected)
3301
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003302 def test_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003303 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003304 cls = self.theclass
3305 self.assertTrue(cls(1))
3306 self.assertTrue(cls(0, 1))
3307 self.assertTrue(cls(0, 0, 1))
3308 self.assertTrue(cls(0, 0, 0, 1))
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003309 self.assertTrue(cls(0))
3310 self.assertTrue(cls())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003311
3312 def test_replace(self):
3313 cls = self.theclass
3314 args = [1, 2, 3, 4]
3315 base = cls(*args)
3316 self.assertEqual(base, base.replace())
3317
3318 i = 0
3319 for name, newval in (("hour", 5),
3320 ("minute", 6),
3321 ("second", 7),
3322 ("microsecond", 8)):
3323 newargs = args[:]
3324 newargs[i] = newval
3325 expected = cls(*newargs)
3326 got = base.replace(**{name: newval})
3327 self.assertEqual(expected, got)
3328 i += 1
3329
3330 # Out of bounds.
3331 base = cls(1)
3332 self.assertRaises(ValueError, base.replace, hour=24)
3333 self.assertRaises(ValueError, base.replace, minute=-1)
3334 self.assertRaises(ValueError, base.replace, second=100)
3335 self.assertRaises(ValueError, base.replace, microsecond=1000000)
3336
Paul Ganssle191e9932017-11-09 16:34:29 -05003337 def test_subclass_replace(self):
3338 class TimeSubclass(self.theclass):
3339 pass
3340
3341 ctime = TimeSubclass(12, 30)
3342 self.assertIs(type(ctime.replace(hour=10)), TimeSubclass)
3343
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003344 def test_subclass_time(self):
3345
3346 class C(self.theclass):
3347 theAnswer = 42
3348
3349 def __new__(cls, *args, **kws):
3350 temp = kws.copy()
3351 extra = temp.pop('extra')
3352 result = self.theclass.__new__(cls, *args, **temp)
3353 result.extra = extra
3354 return result
3355
3356 def newmeth(self, start):
3357 return start + self.hour + self.second
3358
3359 args = 4, 5, 6
3360
3361 dt1 = self.theclass(*args)
3362 dt2 = C(*args, **{'extra': 7})
3363
3364 self.assertEqual(dt2.__class__, C)
3365 self.assertEqual(dt2.theAnswer, 42)
3366 self.assertEqual(dt2.extra, 7)
3367 self.assertEqual(dt1.isoformat(), dt2.isoformat())
3368 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
3369
3370 def test_backdoor_resistance(self):
3371 # see TestDate.test_backdoor_resistance().
3372 base = '2:59.0'
3373 for hour_byte in ' ', '9', chr(24), '\xff':
3374 self.assertRaises(TypeError, self.theclass,
3375 hour_byte + base[1:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04003376 # Good bytes, but bad tzinfo:
3377 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
3378 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003379
3380# A mixin for classes with a tzinfo= argument. Subclasses must define
Martin Panter46f50722016-05-26 05:35:26 +00003381# theclass as a class attribute, and theclass(1, 1, 1, tzinfo=whatever)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003382# must be legit (which is true for time and datetime).
3383class TZInfoBase:
3384
3385 def test_argument_passing(self):
3386 cls = self.theclass
3387 # A datetime passes itself on, a time passes None.
3388 class introspective(tzinfo):
3389 def tzname(self, dt): return dt and "real" or "none"
3390 def utcoffset(self, dt):
3391 return timedelta(minutes = dt and 42 or -42)
3392 dst = utcoffset
3393
3394 obj = cls(1, 2, 3, tzinfo=introspective())
3395
3396 expected = cls is time and "none" or "real"
3397 self.assertEqual(obj.tzname(), expected)
3398
3399 expected = timedelta(minutes=(cls is time and -42 or 42))
3400 self.assertEqual(obj.utcoffset(), expected)
3401 self.assertEqual(obj.dst(), expected)
3402
3403 def test_bad_tzinfo_classes(self):
3404 cls = self.theclass
3405 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
3406
3407 class NiceTry(object):
3408 def __init__(self): pass
3409 def utcoffset(self, dt): pass
3410 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
3411
3412 class BetterTry(tzinfo):
3413 def __init__(self): pass
3414 def utcoffset(self, dt): pass
3415 b = BetterTry()
3416 t = cls(1, 1, 1, tzinfo=b)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003417 self.assertIs(t.tzinfo, b)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003418
3419 def test_utc_offset_out_of_bounds(self):
3420 class Edgy(tzinfo):
3421 def __init__(self, offset):
3422 self.offset = timedelta(minutes=offset)
3423 def utcoffset(self, dt):
3424 return self.offset
3425
3426 cls = self.theclass
3427 for offset, legit in ((-1440, False),
3428 (-1439, True),
3429 (1439, True),
3430 (1440, False)):
3431 if cls is time:
3432 t = cls(1, 2, 3, tzinfo=Edgy(offset))
3433 elif cls is datetime:
3434 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
3435 else:
3436 assert 0, "impossible"
3437 if legit:
3438 aofs = abs(offset)
3439 h, m = divmod(aofs, 60)
3440 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
3441 if isinstance(t, datetime):
3442 t = t.timetz()
3443 self.assertEqual(str(t), "01:02:03" + tag)
3444 else:
3445 self.assertRaises(ValueError, str, t)
3446
3447 def test_tzinfo_classes(self):
3448 cls = self.theclass
3449 class C1(tzinfo):
3450 def utcoffset(self, dt): return None
3451 def dst(self, dt): return None
3452 def tzname(self, dt): return None
3453 for t in (cls(1, 1, 1),
3454 cls(1, 1, 1, tzinfo=None),
3455 cls(1, 1, 1, tzinfo=C1())):
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003456 self.assertIsNone(t.utcoffset())
3457 self.assertIsNone(t.dst())
3458 self.assertIsNone(t.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003459
3460 class C3(tzinfo):
3461 def utcoffset(self, dt): return timedelta(minutes=-1439)
3462 def dst(self, dt): return timedelta(minutes=1439)
3463 def tzname(self, dt): return "aname"
3464 t = cls(1, 1, 1, tzinfo=C3())
3465 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
3466 self.assertEqual(t.dst(), timedelta(minutes=1439))
3467 self.assertEqual(t.tzname(), "aname")
3468
3469 # Wrong types.
3470 class C4(tzinfo):
3471 def utcoffset(self, dt): return "aname"
3472 def dst(self, dt): return 7
3473 def tzname(self, dt): return 0
3474 t = cls(1, 1, 1, tzinfo=C4())
3475 self.assertRaises(TypeError, t.utcoffset)
3476 self.assertRaises(TypeError, t.dst)
3477 self.assertRaises(TypeError, t.tzname)
3478
3479 # Offset out of range.
3480 class C6(tzinfo):
3481 def utcoffset(self, dt): return timedelta(hours=-24)
3482 def dst(self, dt): return timedelta(hours=24)
3483 t = cls(1, 1, 1, tzinfo=C6())
3484 self.assertRaises(ValueError, t.utcoffset)
3485 self.assertRaises(ValueError, t.dst)
3486
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04003487 # Not a whole number of seconds.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003488 class C7(tzinfo):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04003489 def utcoffset(self, dt): return timedelta(microseconds=61)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003490 def dst(self, dt): return timedelta(microseconds=-81)
3491 t = cls(1, 1, 1, tzinfo=C7())
Alexander Belopolsky018d3532017-07-31 10:26:50 -04003492 self.assertEqual(t.utcoffset(), timedelta(microseconds=61))
3493 self.assertEqual(t.dst(), timedelta(microseconds=-81))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003494
3495 def test_aware_compare(self):
3496 cls = self.theclass
3497
3498 # Ensure that utcoffset() gets ignored if the comparands have
3499 # the same tzinfo member.
3500 class OperandDependentOffset(tzinfo):
3501 def utcoffset(self, t):
3502 if t.minute < 10:
3503 # d0 and d1 equal after adjustment
3504 return timedelta(minutes=t.minute)
3505 else:
3506 # d2 off in the weeds
3507 return timedelta(minutes=59)
3508
3509 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
3510 d0 = base.replace(minute=3)
3511 d1 = base.replace(minute=9)
3512 d2 = base.replace(minute=11)
3513 for x in d0, d1, d2:
3514 for y in d0, d1, d2:
3515 for op in lt, le, gt, ge, eq, ne:
3516 got = op(x, y)
3517 expected = op(x.minute, y.minute)
3518 self.assertEqual(got, expected)
3519
3520 # However, if they're different members, uctoffset is not ignored.
penguindustin96466302019-05-06 14:57:17 -04003521 # Note that a time can't actually have an operand-dependent offset,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003522 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
3523 # so skip this test for time.
3524 if cls is not time:
3525 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3526 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3527 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3528 for x in d0, d1, d2:
3529 for y in d0, d1, d2:
3530 got = (x > y) - (x < y)
3531 if (x is d0 or x is d1) and (y is d0 or y is d1):
3532 expected = 0
3533 elif x is y is d2:
3534 expected = 0
3535 elif x is d2:
3536 expected = -1
3537 else:
3538 assert y is d2
3539 expected = 1
3540 self.assertEqual(got, expected)
3541
3542
3543# Testing time objects with a non-None tzinfo.
3544class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
3545 theclass = time
3546
3547 def test_empty(self):
3548 t = self.theclass()
3549 self.assertEqual(t.hour, 0)
3550 self.assertEqual(t.minute, 0)
3551 self.assertEqual(t.second, 0)
3552 self.assertEqual(t.microsecond, 0)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003553 self.assertIsNone(t.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003554
3555 def test_zones(self):
3556 est = FixedOffset(-300, "EST", 1)
3557 utc = FixedOffset(0, "UTC", -2)
3558 met = FixedOffset(60, "MET", 3)
3559 t1 = time( 7, 47, tzinfo=est)
3560 t2 = time(12, 47, tzinfo=utc)
3561 t3 = time(13, 47, tzinfo=met)
3562 t4 = time(microsecond=40)
3563 t5 = time(microsecond=40, tzinfo=utc)
3564
3565 self.assertEqual(t1.tzinfo, est)
3566 self.assertEqual(t2.tzinfo, utc)
3567 self.assertEqual(t3.tzinfo, met)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003568 self.assertIsNone(t4.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003569 self.assertEqual(t5.tzinfo, utc)
3570
3571 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
3572 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
3573 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003574 self.assertIsNone(t4.utcoffset())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003575 self.assertRaises(TypeError, t1.utcoffset, "no args")
3576
3577 self.assertEqual(t1.tzname(), "EST")
3578 self.assertEqual(t2.tzname(), "UTC")
3579 self.assertEqual(t3.tzname(), "MET")
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003580 self.assertIsNone(t4.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003581 self.assertRaises(TypeError, t1.tzname, "no args")
3582
3583 self.assertEqual(t1.dst(), timedelta(minutes=1))
3584 self.assertEqual(t2.dst(), timedelta(minutes=-2))
3585 self.assertEqual(t3.dst(), timedelta(minutes=3))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003586 self.assertIsNone(t4.dst())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003587 self.assertRaises(TypeError, t1.dst, "no args")
3588
3589 self.assertEqual(hash(t1), hash(t2))
3590 self.assertEqual(hash(t1), hash(t3))
3591 self.assertEqual(hash(t2), hash(t3))
3592
3593 self.assertEqual(t1, t2)
3594 self.assertEqual(t1, t3)
3595 self.assertEqual(t2, t3)
Alexander Belopolsky08313822012-06-15 20:19:47 -04003596 self.assertNotEqual(t4, t5) # mixed tz-aware & naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003597 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
3598 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
3599
3600 self.assertEqual(str(t1), "07:47:00-05:00")
3601 self.assertEqual(str(t2), "12:47:00+00:00")
3602 self.assertEqual(str(t3), "13:47:00+01:00")
3603 self.assertEqual(str(t4), "00:00:00.000040")
3604 self.assertEqual(str(t5), "00:00:00.000040+00:00")
3605
3606 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
3607 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
3608 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
3609 self.assertEqual(t4.isoformat(), "00:00:00.000040")
3610 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
3611
3612 d = 'datetime.time'
3613 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
3614 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
3615 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
3616 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
3617 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
3618
3619 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
3620 "07:47:00 %Z=EST %z=-0500")
3621 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
3622 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
3623
3624 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
3625 t1 = time(23, 59, tzinfo=yuck)
3626 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
3627 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
3628
3629 # Check that an invalid tzname result raises an exception.
3630 class Badtzname(tzinfo):
Alexander Belopolskye239d232010-12-08 23:31:48 +00003631 tz = 42
3632 def tzname(self, dt): return self.tz
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003633 t = time(2, 3, 4, tzinfo=Badtzname())
3634 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
3635 self.assertRaises(TypeError, t.strftime, "%Z")
3636
Alexander Belopolskye239d232010-12-08 23:31:48 +00003637 # Issue #6697:
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +02003638 if '_Fast' in self.__class__.__name__:
Alexander Belopolskye239d232010-12-08 23:31:48 +00003639 Badtzname.tz = '\ud800'
3640 self.assertRaises(ValueError, t.strftime, "%Z")
3641
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003642 def test_hash_edge_cases(self):
3643 # Offsets that overflow a basic time.
3644 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
3645 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
3646 self.assertEqual(hash(t1), hash(t2))
3647
3648 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
3649 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
3650 self.assertEqual(hash(t1), hash(t2))
3651
3652 def test_pickling(self):
3653 # Try one without a tzinfo.
3654 args = 20, 59, 16, 64**2
3655 orig = self.theclass(*args)
3656 for pickler, unpickler, proto in pickle_choices:
3657 green = pickler.dumps(orig, proto)
3658 derived = unpickler.loads(green)
3659 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003660 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003661
3662 # Try one with a tzinfo.
3663 tinfo = PicklableFixedOffset(-300, 'cookie')
3664 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
3665 for pickler, unpickler, proto in pickle_choices:
3666 green = pickler.dumps(orig, proto)
3667 derived = unpickler.loads(green)
3668 self.assertEqual(orig, derived)
3669 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3670 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3671 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003672 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003673
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02003674 def test_compat_unpickle(self):
3675 tests = [
3676 b"cdatetime\ntime\n(S'\\x05\\x06\\x07\\x01\\xe2@'\n"
3677 b"ctest.datetimetester\nPicklableFixedOffset\n(tR"
3678 b"(dS'_FixedOffset__offset'\ncdatetime\ntimedelta\n"
3679 b"(I-1\nI68400\nI0\ntRs"
3680 b"S'_FixedOffset__dstoffset'\nNs"
3681 b"S'_FixedOffset__name'\nS'cookie'\nsbtR.",
3682
3683 b'cdatetime\ntime\n(U\x06\x05\x06\x07\x01\xe2@'
3684 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
3685 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
3686 b'(J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00tR'
3687 b'U\x17_FixedOffset__dstoffsetN'
3688 b'U\x12_FixedOffset__nameU\x06cookieubtR.',
3689
3690 b'\x80\x02cdatetime\ntime\nU\x06\x05\x06\x07\x01\xe2@'
3691 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
3692 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
3693 b'J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00\x87R'
3694 b'U\x17_FixedOffset__dstoffsetN'
3695 b'U\x12_FixedOffset__nameU\x06cookieub\x86R.',
3696 ]
3697
3698 tinfo = PicklableFixedOffset(-300, 'cookie')
3699 expected = self.theclass(5, 6, 7, 123456, tzinfo=tinfo)
3700 for data in tests:
3701 for loads in pickle_loads:
3702 derived = loads(data, encoding='latin1')
3703 self.assertEqual(derived, expected, repr(data))
3704 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3705 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3706 self.assertEqual(derived.tzname(), 'cookie')
3707
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003708 def test_more_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003709 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003710 cls = self.theclass
3711
3712 t = cls(0, tzinfo=FixedOffset(-300, ""))
3713 self.assertTrue(t)
3714
3715 t = cls(5, tzinfo=FixedOffset(-300, ""))
3716 self.assertTrue(t)
3717
3718 t = cls(5, tzinfo=FixedOffset(300, ""))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003719 self.assertTrue(t)
3720
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003721 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
3722 self.assertTrue(t)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003723
3724 def test_replace(self):
3725 cls = self.theclass
3726 z100 = FixedOffset(100, "+100")
3727 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3728 args = [1, 2, 3, 4, z100]
3729 base = cls(*args)
3730 self.assertEqual(base, base.replace())
3731
3732 i = 0
3733 for name, newval in (("hour", 5),
3734 ("minute", 6),
3735 ("second", 7),
3736 ("microsecond", 8),
3737 ("tzinfo", zm200)):
3738 newargs = args[:]
3739 newargs[i] = newval
3740 expected = cls(*newargs)
3741 got = base.replace(**{name: newval})
3742 self.assertEqual(expected, got)
3743 i += 1
3744
3745 # Ensure we can get rid of a tzinfo.
3746 self.assertEqual(base.tzname(), "+100")
3747 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003748 self.assertIsNone(base2.tzinfo)
3749 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003750
3751 # Ensure we can add one.
3752 base3 = base2.replace(tzinfo=z100)
3753 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003754 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003755
3756 # Out of bounds.
3757 base = cls(1)
3758 self.assertRaises(ValueError, base.replace, hour=24)
3759 self.assertRaises(ValueError, base.replace, minute=-1)
3760 self.assertRaises(ValueError, base.replace, second=100)
3761 self.assertRaises(ValueError, base.replace, microsecond=1000000)
3762
3763 def test_mixed_compare(self):
3764 t1 = time(1, 2, 3)
3765 t2 = time(1, 2, 3)
3766 self.assertEqual(t1, t2)
3767 t2 = t2.replace(tzinfo=None)
3768 self.assertEqual(t1, t2)
3769 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3770 self.assertEqual(t1, t2)
3771 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04003772 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003773
3774 # In time w/ identical tzinfo objects, utcoffset is ignored.
3775 class Varies(tzinfo):
3776 def __init__(self):
3777 self.offset = timedelta(minutes=22)
3778 def utcoffset(self, t):
3779 self.offset += timedelta(minutes=1)
3780 return self.offset
3781
3782 v = Varies()
3783 t1 = t2.replace(tzinfo=v)
3784 t2 = t2.replace(tzinfo=v)
3785 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3786 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3787 self.assertEqual(t1, t2)
3788
3789 # But if they're not identical, it isn't ignored.
3790 t2 = t2.replace(tzinfo=Varies())
3791 self.assertTrue(t1 < t2) # t1's offset counter still going up
3792
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003793 def test_fromisoformat(self):
3794 time_examples = [
3795 (0, 0, 0, 0),
3796 (23, 59, 59, 999999),
3797 ]
3798
3799 hh = (9, 12, 20)
3800 mm = (5, 30)
3801 ss = (4, 45)
3802 usec = (0, 245000, 678901)
3803
3804 time_examples += list(itertools.product(hh, mm, ss, usec))
3805
3806 tzinfos = [None, timezone.utc,
3807 timezone(timedelta(hours=2)),
3808 timezone(timedelta(hours=6, minutes=27))]
3809
3810 for ttup in time_examples:
3811 for tzi in tzinfos:
3812 t = self.theclass(*ttup, tzinfo=tzi)
3813 tstr = t.isoformat()
3814
3815 with self.subTest(tstr=tstr):
3816 t_rt = self.theclass.fromisoformat(tstr)
3817 self.assertEqual(t, t_rt)
3818
3819 def test_fromisoformat_timezone(self):
3820 base_time = self.theclass(12, 30, 45, 217456)
3821
3822 tzoffsets = [
3823 timedelta(hours=5), timedelta(hours=2),
3824 timedelta(hours=6, minutes=27),
3825 timedelta(hours=12, minutes=32, seconds=30),
3826 timedelta(hours=2, minutes=4, seconds=9, microseconds=123456)
3827 ]
3828
3829 tzoffsets += [-1 * td for td in tzoffsets]
3830
3831 tzinfos = [None, timezone.utc,
3832 timezone(timedelta(hours=0))]
3833
3834 tzinfos += [timezone(td) for td in tzoffsets]
3835
3836 for tzi in tzinfos:
3837 t = base_time.replace(tzinfo=tzi)
3838 tstr = t.isoformat()
3839
3840 with self.subTest(tstr=tstr):
3841 t_rt = self.theclass.fromisoformat(tstr)
3842 assert t == t_rt, t_rt
3843
3844 def test_fromisoformat_timespecs(self):
3845 time_bases = [
3846 (8, 17, 45, 123456),
3847 (8, 17, 45, 0)
3848 ]
3849
3850 tzinfos = [None, timezone.utc,
3851 timezone(timedelta(hours=-5)),
3852 timezone(timedelta(hours=2)),
3853 timezone(timedelta(hours=6, minutes=27))]
3854
3855 timespecs = ['hours', 'minutes', 'seconds',
3856 'milliseconds', 'microseconds']
3857
3858 for ip, ts in enumerate(timespecs):
3859 for tzi in tzinfos:
3860 for t_tuple in time_bases:
3861 if ts == 'milliseconds':
3862 new_microseconds = 1000 * (t_tuple[-1] // 1000)
3863 t_tuple = t_tuple[0:-1] + (new_microseconds,)
3864
3865 t = self.theclass(*(t_tuple[0:(1 + ip)]), tzinfo=tzi)
3866 tstr = t.isoformat(timespec=ts)
3867 with self.subTest(tstr=tstr):
3868 t_rt = self.theclass.fromisoformat(tstr)
3869 self.assertEqual(t, t_rt)
3870
3871 def test_fromisoformat_fails(self):
3872 bad_strs = [
3873 '', # Empty string
Paul Ganssle096329f2018-08-23 11:06:20 -04003874 '12\ud80000', # Invalid separator - surrogate char
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003875 '12:', # Ends on a separator
3876 '12:30:', # Ends on a separator
3877 '12:30:15.', # Ends on a separator
3878 '1', # Incomplete hours
3879 '12:3', # Incomplete minutes
3880 '12:30:1', # Incomplete seconds
3881 '1a:30:45.334034', # Invalid character in hours
3882 '12:a0:45.334034', # Invalid character in minutes
3883 '12:30:a5.334034', # Invalid character in seconds
3884 '12:30:45.1234', # Too many digits for milliseconds
3885 '12:30:45.1234567', # Too many digits for microseconds
3886 '12:30:45.123456+24:30', # Invalid time zone offset
3887 '12:30:45.123456-24:30', # Invalid negative offset
3888 '12:30:45', # Uses full-width unicode colons
3889 '12:30:45․123456', # Uses \u2024 in place of decimal point
3890 '12:30:45a', # Extra at tend of basic time
3891 '12:30:45.123a', # Extra at end of millisecond time
3892 '12:30:45.123456a', # Extra at end of microsecond time
3893 '12:30:45.123456+12:00:30a', # Extra at end of full time
3894 ]
3895
3896 for bad_str in bad_strs:
3897 with self.subTest(bad_str=bad_str):
3898 with self.assertRaises(ValueError):
3899 self.theclass.fromisoformat(bad_str)
3900
3901 def test_fromisoformat_fails_typeerror(self):
3902 # Test the fromisoformat fails when passed the wrong type
3903 import io
3904
3905 bad_types = [b'12:30:45', None, io.StringIO('12:30:45')]
3906
3907 for bad_type in bad_types:
3908 with self.assertRaises(TypeError):
3909 self.theclass.fromisoformat(bad_type)
3910
3911 def test_fromisoformat_subclass(self):
3912 class TimeSubclass(self.theclass):
3913 pass
3914
3915 tsc = TimeSubclass(12, 14, 45, 203745, tzinfo=timezone.utc)
3916 tsc_rt = TimeSubclass.fromisoformat(tsc.isoformat())
3917
3918 self.assertEqual(tsc, tsc_rt)
3919 self.assertIsInstance(tsc_rt, TimeSubclass)
3920
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003921 def test_subclass_timetz(self):
3922
3923 class C(self.theclass):
3924 theAnswer = 42
3925
3926 def __new__(cls, *args, **kws):
3927 temp = kws.copy()
3928 extra = temp.pop('extra')
3929 result = self.theclass.__new__(cls, *args, **temp)
3930 result.extra = extra
3931 return result
3932
3933 def newmeth(self, start):
3934 return start + self.hour + self.second
3935
3936 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3937
3938 dt1 = self.theclass(*args)
3939 dt2 = C(*args, **{'extra': 7})
3940
3941 self.assertEqual(dt2.__class__, C)
3942 self.assertEqual(dt2.theAnswer, 42)
3943 self.assertEqual(dt2.extra, 7)
3944 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3945 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
3946
3947
3948# Testing datetime objects with a non-None tzinfo.
3949
3950class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
3951 theclass = datetime
3952
3953 def test_trivial(self):
3954 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
3955 self.assertEqual(dt.year, 1)
3956 self.assertEqual(dt.month, 2)
3957 self.assertEqual(dt.day, 3)
3958 self.assertEqual(dt.hour, 4)
3959 self.assertEqual(dt.minute, 5)
3960 self.assertEqual(dt.second, 6)
3961 self.assertEqual(dt.microsecond, 7)
3962 self.assertEqual(dt.tzinfo, None)
3963
3964 def test_even_more_compare(self):
3965 # The test_compare() and test_more_compare() inherited from TestDate
3966 # and TestDateTime covered non-tzinfo cases.
3967
3968 # Smallest possible after UTC adjustment.
3969 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3970 # Largest possible after UTC adjustment.
3971 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3972 tzinfo=FixedOffset(-1439, ""))
3973
3974 # Make sure those compare correctly, and w/o overflow.
3975 self.assertTrue(t1 < t2)
3976 self.assertTrue(t1 != t2)
3977 self.assertTrue(t2 > t1)
3978
3979 self.assertEqual(t1, t1)
3980 self.assertEqual(t2, t2)
3981
3982 # Equal afer adjustment.
3983 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
3984 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
3985 self.assertEqual(t1, t2)
3986
3987 # Change t1 not to subtract a minute, and t1 should be larger.
3988 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
3989 self.assertTrue(t1 > t2)
3990
3991 # Change t1 to subtract 2 minutes, and t1 should be smaller.
3992 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
3993 self.assertTrue(t1 < t2)
3994
3995 # Back to the original t1, but make seconds resolve it.
3996 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3997 second=1)
3998 self.assertTrue(t1 > t2)
3999
4000 # Likewise, but make microseconds resolve it.
4001 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
4002 microsecond=1)
4003 self.assertTrue(t1 > t2)
4004
Alexander Belopolsky08313822012-06-15 20:19:47 -04004005 # Make t2 naive and it should differ.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004006 t2 = self.theclass.min
Alexander Belopolsky08313822012-06-15 20:19:47 -04004007 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004008 self.assertEqual(t2, t2)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04004009 # and > comparison should fail
4010 with self.assertRaises(TypeError):
4011 t1 > t2
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004012
4013 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
4014 class Naive(tzinfo):
4015 def utcoffset(self, dt): return None
4016 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
Alexander Belopolsky08313822012-06-15 20:19:47 -04004017 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004018 self.assertEqual(t2, t2)
4019
4020 # OTOH, it's OK to compare two of these mixing the two ways of being
4021 # naive.
4022 t1 = self.theclass(5, 6, 7)
4023 self.assertEqual(t1, t2)
4024
4025 # Try a bogus uctoffset.
4026 class Bogus(tzinfo):
4027 def utcoffset(self, dt):
4028 return timedelta(minutes=1440) # out of bounds
4029 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
4030 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
4031 self.assertRaises(ValueError, lambda: t1 == t2)
4032
4033 def test_pickling(self):
4034 # Try one without a tzinfo.
4035 args = 6, 7, 23, 20, 59, 1, 64**2
4036 orig = self.theclass(*args)
4037 for pickler, unpickler, proto in pickle_choices:
4038 green = pickler.dumps(orig, proto)
4039 derived = unpickler.loads(green)
4040 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02004041 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004042
4043 # Try one with a tzinfo.
4044 tinfo = PicklableFixedOffset(-300, 'cookie')
4045 orig = self.theclass(*args, **{'tzinfo': tinfo})
4046 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
4047 for pickler, unpickler, proto in pickle_choices:
4048 green = pickler.dumps(orig, proto)
4049 derived = unpickler.loads(green)
4050 self.assertEqual(orig, derived)
4051 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
4052 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
4053 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02004054 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004055
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02004056 def test_compat_unpickle(self):
4057 tests = [
4058 b'cdatetime\ndatetime\n'
4059 b"(S'\\x07\\xdf\\x0b\\x1b\\x14;\\x01\\x01\\xe2@'\n"
4060 b'ctest.datetimetester\nPicklableFixedOffset\n(tR'
4061 b"(dS'_FixedOffset__offset'\ncdatetime\ntimedelta\n"
4062 b'(I-1\nI68400\nI0\ntRs'
4063 b"S'_FixedOffset__dstoffset'\nNs"
4064 b"S'_FixedOffset__name'\nS'cookie'\nsbtR.",
4065
4066 b'cdatetime\ndatetime\n'
4067 b'(U\n\x07\xdf\x0b\x1b\x14;\x01\x01\xe2@'
4068 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
4069 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
4070 b'(J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00tR'
4071 b'U\x17_FixedOffset__dstoffsetN'
4072 b'U\x12_FixedOffset__nameU\x06cookieubtR.',
4073
4074 b'\x80\x02cdatetime\ndatetime\n'
4075 b'U\n\x07\xdf\x0b\x1b\x14;\x01\x01\xe2@'
4076 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
4077 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
4078 b'J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00\x87R'
4079 b'U\x17_FixedOffset__dstoffsetN'
4080 b'U\x12_FixedOffset__nameU\x06cookieub\x86R.',
4081 ]
4082 args = 2015, 11, 27, 20, 59, 1, 123456
4083 tinfo = PicklableFixedOffset(-300, 'cookie')
4084 expected = self.theclass(*args, **{'tzinfo': tinfo})
4085 for data in tests:
4086 for loads in pickle_loads:
4087 derived = loads(data, encoding='latin1')
4088 self.assertEqual(derived, expected)
4089 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
4090 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
4091 self.assertEqual(derived.tzname(), 'cookie')
4092
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004093 def test_extreme_hashes(self):
4094 # If an attempt is made to hash these via subtracting the offset
4095 # then hashing a datetime object, OverflowError results. The
4096 # Python implementation used to blow up here.
4097 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
4098 hash(t)
4099 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
4100 tzinfo=FixedOffset(-1439, ""))
4101 hash(t)
4102
4103 # OTOH, an OOB offset should blow up.
4104 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
4105 self.assertRaises(ValueError, hash, t)
4106
4107 def test_zones(self):
4108 est = FixedOffset(-300, "EST")
4109 utc = FixedOffset(0, "UTC")
4110 met = FixedOffset(60, "MET")
4111 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
4112 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
4113 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
4114 self.assertEqual(t1.tzinfo, est)
4115 self.assertEqual(t2.tzinfo, utc)
4116 self.assertEqual(t3.tzinfo, met)
4117 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
4118 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
4119 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
4120 self.assertEqual(t1.tzname(), "EST")
4121 self.assertEqual(t2.tzname(), "UTC")
4122 self.assertEqual(t3.tzname(), "MET")
4123 self.assertEqual(hash(t1), hash(t2))
4124 self.assertEqual(hash(t1), hash(t3))
4125 self.assertEqual(hash(t2), hash(t3))
4126 self.assertEqual(t1, t2)
4127 self.assertEqual(t1, t3)
4128 self.assertEqual(t2, t3)
4129 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
4130 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
4131 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
4132 d = 'datetime.datetime(2002, 3, 19, '
4133 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
4134 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
4135 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
4136
4137 def test_combine(self):
4138 met = FixedOffset(60, "MET")
4139 d = date(2002, 3, 4)
4140 tz = time(18, 45, 3, 1234, tzinfo=met)
4141 dt = datetime.combine(d, tz)
4142 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
4143 tzinfo=met))
4144
4145 def test_extract(self):
4146 met = FixedOffset(60, "MET")
4147 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
4148 self.assertEqual(dt.date(), date(2002, 3, 4))
4149 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
4150 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
4151
4152 def test_tz_aware_arithmetic(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004153 now = self.theclass.now()
4154 tz55 = FixedOffset(-330, "west 5:30")
4155 timeaware = now.time().replace(tzinfo=tz55)
4156 nowaware = self.theclass.combine(now.date(), timeaware)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004157 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004158 self.assertEqual(nowaware.timetz(), timeaware)
4159
4160 # Can't mix aware and non-aware.
4161 self.assertRaises(TypeError, lambda: now - nowaware)
4162 self.assertRaises(TypeError, lambda: nowaware - now)
4163
4164 # And adding datetime's doesn't make sense, aware or not.
4165 self.assertRaises(TypeError, lambda: now + nowaware)
4166 self.assertRaises(TypeError, lambda: nowaware + now)
4167 self.assertRaises(TypeError, lambda: nowaware + nowaware)
4168
4169 # Subtracting should yield 0.
4170 self.assertEqual(now - now, timedelta(0))
4171 self.assertEqual(nowaware - nowaware, timedelta(0))
4172
4173 # Adding a delta should preserve tzinfo.
4174 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
4175 nowawareplus = nowaware + delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004176 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004177 nowawareplus2 = delta + nowaware
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004178 self.assertIs(nowawareplus2.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004179 self.assertEqual(nowawareplus, nowawareplus2)
4180
4181 # that - delta should be what we started with, and that - what we
4182 # started with should be delta.
4183 diff = nowawareplus - delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004184 self.assertIs(diff.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004185 self.assertEqual(nowaware, diff)
4186 self.assertRaises(TypeError, lambda: delta - nowawareplus)
4187 self.assertEqual(nowawareplus - nowaware, delta)
4188
4189 # Make up a random timezone.
4190 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
4191 # Attach it to nowawareplus.
4192 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004193 self.assertIs(nowawareplus.tzinfo, tzr)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004194 # Make sure the difference takes the timezone adjustments into account.
4195 got = nowaware - nowawareplus
4196 # Expected: (nowaware base - nowaware offset) -
4197 # (nowawareplus base - nowawareplus offset) =
4198 # (nowaware base - nowawareplus base) +
4199 # (nowawareplus offset - nowaware offset) =
4200 # -delta + nowawareplus offset - nowaware offset
4201 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
4202 self.assertEqual(got, expected)
4203
4204 # Try max possible difference.
4205 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
4206 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
4207 tzinfo=FixedOffset(-1439, "max"))
4208 maxdiff = max - min
4209 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
4210 timedelta(minutes=2*1439))
4211 # Different tzinfo, but the same offset
4212 tza = timezone(HOUR, 'A')
4213 tzb = timezone(HOUR, 'B')
4214 delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
4215 self.assertEqual(delta, self.theclass.min - self.theclass.max)
4216
4217 def test_tzinfo_now(self):
4218 meth = self.theclass.now
4219 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4220 base = meth()
4221 # Try with and without naming the keyword.
4222 off42 = FixedOffset(42, "42")
4223 another = meth(off42)
4224 again = meth(tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004225 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004226 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
4227 # Bad argument with and w/o naming the keyword.
4228 self.assertRaises(TypeError, meth, 16)
4229 self.assertRaises(TypeError, meth, tzinfo=16)
4230 # Bad keyword name.
4231 self.assertRaises(TypeError, meth, tinfo=off42)
4232 # Too many args.
4233 self.assertRaises(TypeError, meth, off42, off42)
4234
4235 # We don't know which time zone we're in, and don't have a tzinfo
4236 # class to represent it, so seeing whether a tz argument actually
4237 # does a conversion is tricky.
4238 utc = FixedOffset(0, "utc", 0)
4239 for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
4240 timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
4241 for dummy in range(3):
4242 now = datetime.now(weirdtz)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004243 self.assertIs(now.tzinfo, weirdtz)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004244 utcnow = datetime.utcnow().replace(tzinfo=utc)
4245 now2 = utcnow.astimezone(weirdtz)
4246 if abs(now - now2) < timedelta(seconds=30):
4247 break
4248 # Else the code is broken, or more than 30 seconds passed between
4249 # calls; assuming the latter, just try again.
4250 else:
4251 # Three strikes and we're out.
4252 self.fail("utcnow(), now(tz), or astimezone() may be broken")
4253
4254 def test_tzinfo_fromtimestamp(self):
4255 import time
4256 meth = self.theclass.fromtimestamp
4257 ts = time.time()
4258 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4259 base = meth(ts)
4260 # Try with and without naming the keyword.
4261 off42 = FixedOffset(42, "42")
4262 another = meth(ts, off42)
4263 again = meth(ts, tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004264 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004265 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
4266 # Bad argument with and w/o naming the keyword.
4267 self.assertRaises(TypeError, meth, ts, 16)
4268 self.assertRaises(TypeError, meth, ts, tzinfo=16)
4269 # Bad keyword name.
4270 self.assertRaises(TypeError, meth, ts, tinfo=off42)
4271 # Too many args.
4272 self.assertRaises(TypeError, meth, ts, off42, off42)
4273 # Too few args.
4274 self.assertRaises(TypeError, meth)
4275
4276 # Try to make sure tz= actually does some conversion.
4277 timestamp = 1000000000
4278 utcdatetime = datetime.utcfromtimestamp(timestamp)
4279 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
4280 # But on some flavor of Mac, it's nowhere near that. So we can't have
4281 # any idea here what time that actually is, we can only test that
4282 # relative changes match.
4283 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
4284 tz = FixedOffset(utcoffset, "tz", 0)
4285 expected = utcdatetime + utcoffset
4286 got = datetime.fromtimestamp(timestamp, tz)
4287 self.assertEqual(expected, got.replace(tzinfo=None))
4288
4289 def test_tzinfo_utcnow(self):
4290 meth = self.theclass.utcnow
4291 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4292 base = meth()
4293 # Try with and without naming the keyword; for whatever reason,
4294 # utcnow() doesn't accept a tzinfo argument.
4295 off42 = FixedOffset(42, "42")
4296 self.assertRaises(TypeError, meth, off42)
4297 self.assertRaises(TypeError, meth, tzinfo=off42)
4298
4299 def test_tzinfo_utcfromtimestamp(self):
4300 import time
4301 meth = self.theclass.utcfromtimestamp
4302 ts = time.time()
4303 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4304 base = meth(ts)
4305 # Try with and without naming the keyword; for whatever reason,
4306 # utcfromtimestamp() doesn't accept a tzinfo argument.
4307 off42 = FixedOffset(42, "42")
4308 self.assertRaises(TypeError, meth, ts, off42)
4309 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
4310
4311 def test_tzinfo_timetuple(self):
4312 # TestDateTime tested most of this. datetime adds a twist to the
4313 # DST flag.
4314 class DST(tzinfo):
4315 def __init__(self, dstvalue):
4316 if isinstance(dstvalue, int):
4317 dstvalue = timedelta(minutes=dstvalue)
4318 self.dstvalue = dstvalue
4319 def dst(self, dt):
4320 return self.dstvalue
4321
4322 cls = self.theclass
4323 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
4324 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
4325 t = d.timetuple()
4326 self.assertEqual(1, t.tm_year)
4327 self.assertEqual(1, t.tm_mon)
4328 self.assertEqual(1, t.tm_mday)
4329 self.assertEqual(10, t.tm_hour)
4330 self.assertEqual(20, t.tm_min)
4331 self.assertEqual(30, t.tm_sec)
4332 self.assertEqual(0, t.tm_wday)
4333 self.assertEqual(1, t.tm_yday)
4334 self.assertEqual(flag, t.tm_isdst)
4335
4336 # dst() returns wrong type.
4337 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
4338
4339 # dst() at the edge.
4340 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
4341 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
4342
4343 # dst() out of range.
4344 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
4345 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
4346
4347 def test_utctimetuple(self):
4348 class DST(tzinfo):
4349 def __init__(self, dstvalue=0):
4350 if isinstance(dstvalue, int):
4351 dstvalue = timedelta(minutes=dstvalue)
4352 self.dstvalue = dstvalue
4353 def dst(self, dt):
4354 return self.dstvalue
4355
4356 cls = self.theclass
4357 # This can't work: DST didn't implement utcoffset.
4358 self.assertRaises(NotImplementedError,
4359 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
4360
4361 class UOFS(DST):
4362 def __init__(self, uofs, dofs=None):
4363 DST.__init__(self, dofs)
4364 self.uofs = timedelta(minutes=uofs)
4365 def utcoffset(self, dt):
4366 return self.uofs
4367
4368 for dstvalue in -33, 33, 0, None:
4369 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
4370 t = d.utctimetuple()
4371 self.assertEqual(d.year, t.tm_year)
4372 self.assertEqual(d.month, t.tm_mon)
4373 self.assertEqual(d.day, t.tm_mday)
4374 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
4375 self.assertEqual(13, t.tm_min)
4376 self.assertEqual(d.second, t.tm_sec)
4377 self.assertEqual(d.weekday(), t.tm_wday)
4378 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
4379 t.tm_yday)
4380 # Ensure tm_isdst is 0 regardless of what dst() says: DST
4381 # is never in effect for a UTC time.
4382 self.assertEqual(0, t.tm_isdst)
4383
4384 # For naive datetime, utctimetuple == timetuple except for isdst
4385 d = cls(1, 2, 3, 10, 20, 30, 40)
4386 t = d.utctimetuple()
4387 self.assertEqual(t[:-1], d.timetuple()[:-1])
4388 self.assertEqual(0, t.tm_isdst)
4389 # Same if utcoffset is None
4390 class NOFS(DST):
4391 def utcoffset(self, dt):
4392 return None
4393 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
4394 t = d.utctimetuple()
4395 self.assertEqual(t[:-1], d.timetuple()[:-1])
4396 self.assertEqual(0, t.tm_isdst)
4397 # Check that bad tzinfo is detected
4398 class BOFS(DST):
4399 def utcoffset(self, dt):
4400 return "EST"
4401 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
4402 self.assertRaises(TypeError, d.utctimetuple)
4403
4404 # Check that utctimetuple() is the same as
4405 # astimezone(utc).timetuple()
4406 d = cls(2010, 11, 13, 14, 15, 16, 171819)
4407 for tz in [timezone.min, timezone.utc, timezone.max]:
4408 dtz = d.replace(tzinfo=tz)
4409 self.assertEqual(dtz.utctimetuple()[:-1],
4410 dtz.astimezone(timezone.utc).timetuple()[:-1])
4411 # At the edges, UTC adjustment can produce years out-of-range
4412 # for a datetime object. Ensure that an OverflowError is
4413 # raised.
4414 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
4415 # That goes back 1 minute less than a full day.
4416 self.assertRaises(OverflowError, tiny.utctimetuple)
4417
4418 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
4419 # That goes forward 1 minute less than a full day.
4420 self.assertRaises(OverflowError, huge.utctimetuple)
4421 # More overflow cases
4422 tiny = cls.min.replace(tzinfo=timezone(MINUTE))
4423 self.assertRaises(OverflowError, tiny.utctimetuple)
4424 huge = cls.max.replace(tzinfo=timezone(-MINUTE))
4425 self.assertRaises(OverflowError, huge.utctimetuple)
4426
4427 def test_tzinfo_isoformat(self):
4428 zero = FixedOffset(0, "+00:00")
4429 plus = FixedOffset(220, "+03:40")
4430 minus = FixedOffset(-231, "-03:51")
4431 unknown = FixedOffset(None, "")
4432
4433 cls = self.theclass
4434 datestr = '0001-02-03'
4435 for ofs in None, zero, plus, minus, unknown:
4436 for us in 0, 987001:
4437 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
4438 timestr = '04:05:59' + (us and '.987001' or '')
4439 ofsstr = ofs is not None and d.tzname() or ''
4440 tailstr = timestr + ofsstr
4441 iso = d.isoformat()
4442 self.assertEqual(iso, datestr + 'T' + tailstr)
4443 self.assertEqual(iso, d.isoformat('T'))
4444 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
4445 self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
4446 self.assertEqual(str(d), datestr + ' ' + tailstr)
4447
4448 def test_replace(self):
4449 cls = self.theclass
4450 z100 = FixedOffset(100, "+100")
4451 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
4452 args = [1, 2, 3, 4, 5, 6, 7, z100]
4453 base = cls(*args)
4454 self.assertEqual(base, base.replace())
4455
4456 i = 0
4457 for name, newval in (("year", 2),
4458 ("month", 3),
4459 ("day", 4),
4460 ("hour", 5),
4461 ("minute", 6),
4462 ("second", 7),
4463 ("microsecond", 8),
4464 ("tzinfo", zm200)):
4465 newargs = args[:]
4466 newargs[i] = newval
4467 expected = cls(*newargs)
4468 got = base.replace(**{name: newval})
4469 self.assertEqual(expected, got)
4470 i += 1
4471
4472 # Ensure we can get rid of a tzinfo.
4473 self.assertEqual(base.tzname(), "+100")
4474 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004475 self.assertIsNone(base2.tzinfo)
4476 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004477
4478 # Ensure we can add one.
4479 base3 = base2.replace(tzinfo=z100)
4480 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004481 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004482
4483 # Out of bounds.
4484 base = cls(2000, 2, 29)
4485 self.assertRaises(ValueError, base.replace, year=2001)
4486
4487 def test_more_astimezone(self):
4488 # The inherited test_astimezone covered some trivial and error cases.
4489 fnone = FixedOffset(None, "None")
4490 f44m = FixedOffset(44, "44")
4491 fm5h = FixedOffset(-timedelta(hours=5), "m300")
4492
4493 dt = self.theclass.now(tz=f44m)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004494 self.assertIs(dt.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004495 # Replacing with degenerate tzinfo raises an exception.
4496 self.assertRaises(ValueError, dt.astimezone, fnone)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004497 # Replacing with same tzinfo makes no change.
4498 x = dt.astimezone(dt.tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004499 self.assertIs(x.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004500 self.assertEqual(x.date(), dt.date())
4501 self.assertEqual(x.time(), dt.time())
4502
4503 # Replacing with different tzinfo does adjust.
4504 got = dt.astimezone(fm5h)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004505 self.assertIs(got.tzinfo, fm5h)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004506 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
4507 expected = dt - dt.utcoffset() # in effect, convert to UTC
4508 expected += fm5h.utcoffset(dt) # and from there to local time
4509 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
4510 self.assertEqual(got.date(), expected.date())
4511 self.assertEqual(got.time(), expected.time())
4512 self.assertEqual(got.timetz(), expected.timetz())
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004513 self.assertIs(got.tzinfo, expected.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004514 self.assertEqual(got, expected)
4515
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004516 @support.run_with_tz('UTC')
4517 def test_astimezone_default_utc(self):
4518 dt = self.theclass.now(timezone.utc)
4519 self.assertEqual(dt.astimezone(None), dt)
4520 self.assertEqual(dt.astimezone(), dt)
4521
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004522 # Note that offset in TZ variable has the opposite sign to that
4523 # produced by %z directive.
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004524 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4525 def test_astimezone_default_eastern(self):
4526 dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
4527 local = dt.astimezone()
4528 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004529 self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004530 dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
4531 local = dt.astimezone()
4532 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004533 self.assertEqual(local.strftime("%z %Z"), "-0400 EDT")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004534
Alexander Belopolsky1dcf4f92016-03-25 15:42:59 -04004535 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4536 def test_astimezone_default_near_fold(self):
4537 # Issue #26616.
4538 u = datetime(2015, 11, 1, 5, tzinfo=timezone.utc)
4539 t = u.astimezone()
4540 s = t.astimezone()
4541 self.assertEqual(t.tzinfo, s.tzinfo)
4542
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004543 def test_aware_subtract(self):
4544 cls = self.theclass
4545
4546 # Ensure that utcoffset() is ignored when the operands have the
4547 # same tzinfo member.
4548 class OperandDependentOffset(tzinfo):
4549 def utcoffset(self, t):
4550 if t.minute < 10:
4551 # d0 and d1 equal after adjustment
4552 return timedelta(minutes=t.minute)
4553 else:
4554 # d2 off in the weeds
4555 return timedelta(minutes=59)
4556
4557 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
4558 d0 = base.replace(minute=3)
4559 d1 = base.replace(minute=9)
4560 d2 = base.replace(minute=11)
4561 for x in d0, d1, d2:
4562 for y in d0, d1, d2:
4563 got = x - y
4564 expected = timedelta(minutes=x.minute - y.minute)
4565 self.assertEqual(got, expected)
4566
4567 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
4568 # ignored.
4569 base = cls(8, 9, 10, 11, 12, 13, 14)
4570 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
4571 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
4572 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
4573 for x in d0, d1, d2:
4574 for y in d0, d1, d2:
4575 got = x - y
4576 if (x is d0 or x is d1) and (y is d0 or y is d1):
4577 expected = timedelta(0)
4578 elif x is y is d2:
4579 expected = timedelta(0)
4580 elif x is d2:
4581 expected = timedelta(minutes=(11-59)-0)
4582 else:
4583 assert y is d2
4584 expected = timedelta(minutes=0-(11-59))
4585 self.assertEqual(got, expected)
4586
4587 def test_mixed_compare(self):
4588 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
4589 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
4590 self.assertEqual(t1, t2)
4591 t2 = t2.replace(tzinfo=None)
4592 self.assertEqual(t1, t2)
4593 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
4594 self.assertEqual(t1, t2)
4595 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04004596 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004597
4598 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
4599 class Varies(tzinfo):
4600 def __init__(self):
4601 self.offset = timedelta(minutes=22)
4602 def utcoffset(self, t):
4603 self.offset += timedelta(minutes=1)
4604 return self.offset
4605
4606 v = Varies()
4607 t1 = t2.replace(tzinfo=v)
4608 t2 = t2.replace(tzinfo=v)
4609 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
4610 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
4611 self.assertEqual(t1, t2)
4612
4613 # But if they're not identical, it isn't ignored.
4614 t2 = t2.replace(tzinfo=Varies())
4615 self.assertTrue(t1 < t2) # t1's offset counter still going up
4616
4617 def test_subclass_datetimetz(self):
4618
4619 class C(self.theclass):
4620 theAnswer = 42
4621
4622 def __new__(cls, *args, **kws):
4623 temp = kws.copy()
4624 extra = temp.pop('extra')
4625 result = self.theclass.__new__(cls, *args, **temp)
4626 result.extra = extra
4627 return result
4628
4629 def newmeth(self, start):
4630 return start + self.hour + self.year
4631
4632 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
4633
4634 dt1 = self.theclass(*args)
4635 dt2 = C(*args, **{'extra': 7})
4636
4637 self.assertEqual(dt2.__class__, C)
4638 self.assertEqual(dt2.theAnswer, 42)
4639 self.assertEqual(dt2.extra, 7)
4640 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
4641 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
4642
4643# Pain to set up DST-aware tzinfo classes.
4644
4645def first_sunday_on_or_after(dt):
4646 days_to_go = 6 - dt.weekday()
4647 if days_to_go:
4648 dt += timedelta(days_to_go)
4649 return dt
4650
4651ZERO = timedelta(0)
4652MINUTE = timedelta(minutes=1)
4653HOUR = timedelta(hours=1)
4654DAY = timedelta(days=1)
4655# In the US, DST starts at 2am (standard time) on the first Sunday in April.
4656DSTSTART = datetime(1, 4, 1, 2)
4657# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
4658# which is the first Sunday on or after Oct 25. Because we view 1:MM as
4659# being standard time on that day, there is no spelling in local time of
4660# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
4661DSTEND = datetime(1, 10, 25, 1)
4662
4663class USTimeZone(tzinfo):
4664
4665 def __init__(self, hours, reprname, stdname, dstname):
4666 self.stdoffset = timedelta(hours=hours)
4667 self.reprname = reprname
4668 self.stdname = stdname
4669 self.dstname = dstname
4670
4671 def __repr__(self):
4672 return self.reprname
4673
4674 def tzname(self, dt):
4675 if self.dst(dt):
4676 return self.dstname
4677 else:
4678 return self.stdname
4679
4680 def utcoffset(self, dt):
4681 return self.stdoffset + self.dst(dt)
4682
4683 def dst(self, dt):
4684 if dt is None or dt.tzinfo is None:
4685 # An exception instead may be sensible here, in one or more of
4686 # the cases.
4687 return ZERO
4688 assert dt.tzinfo is self
4689
4690 # Find first Sunday in April.
4691 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
4692 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
4693
4694 # Find last Sunday in October.
4695 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
4696 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
4697
4698 # Can't compare naive to aware objects, so strip the timezone from
4699 # dt first.
4700 if start <= dt.replace(tzinfo=None) < end:
4701 return HOUR
4702 else:
4703 return ZERO
4704
4705Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
4706Central = USTimeZone(-6, "Central", "CST", "CDT")
4707Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
4708Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
4709utc_real = FixedOffset(0, "UTC", 0)
4710# For better test coverage, we want another flavor of UTC that's west of
4711# the Eastern and Pacific timezones.
4712utc_fake = FixedOffset(-12*60, "UTCfake", 0)
4713
4714class TestTimezoneConversions(unittest.TestCase):
4715 # The DST switch times for 2002, in std time.
4716 dston = datetime(2002, 4, 7, 2)
4717 dstoff = datetime(2002, 10, 27, 1)
4718
4719 theclass = datetime
4720
4721 # Check a time that's inside DST.
4722 def checkinside(self, dt, tz, utc, dston, dstoff):
4723 self.assertEqual(dt.dst(), HOUR)
4724
4725 # Conversion to our own timezone is always an identity.
4726 self.assertEqual(dt.astimezone(tz), dt)
4727
4728 asutc = dt.astimezone(utc)
4729 there_and_back = asutc.astimezone(tz)
4730
4731 # Conversion to UTC and back isn't always an identity here,
4732 # because there are redundant spellings (in local time) of
4733 # UTC time when DST begins: the clock jumps from 1:59:59
4734 # to 3:00:00, and a local time of 2:MM:SS doesn't really
4735 # make sense then. The classes above treat 2:MM:SS as
4736 # daylight time then (it's "after 2am"), really an alias
4737 # for 1:MM:SS standard time. The latter form is what
4738 # conversion back from UTC produces.
4739 if dt.date() == dston.date() and dt.hour == 2:
4740 # We're in the redundant hour, and coming back from
4741 # UTC gives the 1:MM:SS standard-time spelling.
4742 self.assertEqual(there_and_back + HOUR, dt)
4743 # Although during was considered to be in daylight
4744 # time, there_and_back is not.
4745 self.assertEqual(there_and_back.dst(), ZERO)
4746 # They're the same times in UTC.
4747 self.assertEqual(there_and_back.astimezone(utc),
4748 dt.astimezone(utc))
4749 else:
4750 # We're not in the redundant hour.
4751 self.assertEqual(dt, there_and_back)
4752
4753 # Because we have a redundant spelling when DST begins, there is
Ezio Melotti3b3499b2011-03-16 11:35:38 +02004754 # (unfortunately) an hour when DST ends that can't be spelled at all in
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004755 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
4756 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
4757 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
4758 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
4759 # expressed in local time. Nevertheless, we want conversion back
4760 # from UTC to mimic the local clock's "repeat an hour" behavior.
4761 nexthour_utc = asutc + HOUR
4762 nexthour_tz = nexthour_utc.astimezone(tz)
4763 if dt.date() == dstoff.date() and dt.hour == 0:
4764 # We're in the hour before the last DST hour. The last DST hour
4765 # is ineffable. We want the conversion back to repeat 1:MM.
4766 self.assertEqual(nexthour_tz, dt.replace(hour=1))
4767 nexthour_utc += HOUR
4768 nexthour_tz = nexthour_utc.astimezone(tz)
4769 self.assertEqual(nexthour_tz, dt.replace(hour=1))
4770 else:
4771 self.assertEqual(nexthour_tz - dt, HOUR)
4772
4773 # Check a time that's outside DST.
4774 def checkoutside(self, dt, tz, utc):
4775 self.assertEqual(dt.dst(), ZERO)
4776
4777 # Conversion to our own timezone is always an identity.
4778 self.assertEqual(dt.astimezone(tz), dt)
4779
4780 # Converting to UTC and back is an identity too.
4781 asutc = dt.astimezone(utc)
4782 there_and_back = asutc.astimezone(tz)
4783 self.assertEqual(dt, there_and_back)
4784
4785 def convert_between_tz_and_utc(self, tz, utc):
4786 dston = self.dston.replace(tzinfo=tz)
4787 # Because 1:MM on the day DST ends is taken as being standard time,
4788 # there is no spelling in tz for the last hour of daylight time.
4789 # For purposes of the test, the last hour of DST is 0:MM, which is
4790 # taken as being daylight time (and 1:MM is taken as being standard
4791 # time).
4792 dstoff = self.dstoff.replace(tzinfo=tz)
4793 for delta in (timedelta(weeks=13),
4794 DAY,
4795 HOUR,
4796 timedelta(minutes=1),
4797 timedelta(microseconds=1)):
4798
4799 self.checkinside(dston, tz, utc, dston, dstoff)
4800 for during in dston + delta, dstoff - delta:
4801 self.checkinside(during, tz, utc, dston, dstoff)
4802
4803 self.checkoutside(dstoff, tz, utc)
4804 for outside in dston - delta, dstoff + delta:
4805 self.checkoutside(outside, tz, utc)
4806
4807 def test_easy(self):
4808 # Despite the name of this test, the endcases are excruciating.
4809 self.convert_between_tz_and_utc(Eastern, utc_real)
4810 self.convert_between_tz_and_utc(Pacific, utc_real)
4811 self.convert_between_tz_and_utc(Eastern, utc_fake)
4812 self.convert_between_tz_and_utc(Pacific, utc_fake)
4813 # The next is really dancing near the edge. It works because
4814 # Pacific and Eastern are far enough apart that their "problem
4815 # hours" don't overlap.
4816 self.convert_between_tz_and_utc(Eastern, Pacific)
4817 self.convert_between_tz_and_utc(Pacific, Eastern)
4818 # OTOH, these fail! Don't enable them. The difficulty is that
4819 # the edge case tests assume that every hour is representable in
4820 # the "utc" class. This is always true for a fixed-offset tzinfo
4821 # class (lke utc_real and utc_fake), but not for Eastern or Central.
4822 # For these adjacent DST-aware time zones, the range of time offsets
4823 # tested ends up creating hours in the one that aren't representable
4824 # in the other. For the same reason, we would see failures in the
4825 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
4826 # offset deltas in convert_between_tz_and_utc().
4827 #
4828 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
4829 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
4830
4831 def test_tricky(self):
4832 # 22:00 on day before daylight starts.
4833 fourback = self.dston - timedelta(hours=4)
4834 ninewest = FixedOffset(-9*60, "-0900", 0)
4835 fourback = fourback.replace(tzinfo=ninewest)
4836 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
4837 # 2", we should get the 3 spelling.
4838 # If we plug 22:00 the day before into Eastern, it "looks like std
4839 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
4840 # to 22:00 lands on 2:00, which makes no sense in local time (the
4841 # local clock jumps from 1 to 3). The point here is to make sure we
4842 # get the 3 spelling.
4843 expected = self.dston.replace(hour=3)
4844 got = fourback.astimezone(Eastern).replace(tzinfo=None)
4845 self.assertEqual(expected, got)
4846
4847 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
4848 # case we want the 1:00 spelling.
4849 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
4850 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
4851 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
4852 # spelling.
4853 expected = self.dston.replace(hour=1)
4854 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
4855 self.assertEqual(expected, got)
4856
4857 # Now on the day DST ends, we want "repeat an hour" behavior.
4858 # UTC 4:MM 5:MM 6:MM 7:MM checking these
4859 # EST 23:MM 0:MM 1:MM 2:MM
4860 # EDT 0:MM 1:MM 2:MM 3:MM
4861 # wall 0:MM 1:MM 1:MM 2:MM against these
4862 for utc in utc_real, utc_fake:
4863 for tz in Eastern, Pacific:
4864 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
4865 # Convert that to UTC.
4866 first_std_hour -= tz.utcoffset(None)
4867 # Adjust for possibly fake UTC.
4868 asutc = first_std_hour + utc.utcoffset(None)
4869 # First UTC hour to convert; this is 4:00 when utc=utc_real &
4870 # tz=Eastern.
4871 asutcbase = asutc.replace(tzinfo=utc)
4872 for tzhour in (0, 1, 1, 2):
4873 expectedbase = self.dstoff.replace(hour=tzhour)
4874 for minute in 0, 30, 59:
4875 expected = expectedbase.replace(minute=minute)
4876 asutc = asutcbase.replace(minute=minute)
4877 astz = asutc.astimezone(tz)
4878 self.assertEqual(astz.replace(tzinfo=None), expected)
4879 asutcbase += HOUR
4880
4881
4882 def test_bogus_dst(self):
4883 class ok(tzinfo):
4884 def utcoffset(self, dt): return HOUR
4885 def dst(self, dt): return HOUR
4886
4887 now = self.theclass.now().replace(tzinfo=utc_real)
4888 # Doesn't blow up.
4889 now.astimezone(ok())
4890
4891 # Does blow up.
4892 class notok(ok):
4893 def dst(self, dt): return None
4894 self.assertRaises(ValueError, now.astimezone, notok())
4895
4896 # Sometimes blow up. In the following, tzinfo.dst()
4897 # implementation may return None or not None depending on
4898 # whether DST is assumed to be in effect. In this situation,
4899 # a ValueError should be raised by astimezone().
4900 class tricky_notok(ok):
4901 def dst(self, dt):
4902 if dt.year == 2000:
4903 return None
4904 else:
4905 return 10*HOUR
4906 dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
4907 self.assertRaises(ValueError, dt.astimezone, tricky_notok())
4908
4909 def test_fromutc(self):
4910 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
4911 now = datetime.utcnow().replace(tzinfo=utc_real)
4912 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
4913 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
4914 enow = Eastern.fromutc(now) # doesn't blow up
4915 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
4916 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
4917 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
4918
4919 # Always converts UTC to standard time.
4920 class FauxUSTimeZone(USTimeZone):
4921 def fromutc(self, dt):
4922 return dt + self.stdoffset
4923 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
4924
4925 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
4926 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
4927 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
4928
4929 # Check around DST start.
4930 start = self.dston.replace(hour=4, tzinfo=Eastern)
4931 fstart = start.replace(tzinfo=FEastern)
4932 for wall in 23, 0, 1, 3, 4, 5:
4933 expected = start.replace(hour=wall)
4934 if wall == 23:
4935 expected -= timedelta(days=1)
4936 got = Eastern.fromutc(start)
4937 self.assertEqual(expected, got)
4938
4939 expected = fstart + FEastern.stdoffset
4940 got = FEastern.fromutc(fstart)
4941 self.assertEqual(expected, got)
4942
4943 # Ensure astimezone() calls fromutc() too.
4944 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
4945 self.assertEqual(expected, got)
4946
4947 start += HOUR
4948 fstart += HOUR
4949
4950 # Check around DST end.
4951 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
4952 fstart = start.replace(tzinfo=FEastern)
4953 for wall in 0, 1, 1, 2, 3, 4:
4954 expected = start.replace(hour=wall)
4955 got = Eastern.fromutc(start)
4956 self.assertEqual(expected, got)
4957
4958 expected = fstart + FEastern.stdoffset
4959 got = FEastern.fromutc(fstart)
4960 self.assertEqual(expected, got)
4961
4962 # Ensure astimezone() calls fromutc() too.
4963 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
4964 self.assertEqual(expected, got)
4965
4966 start += HOUR
4967 fstart += HOUR
4968
4969
4970#############################################################################
4971# oddballs
4972
4973class Oddballs(unittest.TestCase):
4974
4975 def test_bug_1028306(self):
4976 # Trying to compare a date to a datetime should act like a mixed-
4977 # type comparison, despite that datetime is a subclass of date.
4978 as_date = date.today()
4979 as_datetime = datetime.combine(as_date, time())
4980 self.assertTrue(as_date != as_datetime)
4981 self.assertTrue(as_datetime != as_date)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004982 self.assertFalse(as_date == as_datetime)
4983 self.assertFalse(as_datetime == as_date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004984 self.assertRaises(TypeError, lambda: as_date < as_datetime)
4985 self.assertRaises(TypeError, lambda: as_datetime < as_date)
4986 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
4987 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
4988 self.assertRaises(TypeError, lambda: as_date > as_datetime)
4989 self.assertRaises(TypeError, lambda: as_datetime > as_date)
4990 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
4991 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
4992
Raymond Hettinger15f44ab2016-08-30 10:47:49 -07004993 # Nevertheless, comparison should work with the base-class (date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004994 # projection if use of a date method is forced.
4995 self.assertEqual(as_date.__eq__(as_datetime), True)
4996 different_day = (as_date.day + 1) % 20 + 1
4997 as_different = as_datetime.replace(day= different_day)
4998 self.assertEqual(as_date.__eq__(as_different), False)
4999
5000 # And date should compare with other subclasses of date. If a
5001 # subclass wants to stop this, it's up to the subclass to do so.
5002 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
5003 self.assertEqual(as_date, date_sc)
5004 self.assertEqual(date_sc, as_date)
5005
5006 # Ditto for datetimes.
5007 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
5008 as_date.day, 0, 0, 0)
5009 self.assertEqual(as_datetime, datetime_sc)
5010 self.assertEqual(datetime_sc, as_datetime)
5011
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04005012 def test_extra_attributes(self):
5013 for x in [date.today(),
5014 time(),
5015 datetime.utcnow(),
5016 timedelta(),
5017 tzinfo(),
5018 timezone(timedelta())]:
5019 with self.assertRaises(AttributeError):
5020 x.abc = 1
5021
5022 def test_check_arg_types(self):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04005023 class Number:
5024 def __init__(self, value):
5025 self.value = value
5026 def __int__(self):
5027 return self.value
5028
5029 for xx in [decimal.Decimal(10),
5030 decimal.Decimal('10.9'),
5031 Number(10)]:
Serhiy Storchaka6a44f6e2019-02-25 17:57:58 +02005032 with self.assertWarns(DeprecationWarning):
5033 self.assertEqual(datetime(10, 10, 10, 10, 10, 10, 10),
5034 datetime(xx, xx, xx, xx, xx, xx, xx))
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04005035
5036 with self.assertRaisesRegex(TypeError, '^an integer is required '
R David Murray44b548d2016-09-08 13:59:53 -04005037 r'\(got type str\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04005038 datetime(10, 10, '10')
5039
5040 f10 = Number(10.9)
5041 with self.assertRaisesRegex(TypeError, '^__int__ returned non-int '
Serhiy Storchaka6a44f6e2019-02-25 17:57:58 +02005042 r'\(type float\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04005043 datetime(10, 10, f10)
5044
5045 class Float(float):
5046 pass
5047 s10 = Float(10.9)
5048 with self.assertRaisesRegex(TypeError, '^integer argument expected, '
5049 'got float$'):
5050 datetime(10, 10, s10)
5051
5052 with self.assertRaises(TypeError):
5053 datetime(10., 10, 10)
5054 with self.assertRaises(TypeError):
5055 datetime(10, 10., 10)
5056 with self.assertRaises(TypeError):
5057 datetime(10, 10, 10.)
5058 with self.assertRaises(TypeError):
5059 datetime(10, 10, 10, 10.)
5060 with self.assertRaises(TypeError):
5061 datetime(10, 10, 10, 10, 10.)
5062 with self.assertRaises(TypeError):
5063 datetime(10, 10, 10, 10, 10, 10.)
5064 with self.assertRaises(TypeError):
5065 datetime(10, 10, 10, 10, 10, 10, 10.)
5066
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005067#############################################################################
5068# Local Time Disambiguation
5069
5070# An experimental reimplementation of fromutc that respects the "fold" flag.
5071
5072class tzinfo2(tzinfo):
5073
5074 def fromutc(self, dt):
5075 "datetime in UTC -> datetime in local time."
5076
5077 if not isinstance(dt, datetime):
5078 raise TypeError("fromutc() requires a datetime argument")
5079 if dt.tzinfo is not self:
5080 raise ValueError("dt.tzinfo is not self")
5081 # Returned value satisfies
5082 # dt + ldt.utcoffset() = ldt
5083 off0 = dt.replace(fold=0).utcoffset()
5084 off1 = dt.replace(fold=1).utcoffset()
5085 if off0 is None or off1 is None or dt.dst() is None:
5086 raise ValueError
5087 if off0 == off1:
5088 ldt = dt + off0
5089 off1 = ldt.utcoffset()
5090 if off0 == off1:
5091 return ldt
5092 # Now, we discovered both possible offsets, so
5093 # we can just try four possible solutions:
5094 for off in [off0, off1]:
5095 ldt = dt + off
5096 if ldt.utcoffset() == off:
5097 return ldt
5098 ldt = ldt.replace(fold=1)
5099 if ldt.utcoffset() == off:
5100 return ldt
5101
5102 raise ValueError("No suitable local time found")
5103
5104# Reimplementing simplified US timezones to respect the "fold" flag:
5105
5106class USTimeZone2(tzinfo2):
5107
5108 def __init__(self, hours, reprname, stdname, dstname):
5109 self.stdoffset = timedelta(hours=hours)
5110 self.reprname = reprname
5111 self.stdname = stdname
5112 self.dstname = dstname
5113
5114 def __repr__(self):
5115 return self.reprname
5116
5117 def tzname(self, dt):
5118 if self.dst(dt):
5119 return self.dstname
5120 else:
5121 return self.stdname
5122
5123 def utcoffset(self, dt):
5124 return self.stdoffset + self.dst(dt)
5125
5126 def dst(self, dt):
5127 if dt is None or dt.tzinfo is None:
5128 # An exception instead may be sensible here, in one or more of
5129 # the cases.
5130 return ZERO
5131 assert dt.tzinfo is self
5132
5133 # Find first Sunday in April.
5134 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
5135 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
5136
5137 # Find last Sunday in October.
5138 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
5139 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
5140
5141 # Can't compare naive to aware objects, so strip the timezone from
5142 # dt first.
5143 dt = dt.replace(tzinfo=None)
5144 if start + HOUR <= dt < end:
5145 # DST is in effect.
5146 return HOUR
5147 elif end <= dt < end + HOUR:
5148 # Fold (an ambiguous hour): use dt.fold to disambiguate.
5149 return ZERO if dt.fold else HOUR
5150 elif start <= dt < start + HOUR:
5151 # Gap (a non-existent hour): reverse the fold rule.
5152 return HOUR if dt.fold else ZERO
5153 else:
5154 # DST is off.
5155 return ZERO
5156
5157Eastern2 = USTimeZone2(-5, "Eastern2", "EST", "EDT")
5158Central2 = USTimeZone2(-6, "Central2", "CST", "CDT")
5159Mountain2 = USTimeZone2(-7, "Mountain2", "MST", "MDT")
5160Pacific2 = USTimeZone2(-8, "Pacific2", "PST", "PDT")
5161
5162# Europe_Vilnius_1941 tzinfo implementation reproduces the following
5163# 1941 transition from Olson's tzdist:
5164#
5165# Zone NAME GMTOFF RULES FORMAT [UNTIL]
5166# ZoneEurope/Vilnius 1:00 - CET 1940 Aug 3
5167# 3:00 - MSK 1941 Jun 24
5168# 1:00 C-Eur CE%sT 1944 Aug
5169#
5170# $ zdump -v Europe/Vilnius | grep 1941
5171# Europe/Vilnius Mon Jun 23 20:59:59 1941 UTC = Mon Jun 23 23:59:59 1941 MSK isdst=0 gmtoff=10800
5172# Europe/Vilnius Mon Jun 23 21:00:00 1941 UTC = Mon Jun 23 23:00:00 1941 CEST isdst=1 gmtoff=7200
5173
5174class Europe_Vilnius_1941(tzinfo):
5175 def _utc_fold(self):
5176 return [datetime(1941, 6, 23, 21, tzinfo=self), # Mon Jun 23 21:00:00 1941 UTC
5177 datetime(1941, 6, 23, 22, tzinfo=self)] # Mon Jun 23 22:00:00 1941 UTC
5178
5179 def _loc_fold(self):
5180 return [datetime(1941, 6, 23, 23, tzinfo=self), # Mon Jun 23 23:00:00 1941 MSK / CEST
5181 datetime(1941, 6, 24, 0, tzinfo=self)] # Mon Jun 24 00:00:00 1941 CEST
5182
5183 def utcoffset(self, dt):
5184 fold_start, fold_stop = self._loc_fold()
5185 if dt < fold_start:
5186 return 3 * HOUR
5187 if dt < fold_stop:
5188 return (2 if dt.fold else 3) * HOUR
5189 # if dt >= fold_stop
5190 return 2 * HOUR
5191
5192 def dst(self, dt):
5193 fold_start, fold_stop = self._loc_fold()
5194 if dt < fold_start:
5195 return 0 * HOUR
5196 if dt < fold_stop:
5197 return (1 if dt.fold else 0) * HOUR
5198 # if dt >= fold_stop
5199 return 1 * HOUR
5200
5201 def tzname(self, dt):
5202 fold_start, fold_stop = self._loc_fold()
5203 if dt < fold_start:
5204 return 'MSK'
5205 if dt < fold_stop:
5206 return ('MSK', 'CEST')[dt.fold]
5207 # if dt >= fold_stop
5208 return 'CEST'
5209
5210 def fromutc(self, dt):
5211 assert dt.fold == 0
5212 assert dt.tzinfo is self
5213 if dt.year != 1941:
5214 raise NotImplementedError
5215 fold_start, fold_stop = self._utc_fold()
5216 if dt < fold_start:
5217 return dt + 3 * HOUR
5218 if dt < fold_stop:
5219 return (dt + 2 * HOUR).replace(fold=1)
5220 # if dt >= fold_stop
5221 return dt + 2 * HOUR
5222
5223
5224class TestLocalTimeDisambiguation(unittest.TestCase):
5225
5226 def test_vilnius_1941_fromutc(self):
5227 Vilnius = Europe_Vilnius_1941()
5228
5229 gdt = datetime(1941, 6, 23, 20, 59, 59, tzinfo=timezone.utc)
5230 ldt = gdt.astimezone(Vilnius)
5231 self.assertEqual(ldt.strftime("%c %Z%z"),
5232 'Mon Jun 23 23:59:59 1941 MSK+0300')
5233 self.assertEqual(ldt.fold, 0)
5234 self.assertFalse(ldt.dst())
5235
5236 gdt = datetime(1941, 6, 23, 21, tzinfo=timezone.utc)
5237 ldt = gdt.astimezone(Vilnius)
5238 self.assertEqual(ldt.strftime("%c %Z%z"),
5239 'Mon Jun 23 23:00:00 1941 CEST+0200')
5240 self.assertEqual(ldt.fold, 1)
5241 self.assertTrue(ldt.dst())
5242
5243 gdt = datetime(1941, 6, 23, 22, tzinfo=timezone.utc)
5244 ldt = gdt.astimezone(Vilnius)
5245 self.assertEqual(ldt.strftime("%c %Z%z"),
5246 'Tue Jun 24 00:00:00 1941 CEST+0200')
5247 self.assertEqual(ldt.fold, 0)
5248 self.assertTrue(ldt.dst())
5249
5250 def test_vilnius_1941_toutc(self):
5251 Vilnius = Europe_Vilnius_1941()
5252
5253 ldt = datetime(1941, 6, 23, 22, 59, 59, tzinfo=Vilnius)
5254 gdt = ldt.astimezone(timezone.utc)
5255 self.assertEqual(gdt.strftime("%c %Z"),
5256 'Mon Jun 23 19:59:59 1941 UTC')
5257
5258 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius)
5259 gdt = ldt.astimezone(timezone.utc)
5260 self.assertEqual(gdt.strftime("%c %Z"),
5261 'Mon Jun 23 20:59:59 1941 UTC')
5262
5263 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius, fold=1)
5264 gdt = ldt.astimezone(timezone.utc)
5265 self.assertEqual(gdt.strftime("%c %Z"),
5266 'Mon Jun 23 21:59:59 1941 UTC')
5267
5268 ldt = datetime(1941, 6, 24, 0, tzinfo=Vilnius)
5269 gdt = ldt.astimezone(timezone.utc)
5270 self.assertEqual(gdt.strftime("%c %Z"),
5271 'Mon Jun 23 22:00:00 1941 UTC')
5272
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005273 def test_constructors(self):
5274 t = time(0, fold=1)
5275 dt = datetime(1, 1, 1, fold=1)
5276 self.assertEqual(t.fold, 1)
5277 self.assertEqual(dt.fold, 1)
5278 with self.assertRaises(TypeError):
5279 time(0, 0, 0, 0, None, 0)
5280
5281 def test_member(self):
5282 dt = datetime(1, 1, 1, fold=1)
5283 t = dt.time()
5284 self.assertEqual(t.fold, 1)
5285 t = dt.timetz()
5286 self.assertEqual(t.fold, 1)
5287
5288 def test_replace(self):
5289 t = time(0)
5290 dt = datetime(1, 1, 1)
5291 self.assertEqual(t.replace(fold=1).fold, 1)
5292 self.assertEqual(dt.replace(fold=1).fold, 1)
5293 self.assertEqual(t.replace(fold=0).fold, 0)
5294 self.assertEqual(dt.replace(fold=0).fold, 0)
5295 # Check that replacement of other fields does not change "fold".
5296 t = t.replace(fold=1, tzinfo=Eastern)
5297 dt = dt.replace(fold=1, tzinfo=Eastern)
5298 self.assertEqual(t.replace(tzinfo=None).fold, 1)
5299 self.assertEqual(dt.replace(tzinfo=None).fold, 1)
Serhiy Storchaka314d6fc2017-03-31 22:48:16 +03005300 # Out of bounds.
5301 with self.assertRaises(ValueError):
5302 t.replace(fold=2)
5303 with self.assertRaises(ValueError):
5304 dt.replace(fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005305 # Check that fold is a keyword-only argument
5306 with self.assertRaises(TypeError):
5307 t.replace(1, 1, 1, None, 1)
5308 with self.assertRaises(TypeError):
5309 dt.replace(1, 1, 1, 1, 1, 1, 1, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04005310
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005311 def test_comparison(self):
5312 t = time(0)
5313 dt = datetime(1, 1, 1)
5314 self.assertEqual(t, t.replace(fold=1))
5315 self.assertEqual(dt, dt.replace(fold=1))
5316
5317 def test_hash(self):
5318 t = time(0)
5319 dt = datetime(1, 1, 1)
5320 self.assertEqual(hash(t), hash(t.replace(fold=1)))
5321 self.assertEqual(hash(dt), hash(dt.replace(fold=1)))
5322
5323 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5324 def test_fromtimestamp(self):
5325 s = 1414906200
5326 dt0 = datetime.fromtimestamp(s)
5327 dt1 = datetime.fromtimestamp(s + 3600)
5328 self.assertEqual(dt0.fold, 0)
5329 self.assertEqual(dt1.fold, 1)
5330
5331 @support.run_with_tz('Australia/Lord_Howe')
5332 def test_fromtimestamp_lord_howe(self):
5333 tm = _time.localtime(1.4e9)
5334 if _time.strftime('%Z%z', tm) != 'LHST+1030':
5335 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
5336 # $ TZ=Australia/Lord_Howe date -r 1428158700
5337 # Sun Apr 5 01:45:00 LHDT 2015
5338 # $ TZ=Australia/Lord_Howe date -r 1428160500
5339 # Sun Apr 5 01:45:00 LHST 2015
5340 s = 1428158700
5341 t0 = datetime.fromtimestamp(s)
5342 t1 = datetime.fromtimestamp(s + 1800)
5343 self.assertEqual(t0, t1)
5344 self.assertEqual(t0.fold, 0)
5345 self.assertEqual(t1.fold, 1)
5346
Ammar Askar96d1e692018-07-25 09:54:58 -07005347 def test_fromtimestamp_low_fold_detection(self):
5348 # Ensure that fold detection doesn't cause an
5349 # OSError for really low values, see bpo-29097
5350 self.assertEqual(datetime.fromtimestamp(0).fold, 0)
5351
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005352 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5353 def test_timestamp(self):
5354 dt0 = datetime(2014, 11, 2, 1, 30)
5355 dt1 = dt0.replace(fold=1)
5356 self.assertEqual(dt0.timestamp() + 3600,
5357 dt1.timestamp())
5358
5359 @support.run_with_tz('Australia/Lord_Howe')
5360 def test_timestamp_lord_howe(self):
5361 tm = _time.localtime(1.4e9)
5362 if _time.strftime('%Z%z', tm) != 'LHST+1030':
5363 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
5364 t = datetime(2015, 4, 5, 1, 45)
5365 s0 = t.replace(fold=0).timestamp()
5366 s1 = t.replace(fold=1).timestamp()
5367 self.assertEqual(s0 + 1800, s1)
5368
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005369 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5370 def test_astimezone(self):
5371 dt0 = datetime(2014, 11, 2, 1, 30)
5372 dt1 = dt0.replace(fold=1)
5373 # Convert both naive instances to aware.
5374 adt0 = dt0.astimezone()
5375 adt1 = dt1.astimezone()
5376 # Check that the first instance in DST zone and the second in STD
5377 self.assertEqual(adt0.tzname(), 'EDT')
5378 self.assertEqual(adt1.tzname(), 'EST')
5379 self.assertEqual(adt0 + HOUR, adt1)
5380 # Aware instances with fixed offset tzinfo's always have fold=0
5381 self.assertEqual(adt0.fold, 0)
5382 self.assertEqual(adt1.fold, 0)
5383
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005384 def test_pickle_fold(self):
5385 t = time(fold=1)
5386 dt = datetime(1, 1, 1, fold=1)
5387 for pickler, unpickler, proto in pickle_choices:
5388 for x in [t, dt]:
5389 s = pickler.dumps(x, proto)
5390 y = unpickler.loads(s)
5391 self.assertEqual(x, y)
5392 self.assertEqual((0 if proto < 4 else x.fold), y.fold)
5393
5394 def test_repr(self):
5395 t = time(fold=1)
5396 dt = datetime(1, 1, 1, fold=1)
5397 self.assertEqual(repr(t), 'datetime.time(0, 0, fold=1)')
5398 self.assertEqual(repr(dt),
5399 'datetime.datetime(1, 1, 1, 0, 0, fold=1)')
5400
5401 def test_dst(self):
5402 # Let's first establish that things work in regular times.
5403 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
5404 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
5405 self.assertEqual(dt_summer.dst(), HOUR)
5406 self.assertEqual(dt_winter.dst(), ZERO)
5407 # The disambiguation flag is ignored
5408 self.assertEqual(dt_summer.replace(fold=1).dst(), HOUR)
5409 self.assertEqual(dt_winter.replace(fold=1).dst(), ZERO)
5410
5411 # Pick local time in the fold.
5412 for minute in [0, 30, 59]:
5413 dt = datetime(2002, 10, 27, 1, minute, tzinfo=Eastern2)
5414 # With fold=0 (the default) it is in DST.
5415 self.assertEqual(dt.dst(), HOUR)
5416 # With fold=1 it is in STD.
5417 self.assertEqual(dt.replace(fold=1).dst(), ZERO)
5418
5419 # Pick local time in the gap.
5420 for minute in [0, 30, 59]:
5421 dt = datetime(2002, 4, 7, 2, minute, tzinfo=Eastern2)
5422 # With fold=0 (the default) it is in STD.
5423 self.assertEqual(dt.dst(), ZERO)
5424 # With fold=1 it is in DST.
5425 self.assertEqual(dt.replace(fold=1).dst(), HOUR)
5426
5427
5428 def test_utcoffset(self):
5429 # Let's first establish that things work in regular times.
5430 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
5431 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
5432 self.assertEqual(dt_summer.utcoffset(), -4 * HOUR)
5433 self.assertEqual(dt_winter.utcoffset(), -5 * HOUR)
5434 # The disambiguation flag is ignored
5435 self.assertEqual(dt_summer.replace(fold=1).utcoffset(), -4 * HOUR)
5436 self.assertEqual(dt_winter.replace(fold=1).utcoffset(), -5 * HOUR)
5437
5438 def test_fromutc(self):
5439 # Let's first establish that things work in regular times.
5440 u_summer = datetime(2002, 10, 27, 6, tzinfo=Eastern2) - timedelta.resolution
5441 u_winter = datetime(2002, 10, 27, 7, tzinfo=Eastern2)
5442 t_summer = Eastern2.fromutc(u_summer)
5443 t_winter = Eastern2.fromutc(u_winter)
5444 self.assertEqual(t_summer, u_summer - 4 * HOUR)
5445 self.assertEqual(t_winter, u_winter - 5 * HOUR)
5446 self.assertEqual(t_summer.fold, 0)
5447 self.assertEqual(t_winter.fold, 0)
5448
5449 # What happens in the fall-back fold?
5450 u = datetime(2002, 10, 27, 5, 30, tzinfo=Eastern2)
5451 t0 = Eastern2.fromutc(u)
5452 u += HOUR
5453 t1 = Eastern2.fromutc(u)
5454 self.assertEqual(t0, t1)
5455 self.assertEqual(t0.fold, 0)
5456 self.assertEqual(t1.fold, 1)
5457 # The tricky part is when u is in the local fold:
5458 u = datetime(2002, 10, 27, 1, 30, tzinfo=Eastern2)
5459 t = Eastern2.fromutc(u)
5460 self.assertEqual((t.day, t.hour), (26, 21))
5461 # .. or gets into the local fold after a standard time adjustment
5462 u = datetime(2002, 10, 27, 6, 30, tzinfo=Eastern2)
5463 t = Eastern2.fromutc(u)
5464 self.assertEqual((t.day, t.hour), (27, 1))
5465
5466 # What happens in the spring-forward gap?
5467 u = datetime(2002, 4, 7, 2, 0, tzinfo=Eastern2)
5468 t = Eastern2.fromutc(u)
5469 self.assertEqual((t.day, t.hour), (6, 21))
5470
5471 def test_mixed_compare_regular(self):
5472 t = datetime(2000, 1, 1, tzinfo=Eastern2)
5473 self.assertEqual(t, t.astimezone(timezone.utc))
5474 t = datetime(2000, 6, 1, tzinfo=Eastern2)
5475 self.assertEqual(t, t.astimezone(timezone.utc))
5476
5477 def test_mixed_compare_fold(self):
5478 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
5479 t_fold_utc = t_fold.astimezone(timezone.utc)
5480 self.assertNotEqual(t_fold, t_fold_utc)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04005481 self.assertNotEqual(t_fold_utc, t_fold)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005482
5483 def test_mixed_compare_gap(self):
5484 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
5485 t_gap_utc = t_gap.astimezone(timezone.utc)
5486 self.assertNotEqual(t_gap, t_gap_utc)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04005487 self.assertNotEqual(t_gap_utc, t_gap)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005488
5489 def test_hash_aware(self):
5490 t = datetime(2000, 1, 1, tzinfo=Eastern2)
5491 self.assertEqual(hash(t), hash(t.replace(fold=1)))
5492 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
5493 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
5494 self.assertEqual(hash(t_fold), hash(t_fold.replace(fold=1)))
5495 self.assertEqual(hash(t_gap), hash(t_gap.replace(fold=1)))
5496
5497SEC = timedelta(0, 1)
5498
5499def pairs(iterable):
5500 a, b = itertools.tee(iterable)
5501 next(b, None)
5502 return zip(a, b)
5503
5504class ZoneInfo(tzinfo):
5505 zoneroot = '/usr/share/zoneinfo'
5506 def __init__(self, ut, ti):
5507 """
5508
5509 :param ut: array
5510 Array of transition point timestamps
5511 :param ti: list
5512 A list of (offset, isdst, abbr) tuples
5513 :return: None
5514 """
5515 self.ut = ut
5516 self.ti = ti
5517 self.lt = self.invert(ut, ti)
5518
5519 @staticmethod
5520 def invert(ut, ti):
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04005521 lt = (array('q', ut), array('q', ut))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005522 if ut:
5523 offset = ti[0][0] // SEC
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04005524 lt[0][0] += offset
5525 lt[1][0] += offset
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005526 for i in range(1, len(ut)):
5527 lt[0][i] += ti[i-1][0] // SEC
5528 lt[1][i] += ti[i][0] // SEC
5529 return lt
5530
5531 @classmethod
5532 def fromfile(cls, fileobj):
5533 if fileobj.read(4).decode() != "TZif":
5534 raise ValueError("not a zoneinfo file")
5535 fileobj.seek(32)
5536 counts = array('i')
5537 counts.fromfile(fileobj, 3)
5538 if sys.byteorder != 'big':
5539 counts.byteswap()
5540
5541 ut = array('i')
5542 ut.fromfile(fileobj, counts[0])
5543 if sys.byteorder != 'big':
5544 ut.byteswap()
5545
5546 type_indices = array('B')
5547 type_indices.fromfile(fileobj, counts[0])
5548
5549 ttis = []
5550 for i in range(counts[1]):
5551 ttis.append(struct.unpack(">lbb", fileobj.read(6)))
5552
5553 abbrs = fileobj.read(counts[2])
5554
5555 # Convert ttis
5556 for i, (gmtoff, isdst, abbrind) in enumerate(ttis):
5557 abbr = abbrs[abbrind:abbrs.find(0, abbrind)].decode()
5558 ttis[i] = (timedelta(0, gmtoff), isdst, abbr)
5559
5560 ti = [None] * len(ut)
5561 for i, idx in enumerate(type_indices):
5562 ti[i] = ttis[idx]
5563
5564 self = cls(ut, ti)
5565
5566 return self
5567
5568 @classmethod
5569 def fromname(cls, name):
5570 path = os.path.join(cls.zoneroot, name)
5571 with open(path, 'rb') as f:
5572 return cls.fromfile(f)
5573
5574 EPOCHORDINAL = date(1970, 1, 1).toordinal()
5575
5576 def fromutc(self, dt):
5577 """datetime in UTC -> datetime in local time."""
5578
5579 if not isinstance(dt, datetime):
5580 raise TypeError("fromutc() requires a datetime argument")
5581 if dt.tzinfo is not self:
5582 raise ValueError("dt.tzinfo is not self")
5583
5584 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
5585 + dt.hour * 3600
5586 + dt.minute * 60
5587 + dt.second)
5588
5589 if timestamp < self.ut[1]:
5590 tti = self.ti[0]
5591 fold = 0
5592 else:
5593 idx = bisect.bisect_right(self.ut, timestamp)
5594 assert self.ut[idx-1] <= timestamp
5595 assert idx == len(self.ut) or timestamp < self.ut[idx]
5596 tti_prev, tti = self.ti[idx-2:idx]
5597 # Detect fold
5598 shift = tti_prev[0] - tti[0]
5599 fold = (shift > timedelta(0, timestamp - self.ut[idx-1]))
5600 dt += tti[0]
5601 if fold:
5602 return dt.replace(fold=1)
5603 else:
5604 return dt
5605
5606 def _find_ti(self, dt, i):
5607 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
5608 + dt.hour * 3600
5609 + dt.minute * 60
5610 + dt.second)
5611 lt = self.lt[dt.fold]
5612 idx = bisect.bisect_right(lt, timestamp)
5613
5614 return self.ti[max(0, idx - 1)][i]
5615
5616 def utcoffset(self, dt):
5617 return self._find_ti(dt, 0)
5618
5619 def dst(self, dt):
5620 isdst = self._find_ti(dt, 1)
5621 # XXX: We cannot accurately determine the "save" value,
5622 # so let's return 1h whenever DST is in effect. Since
5623 # we don't use dst() in fromutc(), it is unlikely that
5624 # it will be needed for anything more than bool(dst()).
5625 return ZERO if isdst else HOUR
5626
5627 def tzname(self, dt):
5628 return self._find_ti(dt, 2)
5629
5630 @classmethod
5631 def zonenames(cls, zonedir=None):
5632 if zonedir is None:
5633 zonedir = cls.zoneroot
Alexander Belopolsky1b8f26c2016-08-11 11:01:45 -04005634 zone_tab = os.path.join(zonedir, 'zone.tab')
5635 try:
5636 f = open(zone_tab)
5637 except OSError:
5638 return
5639 with f:
5640 for line in f:
5641 line = line.strip()
5642 if line and not line.startswith('#'):
5643 yield line.split()[2]
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005644
5645 @classmethod
5646 def stats(cls, start_year=1):
5647 count = gap_count = fold_count = zeros_count = 0
5648 min_gap = min_fold = timedelta.max
5649 max_gap = max_fold = ZERO
5650 min_gap_datetime = max_gap_datetime = datetime.min
5651 min_gap_zone = max_gap_zone = None
5652 min_fold_datetime = max_fold_datetime = datetime.min
5653 min_fold_zone = max_fold_zone = None
5654 stats_since = datetime(start_year, 1, 1) # Starting from 1970 eliminates a lot of noise
5655 for zonename in cls.zonenames():
5656 count += 1
5657 tz = cls.fromname(zonename)
5658 for dt, shift in tz.transitions():
5659 if dt < stats_since:
5660 continue
5661 if shift > ZERO:
5662 gap_count += 1
5663 if (shift, dt) > (max_gap, max_gap_datetime):
5664 max_gap = shift
5665 max_gap_zone = zonename
5666 max_gap_datetime = dt
5667 if (shift, datetime.max - dt) < (min_gap, datetime.max - min_gap_datetime):
5668 min_gap = shift
5669 min_gap_zone = zonename
5670 min_gap_datetime = dt
5671 elif shift < ZERO:
5672 fold_count += 1
5673 shift = -shift
5674 if (shift, dt) > (max_fold, max_fold_datetime):
5675 max_fold = shift
5676 max_fold_zone = zonename
5677 max_fold_datetime = dt
5678 if (shift, datetime.max - dt) < (min_fold, datetime.max - min_fold_datetime):
5679 min_fold = shift
5680 min_fold_zone = zonename
5681 min_fold_datetime = dt
5682 else:
5683 zeros_count += 1
5684 trans_counts = (gap_count, fold_count, zeros_count)
5685 print("Number of zones: %5d" % count)
5686 print("Number of transitions: %5d = %d (gaps) + %d (folds) + %d (zeros)" %
5687 ((sum(trans_counts),) + trans_counts))
5688 print("Min gap: %16s at %s in %s" % (min_gap, min_gap_datetime, min_gap_zone))
5689 print("Max gap: %16s at %s in %s" % (max_gap, max_gap_datetime, max_gap_zone))
5690 print("Min fold: %16s at %s in %s" % (min_fold, min_fold_datetime, min_fold_zone))
5691 print("Max fold: %16s at %s in %s" % (max_fold, max_fold_datetime, max_fold_zone))
5692
5693
5694 def transitions(self):
5695 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
5696 shift = ti[0] - prev_ti[0]
5697 yield datetime.utcfromtimestamp(t), shift
5698
5699 def nondst_folds(self):
5700 """Find all folds with the same value of isdst on both sides of the transition."""
5701 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
5702 shift = ti[0] - prev_ti[0]
5703 if shift < ZERO and ti[1] == prev_ti[1]:
5704 yield datetime.utcfromtimestamp(t), -shift, prev_ti[2], ti[2]
5705
5706 @classmethod
5707 def print_all_nondst_folds(cls, same_abbr=False, start_year=1):
5708 count = 0
5709 for zonename in cls.zonenames():
5710 tz = cls.fromname(zonename)
5711 for dt, shift, prev_abbr, abbr in tz.nondst_folds():
5712 if dt.year < start_year or same_abbr and prev_abbr != abbr:
5713 continue
5714 count += 1
5715 print("%3d) %-30s %s %10s %5s -> %s" %
5716 (count, zonename, dt, shift, prev_abbr, abbr))
5717
5718 def folds(self):
5719 for t, shift in self.transitions():
5720 if shift < ZERO:
5721 yield t, -shift
5722
5723 def gaps(self):
5724 for t, shift in self.transitions():
5725 if shift > ZERO:
5726 yield t, shift
5727
5728 def zeros(self):
5729 for t, shift in self.transitions():
5730 if not shift:
5731 yield t
5732
5733
5734class ZoneInfoTest(unittest.TestCase):
5735 zonename = 'America/New_York'
5736
5737 def setUp(self):
5738 if sys.platform == "win32":
5739 self.skipTest("Skipping zoneinfo tests on Windows")
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04005740 try:
5741 self.tz = ZoneInfo.fromname(self.zonename)
5742 except FileNotFoundError as err:
5743 self.skipTest("Skipping %s: %s" % (self.zonename, err))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005744
5745 def assertEquivDatetimes(self, a, b):
5746 self.assertEqual((a.replace(tzinfo=None), a.fold, id(a.tzinfo)),
5747 (b.replace(tzinfo=None), b.fold, id(b.tzinfo)))
5748
5749 def test_folds(self):
5750 tz = self.tz
5751 for dt, shift in tz.folds():
5752 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
5753 udt = dt + x
5754 ldt = tz.fromutc(udt.replace(tzinfo=tz))
5755 self.assertEqual(ldt.fold, 1)
5756 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
5757 self.assertEquivDatetimes(adt, ldt)
5758 utcoffset = ldt.utcoffset()
5759 self.assertEqual(ldt.replace(tzinfo=None), udt + utcoffset)
5760 # Round trip
5761 self.assertEquivDatetimes(ldt.astimezone(timezone.utc),
5762 udt.replace(tzinfo=timezone.utc))
5763
5764
5765 for x in [-timedelta.resolution, shift]:
5766 udt = dt + x
5767 udt = udt.replace(tzinfo=tz)
5768 ldt = tz.fromutc(udt)
5769 self.assertEqual(ldt.fold, 0)
5770
5771 def test_gaps(self):
5772 tz = self.tz
5773 for dt, shift in tz.gaps():
5774 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
5775 udt = dt + x
5776 udt = udt.replace(tzinfo=tz)
5777 ldt = tz.fromutc(udt)
5778 self.assertEqual(ldt.fold, 0)
5779 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
5780 self.assertEquivDatetimes(adt, ldt)
5781 utcoffset = ldt.utcoffset()
5782 self.assertEqual(ldt.replace(tzinfo=None), udt.replace(tzinfo=None) + utcoffset)
5783 # Create a local time inside the gap
5784 ldt = tz.fromutc(dt.replace(tzinfo=tz)) - shift + x
5785 self.assertLess(ldt.replace(fold=1).utcoffset(),
5786 ldt.replace(fold=0).utcoffset(),
5787 "At %s." % ldt)
5788
5789 for x in [-timedelta.resolution, shift]:
5790 udt = dt + x
5791 ldt = tz.fromutc(udt.replace(tzinfo=tz))
5792 self.assertEqual(ldt.fold, 0)
5793
5794 def test_system_transitions(self):
5795 if ('Riyadh8' in self.zonename or
5796 # From tzdata NEWS file:
5797 # The files solar87, solar88, and solar89 are no longer distributed.
5798 # They were a negative experiment - that is, a demonstration that
5799 # tz data can represent solar time only with some difficulty and error.
5800 # Their presence in the distribution caused confusion, as Riyadh
5801 # civil time was generally not solar time in those years.
5802 self.zonename.startswith('right/')):
5803 self.skipTest("Skipping %s" % self.zonename)
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04005804 tz = self.tz
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005805 TZ = os.environ.get('TZ')
5806 os.environ['TZ'] = self.zonename
5807 try:
5808 _time.tzset()
5809 for udt, shift in tz.transitions():
Alexander Belopolsky10c2dd22016-08-12 19:08:15 -04005810 if udt.year >= 2037:
5811 # System support for times around the end of 32-bit time_t
5812 # and later is flaky on many systems.
5813 break
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005814 s0 = (udt - datetime(1970, 1, 1)) // SEC
5815 ss = shift // SEC # shift seconds
5816 for x in [-40 * 3600, -20*3600, -1, 0,
5817 ss - 1, ss + 20 * 3600, ss + 40 * 3600]:
5818 s = s0 + x
5819 sdt = datetime.fromtimestamp(s)
5820 tzdt = datetime.fromtimestamp(s, tz).replace(tzinfo=None)
5821 self.assertEquivDatetimes(sdt, tzdt)
5822 s1 = sdt.timestamp()
5823 self.assertEqual(s, s1)
5824 if ss > 0: # gap
5825 # Create local time inside the gap
5826 dt = datetime.fromtimestamp(s0) - shift / 2
5827 ts0 = dt.timestamp()
5828 ts1 = dt.replace(fold=1).timestamp()
5829 self.assertEqual(ts0, s0 + ss / 2)
5830 self.assertEqual(ts1, s0 - ss / 2)
5831 finally:
5832 if TZ is None:
5833 del os.environ['TZ']
5834 else:
5835 os.environ['TZ'] = TZ
5836 _time.tzset()
5837
5838
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04005839class ZoneInfoCompleteTest(unittest.TestSuite):
5840 def __init__(self):
5841 tests = []
5842 if is_resource_enabled('tzdata'):
5843 for name in ZoneInfo.zonenames():
5844 Test = type('ZoneInfoTest[%s]' % name, (ZoneInfoTest,), {})
5845 Test.zonename = name
5846 for method in dir(Test):
5847 if method.startswith('test_'):
5848 tests.append(Test(method))
5849 super().__init__(tests)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005850
5851# Iran had a sub-minute UTC offset before 1946.
5852class IranTest(ZoneInfoTest):
Alexander Belopolsky07e2a0a2016-07-30 11:41:02 -04005853 zonename = 'Asia/Tehran'
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005854
Paul Ganssle04af5b12018-01-24 17:29:30 -05005855
5856class CapiTest(unittest.TestCase):
5857 def setUp(self):
5858 # Since the C API is not present in the _Pure tests, skip all tests
5859 if self.__class__.__name__.endswith('Pure'):
5860 self.skipTest('Not relevant in pure Python')
5861
5862 # This *must* be called, and it must be called first, so until either
5863 # restriction is loosened, we'll call it as part of test setup
5864 _testcapi.test_datetime_capi()
5865
5866 def test_utc_capi(self):
5867 for use_macro in (True, False):
5868 capi_utc = _testcapi.get_timezone_utc_capi(use_macro)
5869
5870 with self.subTest(use_macro=use_macro):
5871 self.assertIs(capi_utc, timezone.utc)
5872
5873 def test_timezones_capi(self):
5874 est_capi, est_macro, est_macro_nn = _testcapi.make_timezones_capi()
5875
5876 exp_named = timezone(timedelta(hours=-5), "EST")
5877 exp_unnamed = timezone(timedelta(hours=-5))
5878
5879 cases = [
5880 ('est_capi', est_capi, exp_named),
5881 ('est_macro', est_macro, exp_named),
5882 ('est_macro_nn', est_macro_nn, exp_unnamed)
5883 ]
5884
5885 for name, tz_act, tz_exp in cases:
5886 with self.subTest(name=name):
5887 self.assertEqual(tz_act, tz_exp)
5888
5889 dt1 = datetime(2000, 2, 4, tzinfo=tz_act)
5890 dt2 = datetime(2000, 2, 4, tzinfo=tz_exp)
5891
5892 self.assertEqual(dt1, dt2)
5893 self.assertEqual(dt1.tzname(), dt2.tzname())
5894
5895 dt_utc = datetime(2000, 2, 4, 5, tzinfo=timezone.utc)
5896
5897 self.assertEqual(dt1.astimezone(timezone.utc), dt_utc)
5898
Paul Gansslea049f572018-02-22 15:15:32 -05005899 def test_timezones_offset_zero(self):
5900 utc0, utc1, non_utc = _testcapi.get_timezones_offset_zero()
5901
5902 with self.subTest(testname="utc0"):
5903 self.assertIs(utc0, timezone.utc)
5904
5905 with self.subTest(testname="utc1"):
5906 self.assertIs(utc1, timezone.utc)
5907
5908 with self.subTest(testname="non_utc"):
5909 self.assertIsNot(non_utc, timezone.utc)
5910
5911 non_utc_exp = timezone(timedelta(hours=0), "")
5912
5913 self.assertEqual(non_utc, non_utc_exp)
5914
5915 dt1 = datetime(2000, 2, 4, tzinfo=non_utc)
5916 dt2 = datetime(2000, 2, 4, tzinfo=non_utc_exp)
5917
5918 self.assertEqual(dt1, dt2)
5919 self.assertEqual(dt1.tzname(), dt2.tzname())
5920
Paul Ganssle04af5b12018-01-24 17:29:30 -05005921 def test_check_date(self):
5922 class DateSubclass(date):
5923 pass
5924
5925 d = date(2011, 1, 1)
5926 ds = DateSubclass(2011, 1, 1)
5927 dt = datetime(2011, 1, 1)
5928
5929 is_date = _testcapi.datetime_check_date
5930
5931 # Check the ones that should be valid
5932 self.assertTrue(is_date(d))
5933 self.assertTrue(is_date(dt))
5934 self.assertTrue(is_date(ds))
5935 self.assertTrue(is_date(d, True))
5936
5937 # Check that the subclasses do not match exactly
5938 self.assertFalse(is_date(dt, True))
5939 self.assertFalse(is_date(ds, True))
5940
5941 # Check that various other things are not dates at all
5942 args = [tuple(), list(), 1, '2011-01-01',
5943 timedelta(1), timezone.utc, time(12, 00)]
5944 for arg in args:
5945 for exact in (True, False):
5946 with self.subTest(arg=arg, exact=exact):
5947 self.assertFalse(is_date(arg, exact))
5948
5949 def test_check_time(self):
5950 class TimeSubclass(time):
5951 pass
5952
5953 t = time(12, 30)
5954 ts = TimeSubclass(12, 30)
5955
5956 is_time = _testcapi.datetime_check_time
5957
5958 # Check the ones that should be valid
5959 self.assertTrue(is_time(t))
5960 self.assertTrue(is_time(ts))
5961 self.assertTrue(is_time(t, True))
5962
5963 # Check that the subclass does not match exactly
5964 self.assertFalse(is_time(ts, True))
5965
5966 # Check that various other things are not times
5967 args = [tuple(), list(), 1, '2011-01-01',
5968 timedelta(1), timezone.utc, date(2011, 1, 1)]
5969
5970 for arg in args:
5971 for exact in (True, False):
5972 with self.subTest(arg=arg, exact=exact):
5973 self.assertFalse(is_time(arg, exact))
5974
5975 def test_check_datetime(self):
5976 class DateTimeSubclass(datetime):
5977 pass
5978
5979 dt = datetime(2011, 1, 1, 12, 30)
5980 dts = DateTimeSubclass(2011, 1, 1, 12, 30)
5981
5982 is_datetime = _testcapi.datetime_check_datetime
5983
5984 # Check the ones that should be valid
5985 self.assertTrue(is_datetime(dt))
5986 self.assertTrue(is_datetime(dts))
5987 self.assertTrue(is_datetime(dt, True))
5988
5989 # Check that the subclass does not match exactly
5990 self.assertFalse(is_datetime(dts, True))
5991
5992 # Check that various other things are not datetimes
5993 args = [tuple(), list(), 1, '2011-01-01',
5994 timedelta(1), timezone.utc, date(2011, 1, 1)]
5995
5996 for arg in args:
5997 for exact in (True, False):
5998 with self.subTest(arg=arg, exact=exact):
5999 self.assertFalse(is_datetime(arg, exact))
6000
6001 def test_check_delta(self):
6002 class TimeDeltaSubclass(timedelta):
6003 pass
6004
6005 td = timedelta(1)
6006 tds = TimeDeltaSubclass(1)
6007
6008 is_timedelta = _testcapi.datetime_check_delta
6009
6010 # Check the ones that should be valid
6011 self.assertTrue(is_timedelta(td))
6012 self.assertTrue(is_timedelta(tds))
6013 self.assertTrue(is_timedelta(td, True))
6014
6015 # Check that the subclass does not match exactly
6016 self.assertFalse(is_timedelta(tds, True))
6017
6018 # Check that various other things are not timedeltas
6019 args = [tuple(), list(), 1, '2011-01-01',
6020 timezone.utc, date(2011, 1, 1), datetime(2011, 1, 1)]
6021
6022 for arg in args:
6023 for exact in (True, False):
6024 with self.subTest(arg=arg, exact=exact):
6025 self.assertFalse(is_timedelta(arg, exact))
6026
6027 def test_check_tzinfo(self):
6028 class TZInfoSubclass(tzinfo):
6029 pass
6030
6031 tzi = tzinfo()
6032 tzis = TZInfoSubclass()
6033 tz = timezone(timedelta(hours=-5))
6034
6035 is_tzinfo = _testcapi.datetime_check_tzinfo
6036
6037 # Check the ones that should be valid
6038 self.assertTrue(is_tzinfo(tzi))
6039 self.assertTrue(is_tzinfo(tz))
6040 self.assertTrue(is_tzinfo(tzis))
6041 self.assertTrue(is_tzinfo(tzi, True))
6042
6043 # Check that the subclasses do not match exactly
6044 self.assertFalse(is_tzinfo(tz, True))
6045 self.assertFalse(is_tzinfo(tzis, True))
6046
6047 # Check that various other things are not tzinfos
6048 args = [tuple(), list(), 1, '2011-01-01',
6049 date(2011, 1, 1), datetime(2011, 1, 1)]
6050
6051 for arg in args:
6052 for exact in (True, False):
6053 with self.subTest(arg=arg, exact=exact):
6054 self.assertFalse(is_tzinfo(arg, exact))
6055
Edison A98ff4d52019-05-17 13:28:42 -07006056 def test_date_from_date(self):
6057 exp_date = date(1993, 8, 26)
6058
6059 for macro in [0, 1]:
6060 with self.subTest(macro=macro):
6061 c_api_date = _testcapi.get_date_fromdate(
6062 macro,
6063 exp_date.year,
6064 exp_date.month,
6065 exp_date.day)
6066
6067 self.assertEqual(c_api_date, exp_date)
6068
6069 def test_datetime_from_dateandtime(self):
6070 exp_date = datetime(1993, 8, 26, 22, 12, 55, 99999)
6071
6072 for macro in [0, 1]:
6073 with self.subTest(macro=macro):
6074 c_api_date = _testcapi.get_datetime_fromdateandtime(
6075 macro,
6076 exp_date.year,
6077 exp_date.month,
6078 exp_date.day,
6079 exp_date.hour,
6080 exp_date.minute,
6081 exp_date.second,
6082 exp_date.microsecond)
6083
6084 self.assertEqual(c_api_date, exp_date)
6085
6086 def test_datetime_from_dateandtimeandfold(self):
6087 exp_date = datetime(1993, 8, 26, 22, 12, 55, 99999)
6088
6089 for fold in [0, 1]:
6090 for macro in [0, 1]:
6091 with self.subTest(macro=macro, fold=fold):
6092 c_api_date = _testcapi.get_datetime_fromdateandtimeandfold(
6093 macro,
6094 exp_date.year,
6095 exp_date.month,
6096 exp_date.day,
6097 exp_date.hour,
6098 exp_date.minute,
6099 exp_date.second,
6100 exp_date.microsecond,
6101 exp_date.fold)
6102
6103 self.assertEqual(c_api_date, exp_date)
6104 self.assertEqual(c_api_date.fold, exp_date.fold)
6105
6106 def test_time_from_time(self):
6107 exp_time = time(22, 12, 55, 99999)
6108
6109 for macro in [0, 1]:
6110 with self.subTest(macro=macro):
6111 c_api_time = _testcapi.get_time_fromtime(
6112 macro,
6113 exp_time.hour,
6114 exp_time.minute,
6115 exp_time.second,
6116 exp_time.microsecond)
6117
6118 self.assertEqual(c_api_time, exp_time)
6119
6120 def test_time_from_timeandfold(self):
6121 exp_time = time(22, 12, 55, 99999)
6122
6123 for fold in [0, 1]:
6124 for macro in [0, 1]:
6125 with self.subTest(macro=macro, fold=fold):
6126 c_api_time = _testcapi.get_time_fromtimeandfold(
6127 macro,
6128 exp_time.hour,
6129 exp_time.minute,
6130 exp_time.second,
6131 exp_time.microsecond,
6132 exp_time.fold)
6133
6134 self.assertEqual(c_api_time, exp_time)
6135 self.assertEqual(c_api_time.fold, exp_time.fold)
6136
6137 def test_delta_from_dsu(self):
6138 exp_delta = timedelta(26, 55, 99999)
6139
6140 for macro in [0, 1]:
6141 with self.subTest(macro=macro):
6142 c_api_delta = _testcapi.get_delta_fromdsu(
6143 macro,
6144 exp_delta.days,
6145 exp_delta.seconds,
6146 exp_delta.microseconds)
6147
6148 self.assertEqual(c_api_delta, exp_delta)
6149
Paul Ganssle4d8c8c02019-04-27 15:39:40 -04006150 def test_date_from_timestamp(self):
6151 ts = datetime(1995, 4, 12).timestamp()
6152
6153 for macro in [0, 1]:
6154 with self.subTest(macro=macro):
6155 d = _testcapi.get_date_fromtimestamp(int(ts), macro)
6156
6157 self.assertEqual(d, date(1995, 4, 12))
6158
6159 def test_datetime_from_timestamp(self):
Paul Ganssle4d8c8c02019-04-27 15:39:40 -04006160 cases = [
6161 ((1995, 4, 12), None, False),
6162 ((1995, 4, 12), None, True),
6163 ((1995, 4, 12), timezone(timedelta(hours=1)), True),
6164 ((1995, 4, 12, 14, 30), None, False),
6165 ((1995, 4, 12, 14, 30), None, True),
6166 ((1995, 4, 12, 14, 30), timezone(timedelta(hours=1)), True),
6167 ]
6168
6169 from_timestamp = _testcapi.get_datetime_fromtimestamp
6170 for case in cases:
6171 for macro in [0, 1]:
6172 with self.subTest(case=case, macro=macro):
6173 dtup, tzinfo, usetz = case
6174 dt_orig = datetime(*dtup, tzinfo=tzinfo)
6175 ts = int(dt_orig.timestamp())
6176
6177 dt_rt = from_timestamp(ts, tzinfo, usetz, macro)
6178
6179 self.assertEqual(dt_orig, dt_rt)
6180
6181
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04006182def load_tests(loader, standard_tests, pattern):
6183 standard_tests.addTest(ZoneInfoCompleteTest())
6184 return standard_tests
6185
6186
Alexander Belopolskycf86e362010-07-23 19:25:47 +00006187if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -05006188 unittest.main()