blob: af0047fafd8770e791ad7a39e782d72f2b726243 [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
2504 self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
2505 self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
Mario Corchero32318932017-10-26 01:35:41 +01002506 self.assertEqual(
2507 strptime("-00:02:01.000003", "%z").utcoffset(),
2508 -timedelta(minutes=2, seconds=1, microseconds=3)
2509 )
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002510 # Only local timezone and UTC are supported
2511 for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
2512 (-_time.timezone, _time.tzname[0])):
2513 if tzseconds < 0:
2514 sign = '-'
2515 seconds = -tzseconds
2516 else:
2517 sign ='+'
2518 seconds = tzseconds
2519 hours, minutes = divmod(seconds//60, 60)
2520 dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
Martin Panterfca22322015-11-16 09:22:19 +00002521 dt = strptime(dtstr, "%z %Z")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002522 self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
2523 self.assertEqual(dt.tzname(), tzname)
2524 # Can produce inconsistent datetime
2525 dtstr, fmt = "+1234 UTC", "%z %Z"
2526 dt = strptime(dtstr, fmt)
2527 self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
2528 self.assertEqual(dt.tzname(), 'UTC')
2529 # yet will roundtrip
2530 self.assertEqual(dt.strftime(fmt), dtstr)
2531
2532 # Produce naive datetime if no %z is provided
2533 self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
2534
2535 with self.assertRaises(ValueError): strptime("-2400", "%z")
2536 with self.assertRaises(ValueError): strptime("-000", "%z")
2537
2538 def test_more_timetuple(self):
2539 # This tests fields beyond those tested by the TestDate.test_timetuple.
2540 t = self.theclass(2004, 12, 31, 6, 22, 33)
2541 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
2542 self.assertEqual(t.timetuple(),
2543 (t.year, t.month, t.day,
2544 t.hour, t.minute, t.second,
2545 t.weekday(),
2546 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
2547 -1))
2548 tt = t.timetuple()
2549 self.assertEqual(tt.tm_year, t.year)
2550 self.assertEqual(tt.tm_mon, t.month)
2551 self.assertEqual(tt.tm_mday, t.day)
2552 self.assertEqual(tt.tm_hour, t.hour)
2553 self.assertEqual(tt.tm_min, t.minute)
2554 self.assertEqual(tt.tm_sec, t.second)
2555 self.assertEqual(tt.tm_wday, t.weekday())
2556 self.assertEqual(tt.tm_yday, t.toordinal() -
2557 date(t.year, 1, 1).toordinal() + 1)
2558 self.assertEqual(tt.tm_isdst, -1)
2559
2560 def test_more_strftime(self):
2561 # This tests fields beyond those tested by the TestDate.test_strftime.
2562 t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
2563 self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
2564 "12 31 04 000047 33 22 06 366")
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04002565 for (s, us), z in [((33, 123), "33.000123"), ((33, 0), "33"),]:
2566 tz = timezone(-timedelta(hours=2, seconds=s, microseconds=us))
2567 t = t.replace(tzinfo=tz)
2568 self.assertEqual(t.strftime("%z"), "-0200" + z)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002569
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03002570 # bpo-34482: Check that surrogates don't cause a crash.
2571 try:
2572 t.strftime('%y\ud800%m %H\ud800%M')
2573 except UnicodeEncodeError:
2574 pass
2575
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002576 def test_extract(self):
2577 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2578 self.assertEqual(dt.date(), date(2002, 3, 4))
2579 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2580
2581 def test_combine(self):
2582 d = date(2002, 3, 4)
2583 t = time(18, 45, 3, 1234)
2584 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2585 combine = self.theclass.combine
2586 dt = combine(d, t)
2587 self.assertEqual(dt, expected)
2588
2589 dt = combine(time=t, date=d)
2590 self.assertEqual(dt, expected)
2591
2592 self.assertEqual(d, dt.date())
2593 self.assertEqual(t, dt.time())
2594 self.assertEqual(dt, combine(dt.date(), dt.time()))
2595
2596 self.assertRaises(TypeError, combine) # need an arg
2597 self.assertRaises(TypeError, combine, d) # need two args
2598 self.assertRaises(TypeError, combine, t, d) # args reversed
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002599 self.assertRaises(TypeError, combine, d, t, 1) # wrong tzinfo type
2600 self.assertRaises(TypeError, combine, d, t, 1, 2) # too many args
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002601 self.assertRaises(TypeError, combine, "date", "time") # wrong types
2602 self.assertRaises(TypeError, combine, d, "time") # wrong type
2603 self.assertRaises(TypeError, combine, "date", t) # wrong type
2604
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002605 # tzinfo= argument
2606 dt = combine(d, t, timezone.utc)
2607 self.assertIs(dt.tzinfo, timezone.utc)
2608 dt = combine(d, t, tzinfo=timezone.utc)
2609 self.assertIs(dt.tzinfo, timezone.utc)
2610 t = time()
2611 dt = combine(dt, t)
2612 self.assertEqual(dt.date(), d)
2613 self.assertEqual(dt.time(), t)
2614
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002615 def test_replace(self):
2616 cls = self.theclass
2617 args = [1, 2, 3, 4, 5, 6, 7]
2618 base = cls(*args)
2619 self.assertEqual(base, base.replace())
2620
2621 i = 0
2622 for name, newval in (("year", 2),
2623 ("month", 3),
2624 ("day", 4),
2625 ("hour", 5),
2626 ("minute", 6),
2627 ("second", 7),
2628 ("microsecond", 8)):
2629 newargs = args[:]
2630 newargs[i] = newval
2631 expected = cls(*newargs)
2632 got = base.replace(**{name: newval})
2633 self.assertEqual(expected, got)
2634 i += 1
2635
2636 # Out of bounds.
2637 base = cls(2000, 2, 29)
2638 self.assertRaises(ValueError, base.replace, year=2001)
2639
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002640 @support.run_with_tz('EDT4')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002641 def test_astimezone(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002642 dt = self.theclass.now()
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002643 f = FixedOffset(44, "0044")
2644 dt_utc = dt.replace(tzinfo=timezone(timedelta(hours=-4), 'EDT'))
2645 self.assertEqual(dt.astimezone(), dt_utc) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002646 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
2647 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002648 dt_f = dt.replace(tzinfo=f) + timedelta(hours=4, minutes=44)
2649 self.assertEqual(dt.astimezone(f), dt_f) # naive
2650 self.assertEqual(dt.astimezone(tz=f), dt_f) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002651
2652 class Bogus(tzinfo):
2653 def utcoffset(self, dt): return None
2654 def dst(self, dt): return timedelta(0)
2655 bog = Bogus()
2656 self.assertRaises(ValueError, dt.astimezone, bog) # naive
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002657 self.assertEqual(dt.replace(tzinfo=bog).astimezone(f), dt_f)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002658
2659 class AlsoBogus(tzinfo):
2660 def utcoffset(self, dt): return timedelta(0)
2661 def dst(self, dt): return None
2662 alsobog = AlsoBogus()
2663 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
2664
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002665 class Broken(tzinfo):
2666 def utcoffset(self, dt): return 1
2667 def dst(self, dt): return 1
2668 broken = Broken()
2669 dt_broken = dt.replace(tzinfo=broken)
2670 with self.assertRaises(TypeError):
2671 dt_broken.astimezone()
2672
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002673 def test_subclass_datetime(self):
2674
2675 class C(self.theclass):
2676 theAnswer = 42
2677
2678 def __new__(cls, *args, **kws):
2679 temp = kws.copy()
2680 extra = temp.pop('extra')
2681 result = self.theclass.__new__(cls, *args, **temp)
2682 result.extra = extra
2683 return result
2684
2685 def newmeth(self, start):
2686 return start + self.year + self.month + self.second
2687
2688 args = 2003, 4, 14, 12, 13, 41
2689
2690 dt1 = self.theclass(*args)
2691 dt2 = C(*args, **{'extra': 7})
2692
2693 self.assertEqual(dt2.__class__, C)
2694 self.assertEqual(dt2.theAnswer, 42)
2695 self.assertEqual(dt2.extra, 7)
2696 self.assertEqual(dt1.toordinal(), dt2.toordinal())
2697 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
2698 dt1.second - 7)
2699
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002700 def test_subclass_alternate_constructors_datetime(self):
2701 # Test that alternate constructors call the constructor
2702 class DateTimeSubclass(self.theclass):
2703 def __new__(cls, *args, **kwargs):
2704 result = self.theclass.__new__(cls, *args, **kwargs)
2705 result.extra = 7
2706
2707 return result
2708
2709 args = (2003, 4, 14, 12, 30, 15, 123456)
2710 d_isoformat = '2003-04-14T12:30:15.123456' # Equivalent isoformat()
2711 utc_ts = 1050323415.123456 # UTC timestamp
2712
2713 base_d = DateTimeSubclass(*args)
2714 self.assertIsInstance(base_d, DateTimeSubclass)
2715 self.assertEqual(base_d.extra, 7)
2716
2717 # Timestamp depends on time zone, so we'll calculate the equivalent here
2718 ts = base_d.timestamp()
2719
2720 test_cases = [
Paul Ganssle89427cd2019-02-04 14:42:04 -05002721 ('fromtimestamp', (ts,), base_d),
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002722 # See https://bugs.python.org/issue32417
Paul Ganssle89427cd2019-02-04 14:42:04 -05002723 ('fromtimestamp', (ts, timezone.utc),
2724 base_d.astimezone(timezone.utc)),
2725 ('utcfromtimestamp', (utc_ts,), base_d),
2726 ('fromisoformat', (d_isoformat,), base_d),
2727 ('strptime', (d_isoformat, '%Y-%m-%dT%H:%M:%S.%f'), base_d),
2728 ('combine', (date(*args[0:3]), time(*args[3:])), base_d),
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002729 ]
2730
Paul Ganssle89427cd2019-02-04 14:42:04 -05002731 for constr_name, constr_args, expected in test_cases:
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002732 for base_obj in (DateTimeSubclass, base_d):
2733 # Test both the classmethod and method
2734 with self.subTest(base_obj_type=type(base_obj),
2735 constr_name=constr_name):
Paul Ganssle89427cd2019-02-04 14:42:04 -05002736 constructor = getattr(base_obj, constr_name)
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002737
Paul Ganssle89427cd2019-02-04 14:42:04 -05002738 dt = constructor(*constr_args)
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002739
2740 # Test that it creates the right subclass
2741 self.assertIsInstance(dt, DateTimeSubclass)
2742
2743 # Test that it's equal to the base object
Paul Ganssle89427cd2019-02-04 14:42:04 -05002744 self.assertEqual(dt, expected)
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002745
2746 # Test that it called the constructor
2747 self.assertEqual(dt.extra, 7)
2748
Paul Ganssle89427cd2019-02-04 14:42:04 -05002749 def test_subclass_now(self):
2750 # Test that alternate constructors call the constructor
2751 class DateTimeSubclass(self.theclass):
2752 def __new__(cls, *args, **kwargs):
2753 result = self.theclass.__new__(cls, *args, **kwargs)
2754 result.extra = 7
2755
2756 return result
2757
2758 test_cases = [
2759 ('now', 'now', {}),
2760 ('utcnow', 'utcnow', {}),
2761 ('now_utc', 'now', {'tz': timezone.utc}),
2762 ('now_fixed', 'now', {'tz': timezone(timedelta(hours=-5), "EST")}),
2763 ]
2764
2765 for name, meth_name, kwargs in test_cases:
2766 with self.subTest(name):
2767 constr = getattr(DateTimeSubclass, meth_name)
2768 dt = constr(**kwargs)
2769
2770 self.assertIsInstance(dt, DateTimeSubclass)
2771 self.assertEqual(dt.extra, 7)
2772
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002773 def test_fromisoformat_datetime(self):
2774 # Test that isoformat() is reversible
2775 base_dates = [
2776 (1, 1, 1),
2777 (1900, 1, 1),
2778 (2004, 11, 12),
2779 (2017, 5, 30)
2780 ]
2781
2782 base_times = [
2783 (0, 0, 0, 0),
2784 (0, 0, 0, 241000),
2785 (0, 0, 0, 234567),
2786 (12, 30, 45, 234567)
2787 ]
2788
2789 separators = [' ', 'T']
2790
2791 tzinfos = [None, timezone.utc,
2792 timezone(timedelta(hours=-5)),
2793 timezone(timedelta(hours=2))]
2794
2795 dts = [self.theclass(*date_tuple, *time_tuple, tzinfo=tzi)
2796 for date_tuple in base_dates
2797 for time_tuple in base_times
2798 for tzi in tzinfos]
2799
2800 for dt in dts:
2801 for sep in separators:
2802 dtstr = dt.isoformat(sep=sep)
2803
2804 with self.subTest(dtstr=dtstr):
2805 dt_rt = self.theclass.fromisoformat(dtstr)
2806 self.assertEqual(dt, dt_rt)
2807
2808 def test_fromisoformat_timezone(self):
2809 base_dt = self.theclass(2014, 12, 30, 12, 30, 45, 217456)
2810
2811 tzoffsets = [
2812 timedelta(hours=5), timedelta(hours=2),
2813 timedelta(hours=6, minutes=27),
2814 timedelta(hours=12, minutes=32, seconds=30),
2815 timedelta(hours=2, minutes=4, seconds=9, microseconds=123456)
2816 ]
2817
2818 tzoffsets += [-1 * td for td in tzoffsets]
2819
2820 tzinfos = [None, timezone.utc,
2821 timezone(timedelta(hours=0))]
2822
2823 tzinfos += [timezone(td) for td in tzoffsets]
2824
2825 for tzi in tzinfos:
2826 dt = base_dt.replace(tzinfo=tzi)
2827 dtstr = dt.isoformat()
2828
2829 with self.subTest(tstr=dtstr):
2830 dt_rt = self.theclass.fromisoformat(dtstr)
2831 assert dt == dt_rt, dt_rt
2832
2833 def test_fromisoformat_separators(self):
2834 separators = [
2835 ' ', 'T', '\u007f', # 1-bit widths
2836 '\u0080', 'ʁ', # 2-bit widths
2837 'ᛇ', '時', # 3-bit widths
Paul Ganssle096329f2018-08-23 11:06:20 -04002838 '🐍', # 4-bit widths
2839 '\ud800', # bpo-34454: Surrogate code point
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002840 ]
2841
2842 for sep in separators:
2843 dt = self.theclass(2018, 1, 31, 23, 59, 47, 124789)
2844 dtstr = dt.isoformat(sep=sep)
2845
2846 with self.subTest(dtstr=dtstr):
2847 dt_rt = self.theclass.fromisoformat(dtstr)
2848 self.assertEqual(dt, dt_rt)
2849
2850 def test_fromisoformat_ambiguous(self):
2851 # Test strings like 2018-01-31+12:15 (where +12:15 is not a time zone)
2852 separators = ['+', '-']
2853 for sep in separators:
2854 dt = self.theclass(2018, 1, 31, 12, 15)
2855 dtstr = dt.isoformat(sep=sep)
2856
2857 with self.subTest(dtstr=dtstr):
2858 dt_rt = self.theclass.fromisoformat(dtstr)
2859 self.assertEqual(dt, dt_rt)
2860
2861 def test_fromisoformat_timespecs(self):
2862 datetime_bases = [
2863 (2009, 12, 4, 8, 17, 45, 123456),
2864 (2009, 12, 4, 8, 17, 45, 0)]
2865
2866 tzinfos = [None, timezone.utc,
2867 timezone(timedelta(hours=-5)),
2868 timezone(timedelta(hours=2)),
2869 timezone(timedelta(hours=6, minutes=27))]
2870
2871 timespecs = ['hours', 'minutes', 'seconds',
2872 'milliseconds', 'microseconds']
2873
2874 for ip, ts in enumerate(timespecs):
2875 for tzi in tzinfos:
2876 for dt_tuple in datetime_bases:
2877 if ts == 'milliseconds':
2878 new_microseconds = 1000 * (dt_tuple[6] // 1000)
2879 dt_tuple = dt_tuple[0:6] + (new_microseconds,)
2880
2881 dt = self.theclass(*(dt_tuple[0:(4 + ip)]), tzinfo=tzi)
2882 dtstr = dt.isoformat(timespec=ts)
2883 with self.subTest(dtstr=dtstr):
2884 dt_rt = self.theclass.fromisoformat(dtstr)
2885 self.assertEqual(dt, dt_rt)
2886
2887 def test_fromisoformat_fails_datetime(self):
2888 # Test that fromisoformat() fails on invalid values
2889 bad_strs = [
2890 '', # Empty string
Paul Ganssle096329f2018-08-23 11:06:20 -04002891 '\ud800', # bpo-34454: Surrogate code point
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002892 '2009.04-19T03', # Wrong first separator
2893 '2009-04.19T03', # Wrong second separator
2894 '2009-04-19T0a', # Invalid hours
2895 '2009-04-19T03:1a:45', # Invalid minutes
2896 '2009-04-19T03:15:4a', # Invalid seconds
2897 '2009-04-19T03;15:45', # Bad first time separator
2898 '2009-04-19T03:15;45', # Bad second time separator
2899 '2009-04-19T03:15:4500:00', # Bad time zone separator
2900 '2009-04-19T03:15:45.2345', # Too many digits for milliseconds
2901 '2009-04-19T03:15:45.1234567', # Too many digits for microseconds
2902 '2009-04-19T03:15:45.123456+24:30', # Invalid time zone offset
2903 '2009-04-19T03:15:45.123456-24:30', # Invalid negative offset
2904 '2009-04-10ᛇᛇᛇᛇᛇ12:15', # Too many unicode separators
Paul Ganssle096329f2018-08-23 11:06:20 -04002905 '2009-04\ud80010T12:15', # Surrogate char in date
2906 '2009-04-10T12\ud80015', # Surrogate char in time
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002907 '2009-04-19T1', # Incomplete hours
2908 '2009-04-19T12:3', # Incomplete minutes
2909 '2009-04-19T12:30:4', # Incomplete seconds
2910 '2009-04-19T12:', # Ends with time separator
2911 '2009-04-19T12:30:', # Ends with time separator
2912 '2009-04-19T12:30:45.', # Ends with time separator
2913 '2009-04-19T12:30:45.123456+', # Ends with timzone separator
2914 '2009-04-19T12:30:45.123456-', # Ends with timzone separator
2915 '2009-04-19T12:30:45.123456-05:00a', # Extra text
2916 '2009-04-19T12:30:45.123-05:00a', # Extra text
2917 '2009-04-19T12:30:45-05:00a', # Extra text
2918 ]
2919
2920 for bad_str in bad_strs:
2921 with self.subTest(bad_str=bad_str):
2922 with self.assertRaises(ValueError):
2923 self.theclass.fromisoformat(bad_str)
2924
Paul Ganssle3df85402018-10-22 12:32:52 -04002925 def test_fromisoformat_fails_surrogate(self):
2926 # Test that when fromisoformat() fails with a surrogate character as
2927 # the separator, the error message contains the original string
2928 dtstr = "2018-01-03\ud80001:0113"
2929
2930 with self.assertRaisesRegex(ValueError, re.escape(repr(dtstr))):
2931 self.theclass.fromisoformat(dtstr)
2932
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002933 def test_fromisoformat_utc(self):
2934 dt_str = '2014-04-19T13:21:13+00:00'
2935 dt = self.theclass.fromisoformat(dt_str)
2936
2937 self.assertIs(dt.tzinfo, timezone.utc)
2938
2939 def test_fromisoformat_subclass(self):
2940 class DateTimeSubclass(self.theclass):
2941 pass
2942
2943 dt = DateTimeSubclass(2014, 12, 14, 9, 30, 45, 457390,
2944 tzinfo=timezone(timedelta(hours=10, minutes=45)))
2945
2946 dt_rt = DateTimeSubclass.fromisoformat(dt.isoformat())
2947
2948 self.assertEqual(dt, dt_rt)
2949 self.assertIsInstance(dt_rt, DateTimeSubclass)
2950
2951
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002952class TestSubclassDateTime(TestDateTime):
2953 theclass = SubclassDatetime
2954 # Override tests not designed for subclass
Zachary Ware9fe6d862013-12-08 00:20:35 -06002955 @unittest.skip('not appropriate for subclasses')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002956 def test_roundtrip(self):
2957 pass
2958
2959class SubclassTime(time):
2960 sub_var = 1
2961
2962class TestTime(HarmlessMixedComparison, unittest.TestCase):
2963
2964 theclass = time
2965
2966 def test_basic_attributes(self):
2967 t = self.theclass(12, 0)
2968 self.assertEqual(t.hour, 12)
2969 self.assertEqual(t.minute, 0)
2970 self.assertEqual(t.second, 0)
2971 self.assertEqual(t.microsecond, 0)
2972
2973 def test_basic_attributes_nonzero(self):
2974 # Make sure all attributes are non-zero so bugs in
2975 # bit-shifting access show up.
2976 t = self.theclass(12, 59, 59, 8000)
2977 self.assertEqual(t.hour, 12)
2978 self.assertEqual(t.minute, 59)
2979 self.assertEqual(t.second, 59)
2980 self.assertEqual(t.microsecond, 8000)
2981
2982 def test_roundtrip(self):
2983 t = self.theclass(1, 2, 3, 4)
2984
2985 # Verify t -> string -> time identity.
2986 s = repr(t)
2987 self.assertTrue(s.startswith('datetime.'))
2988 s = s[9:]
2989 t2 = eval(s)
2990 self.assertEqual(t, t2)
2991
2992 # Verify identity via reconstructing from pieces.
2993 t2 = self.theclass(t.hour, t.minute, t.second,
2994 t.microsecond)
2995 self.assertEqual(t, t2)
2996
2997 def test_comparing(self):
2998 args = [1, 2, 3, 4]
2999 t1 = self.theclass(*args)
3000 t2 = self.theclass(*args)
3001 self.assertEqual(t1, t2)
3002 self.assertTrue(t1 <= t2)
3003 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003004 self.assertFalse(t1 != t2)
3005 self.assertFalse(t1 < t2)
3006 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003007
3008 for i in range(len(args)):
3009 newargs = args[:]
3010 newargs[i] = args[i] + 1
3011 t2 = self.theclass(*newargs) # this is larger than t1
3012 self.assertTrue(t1 < t2)
3013 self.assertTrue(t2 > t1)
3014 self.assertTrue(t1 <= t2)
3015 self.assertTrue(t2 >= t1)
3016 self.assertTrue(t1 != t2)
3017 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003018 self.assertFalse(t1 == t2)
3019 self.assertFalse(t2 == t1)
3020 self.assertFalse(t1 > t2)
3021 self.assertFalse(t2 < t1)
3022 self.assertFalse(t1 >= t2)
3023 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003024
3025 for badarg in OTHERSTUFF:
3026 self.assertEqual(t1 == badarg, False)
3027 self.assertEqual(t1 != badarg, True)
3028 self.assertEqual(badarg == t1, False)
3029 self.assertEqual(badarg != t1, True)
3030
3031 self.assertRaises(TypeError, lambda: t1 <= badarg)
3032 self.assertRaises(TypeError, lambda: t1 < badarg)
3033 self.assertRaises(TypeError, lambda: t1 > badarg)
3034 self.assertRaises(TypeError, lambda: t1 >= badarg)
3035 self.assertRaises(TypeError, lambda: badarg <= t1)
3036 self.assertRaises(TypeError, lambda: badarg < t1)
3037 self.assertRaises(TypeError, lambda: badarg > t1)
3038 self.assertRaises(TypeError, lambda: badarg >= t1)
3039
3040 def test_bad_constructor_arguments(self):
3041 # bad hours
3042 self.theclass(0, 0) # no exception
3043 self.theclass(23, 0) # no exception
3044 self.assertRaises(ValueError, self.theclass, -1, 0)
3045 self.assertRaises(ValueError, self.theclass, 24, 0)
3046 # bad minutes
3047 self.theclass(23, 0) # no exception
3048 self.theclass(23, 59) # no exception
3049 self.assertRaises(ValueError, self.theclass, 23, -1)
3050 self.assertRaises(ValueError, self.theclass, 23, 60)
3051 # bad seconds
3052 self.theclass(23, 59, 0) # no exception
3053 self.theclass(23, 59, 59) # no exception
3054 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
3055 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
3056 # bad microseconds
3057 self.theclass(23, 59, 59, 0) # no exception
3058 self.theclass(23, 59, 59, 999999) # no exception
3059 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
3060 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
3061
3062 def test_hash_equality(self):
3063 d = self.theclass(23, 30, 17)
3064 e = self.theclass(23, 30, 17)
3065 self.assertEqual(d, e)
3066 self.assertEqual(hash(d), hash(e))
3067
3068 dic = {d: 1}
3069 dic[e] = 2
3070 self.assertEqual(len(dic), 1)
3071 self.assertEqual(dic[d], 2)
3072 self.assertEqual(dic[e], 2)
3073
3074 d = self.theclass(0, 5, 17)
3075 e = self.theclass(0, 5, 17)
3076 self.assertEqual(d, e)
3077 self.assertEqual(hash(d), hash(e))
3078
3079 dic = {d: 1}
3080 dic[e] = 2
3081 self.assertEqual(len(dic), 1)
3082 self.assertEqual(dic[d], 2)
3083 self.assertEqual(dic[e], 2)
3084
3085 def test_isoformat(self):
3086 t = self.theclass(4, 5, 1, 123)
3087 self.assertEqual(t.isoformat(), "04:05:01.000123")
3088 self.assertEqual(t.isoformat(), str(t))
3089
3090 t = self.theclass()
3091 self.assertEqual(t.isoformat(), "00:00:00")
3092 self.assertEqual(t.isoformat(), str(t))
3093
3094 t = self.theclass(microsecond=1)
3095 self.assertEqual(t.isoformat(), "00:00:00.000001")
3096 self.assertEqual(t.isoformat(), str(t))
3097
3098 t = self.theclass(microsecond=10)
3099 self.assertEqual(t.isoformat(), "00:00:00.000010")
3100 self.assertEqual(t.isoformat(), str(t))
3101
3102 t = self.theclass(microsecond=100)
3103 self.assertEqual(t.isoformat(), "00:00:00.000100")
3104 self.assertEqual(t.isoformat(), str(t))
3105
3106 t = self.theclass(microsecond=1000)
3107 self.assertEqual(t.isoformat(), "00:00:00.001000")
3108 self.assertEqual(t.isoformat(), str(t))
3109
3110 t = self.theclass(microsecond=10000)
3111 self.assertEqual(t.isoformat(), "00:00:00.010000")
3112 self.assertEqual(t.isoformat(), str(t))
3113
3114 t = self.theclass(microsecond=100000)
3115 self.assertEqual(t.isoformat(), "00:00:00.100000")
3116 self.assertEqual(t.isoformat(), str(t))
3117
Alexander Belopolskya2998a62016-03-06 14:58:43 -05003118 t = self.theclass(hour=12, minute=34, second=56, microsecond=123456)
3119 self.assertEqual(t.isoformat(timespec='hours'), "12")
3120 self.assertEqual(t.isoformat(timespec='minutes'), "12:34")
3121 self.assertEqual(t.isoformat(timespec='seconds'), "12:34:56")
3122 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.123")
3123 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.123456")
3124 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56.123456")
3125 self.assertRaises(ValueError, t.isoformat, timespec='monkey')
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03003126 # bpo-34482: Check that surrogates are handled properly.
3127 self.assertRaises(ValueError, t.isoformat, timespec='\ud800')
Alexander Belopolskya2998a62016-03-06 14:58:43 -05003128
3129 t = self.theclass(hour=12, minute=34, second=56, microsecond=999500)
3130 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.999")
3131
3132 t = self.theclass(hour=12, minute=34, second=56, microsecond=0)
3133 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.000")
3134 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.000000")
3135 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56")
3136
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003137 def test_isoformat_timezone(self):
3138 tzoffsets = [
3139 ('05:00', timedelta(hours=5)),
3140 ('02:00', timedelta(hours=2)),
3141 ('06:27', timedelta(hours=6, minutes=27)),
3142 ('12:32:30', timedelta(hours=12, minutes=32, seconds=30)),
3143 ('02:04:09.123456', timedelta(hours=2, minutes=4, seconds=9, microseconds=123456))
3144 ]
3145
3146 tzinfos = [
3147 ('', None),
3148 ('+00:00', timezone.utc),
3149 ('+00:00', timezone(timedelta(0))),
3150 ]
3151
3152 tzinfos += [
3153 (prefix + expected, timezone(sign * td))
3154 for expected, td in tzoffsets
3155 for prefix, sign in [('-', -1), ('+', 1)]
3156 ]
3157
3158 t_base = self.theclass(12, 37, 9)
3159 exp_base = '12:37:09'
3160
3161 for exp_tz, tzi in tzinfos:
3162 t = t_base.replace(tzinfo=tzi)
3163 exp = exp_base + exp_tz
3164 with self.subTest(tzi=tzi):
3165 assert t.isoformat() == exp
3166
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003167 def test_1653736(self):
3168 # verify it doesn't accept extra keyword arguments
3169 t = self.theclass(second=1)
3170 self.assertRaises(TypeError, t.isoformat, foo=3)
3171
3172 def test_strftime(self):
3173 t = self.theclass(1, 2, 3, 4)
3174 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
3175 # A naive object replaces %z and %Z with empty strings.
3176 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
3177
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03003178 # bpo-34482: Check that surrogates don't cause a crash.
3179 try:
3180 t.strftime('%H\ud800%M')
3181 except UnicodeEncodeError:
3182 pass
3183
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003184 def test_format(self):
3185 t = self.theclass(1, 2, 3, 4)
3186 self.assertEqual(t.__format__(''), str(t))
3187
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02003188 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04003189 t.__format__(123)
3190
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003191 # check that a derived class's __str__() gets called
3192 class A(self.theclass):
3193 def __str__(self):
3194 return 'A'
3195 a = A(1, 2, 3, 4)
3196 self.assertEqual(a.__format__(''), 'A')
3197
3198 # check that a derived class's strftime gets called
3199 class B(self.theclass):
3200 def strftime(self, format_spec):
3201 return 'B'
3202 b = B(1, 2, 3, 4)
3203 self.assertEqual(b.__format__(''), str(t))
3204
3205 for fmt in ['%H %M %S',
3206 ]:
3207 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
3208 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
3209 self.assertEqual(b.__format__(fmt), 'B')
3210
3211 def test_str(self):
3212 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
3213 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
3214 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
3215 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
3216 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
3217
3218 def test_repr(self):
3219 name = 'datetime.' + self.theclass.__name__
3220 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
3221 "%s(1, 2, 3, 4)" % name)
3222 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
3223 "%s(10, 2, 3, 4000)" % name)
3224 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
3225 "%s(0, 2, 3, 400000)" % name)
3226 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
3227 "%s(12, 2, 3)" % name)
3228 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
3229 "%s(23, 15)" % name)
3230
3231 def test_resolution_info(self):
3232 self.assertIsInstance(self.theclass.min, self.theclass)
3233 self.assertIsInstance(self.theclass.max, self.theclass)
3234 self.assertIsInstance(self.theclass.resolution, timedelta)
3235 self.assertTrue(self.theclass.max > self.theclass.min)
3236
3237 def test_pickling(self):
3238 args = 20, 59, 16, 64**2
3239 orig = self.theclass(*args)
3240 for pickler, unpickler, proto in pickle_choices:
3241 green = pickler.dumps(orig, proto)
3242 derived = unpickler.loads(green)
3243 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003244 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003245
3246 def test_pickling_subclass_time(self):
3247 args = 20, 59, 16, 64**2
3248 orig = SubclassTime(*args)
3249 for pickler, unpickler, proto in pickle_choices:
3250 green = pickler.dumps(orig, proto)
3251 derived = unpickler.loads(green)
3252 self.assertEqual(orig, derived)
3253
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02003254 def test_compat_unpickle(self):
3255 tests = [
3256 b"cdatetime\ntime\n(S'\\x14;\\x10\\x00\\x10\\x00'\ntR.",
3257 b'cdatetime\ntime\n(U\x06\x14;\x10\x00\x10\x00tR.',
3258 b'\x80\x02cdatetime\ntime\nU\x06\x14;\x10\x00\x10\x00\x85R.',
3259 ]
3260 args = 20, 59, 16, 64**2
3261 expected = self.theclass(*args)
3262 for data in tests:
3263 for loads in pickle_loads:
3264 derived = loads(data, encoding='latin1')
3265 self.assertEqual(derived, expected)
3266
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003267 def test_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003268 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003269 cls = self.theclass
3270 self.assertTrue(cls(1))
3271 self.assertTrue(cls(0, 1))
3272 self.assertTrue(cls(0, 0, 1))
3273 self.assertTrue(cls(0, 0, 0, 1))
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003274 self.assertTrue(cls(0))
3275 self.assertTrue(cls())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003276
3277 def test_replace(self):
3278 cls = self.theclass
3279 args = [1, 2, 3, 4]
3280 base = cls(*args)
3281 self.assertEqual(base, base.replace())
3282
3283 i = 0
3284 for name, newval in (("hour", 5),
3285 ("minute", 6),
3286 ("second", 7),
3287 ("microsecond", 8)):
3288 newargs = args[:]
3289 newargs[i] = newval
3290 expected = cls(*newargs)
3291 got = base.replace(**{name: newval})
3292 self.assertEqual(expected, got)
3293 i += 1
3294
3295 # Out of bounds.
3296 base = cls(1)
3297 self.assertRaises(ValueError, base.replace, hour=24)
3298 self.assertRaises(ValueError, base.replace, minute=-1)
3299 self.assertRaises(ValueError, base.replace, second=100)
3300 self.assertRaises(ValueError, base.replace, microsecond=1000000)
3301
Paul Ganssle191e9932017-11-09 16:34:29 -05003302 def test_subclass_replace(self):
3303 class TimeSubclass(self.theclass):
3304 pass
3305
3306 ctime = TimeSubclass(12, 30)
3307 self.assertIs(type(ctime.replace(hour=10)), TimeSubclass)
3308
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003309 def test_subclass_time(self):
3310
3311 class C(self.theclass):
3312 theAnswer = 42
3313
3314 def __new__(cls, *args, **kws):
3315 temp = kws.copy()
3316 extra = temp.pop('extra')
3317 result = self.theclass.__new__(cls, *args, **temp)
3318 result.extra = extra
3319 return result
3320
3321 def newmeth(self, start):
3322 return start + self.hour + self.second
3323
3324 args = 4, 5, 6
3325
3326 dt1 = self.theclass(*args)
3327 dt2 = C(*args, **{'extra': 7})
3328
3329 self.assertEqual(dt2.__class__, C)
3330 self.assertEqual(dt2.theAnswer, 42)
3331 self.assertEqual(dt2.extra, 7)
3332 self.assertEqual(dt1.isoformat(), dt2.isoformat())
3333 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
3334
3335 def test_backdoor_resistance(self):
3336 # see TestDate.test_backdoor_resistance().
3337 base = '2:59.0'
3338 for hour_byte in ' ', '9', chr(24), '\xff':
3339 self.assertRaises(TypeError, self.theclass,
3340 hour_byte + base[1:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04003341 # Good bytes, but bad tzinfo:
3342 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
3343 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003344
3345# A mixin for classes with a tzinfo= argument. Subclasses must define
Martin Panter46f50722016-05-26 05:35:26 +00003346# theclass as a class attribute, and theclass(1, 1, 1, tzinfo=whatever)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003347# must be legit (which is true for time and datetime).
3348class TZInfoBase:
3349
3350 def test_argument_passing(self):
3351 cls = self.theclass
3352 # A datetime passes itself on, a time passes None.
3353 class introspective(tzinfo):
3354 def tzname(self, dt): return dt and "real" or "none"
3355 def utcoffset(self, dt):
3356 return timedelta(minutes = dt and 42 or -42)
3357 dst = utcoffset
3358
3359 obj = cls(1, 2, 3, tzinfo=introspective())
3360
3361 expected = cls is time and "none" or "real"
3362 self.assertEqual(obj.tzname(), expected)
3363
3364 expected = timedelta(minutes=(cls is time and -42 or 42))
3365 self.assertEqual(obj.utcoffset(), expected)
3366 self.assertEqual(obj.dst(), expected)
3367
3368 def test_bad_tzinfo_classes(self):
3369 cls = self.theclass
3370 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
3371
3372 class NiceTry(object):
3373 def __init__(self): pass
3374 def utcoffset(self, dt): pass
3375 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
3376
3377 class BetterTry(tzinfo):
3378 def __init__(self): pass
3379 def utcoffset(self, dt): pass
3380 b = BetterTry()
3381 t = cls(1, 1, 1, tzinfo=b)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003382 self.assertIs(t.tzinfo, b)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003383
3384 def test_utc_offset_out_of_bounds(self):
3385 class Edgy(tzinfo):
3386 def __init__(self, offset):
3387 self.offset = timedelta(minutes=offset)
3388 def utcoffset(self, dt):
3389 return self.offset
3390
3391 cls = self.theclass
3392 for offset, legit in ((-1440, False),
3393 (-1439, True),
3394 (1439, True),
3395 (1440, False)):
3396 if cls is time:
3397 t = cls(1, 2, 3, tzinfo=Edgy(offset))
3398 elif cls is datetime:
3399 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
3400 else:
3401 assert 0, "impossible"
3402 if legit:
3403 aofs = abs(offset)
3404 h, m = divmod(aofs, 60)
3405 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
3406 if isinstance(t, datetime):
3407 t = t.timetz()
3408 self.assertEqual(str(t), "01:02:03" + tag)
3409 else:
3410 self.assertRaises(ValueError, str, t)
3411
3412 def test_tzinfo_classes(self):
3413 cls = self.theclass
3414 class C1(tzinfo):
3415 def utcoffset(self, dt): return None
3416 def dst(self, dt): return None
3417 def tzname(self, dt): return None
3418 for t in (cls(1, 1, 1),
3419 cls(1, 1, 1, tzinfo=None),
3420 cls(1, 1, 1, tzinfo=C1())):
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003421 self.assertIsNone(t.utcoffset())
3422 self.assertIsNone(t.dst())
3423 self.assertIsNone(t.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003424
3425 class C3(tzinfo):
3426 def utcoffset(self, dt): return timedelta(minutes=-1439)
3427 def dst(self, dt): return timedelta(minutes=1439)
3428 def tzname(self, dt): return "aname"
3429 t = cls(1, 1, 1, tzinfo=C3())
3430 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
3431 self.assertEqual(t.dst(), timedelta(minutes=1439))
3432 self.assertEqual(t.tzname(), "aname")
3433
3434 # Wrong types.
3435 class C4(tzinfo):
3436 def utcoffset(self, dt): return "aname"
3437 def dst(self, dt): return 7
3438 def tzname(self, dt): return 0
3439 t = cls(1, 1, 1, tzinfo=C4())
3440 self.assertRaises(TypeError, t.utcoffset)
3441 self.assertRaises(TypeError, t.dst)
3442 self.assertRaises(TypeError, t.tzname)
3443
3444 # Offset out of range.
3445 class C6(tzinfo):
3446 def utcoffset(self, dt): return timedelta(hours=-24)
3447 def dst(self, dt): return timedelta(hours=24)
3448 t = cls(1, 1, 1, tzinfo=C6())
3449 self.assertRaises(ValueError, t.utcoffset)
3450 self.assertRaises(ValueError, t.dst)
3451
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04003452 # Not a whole number of seconds.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003453 class C7(tzinfo):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04003454 def utcoffset(self, dt): return timedelta(microseconds=61)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003455 def dst(self, dt): return timedelta(microseconds=-81)
3456 t = cls(1, 1, 1, tzinfo=C7())
Alexander Belopolsky018d3532017-07-31 10:26:50 -04003457 self.assertEqual(t.utcoffset(), timedelta(microseconds=61))
3458 self.assertEqual(t.dst(), timedelta(microseconds=-81))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003459
3460 def test_aware_compare(self):
3461 cls = self.theclass
3462
3463 # Ensure that utcoffset() gets ignored if the comparands have
3464 # the same tzinfo member.
3465 class OperandDependentOffset(tzinfo):
3466 def utcoffset(self, t):
3467 if t.minute < 10:
3468 # d0 and d1 equal after adjustment
3469 return timedelta(minutes=t.minute)
3470 else:
3471 # d2 off in the weeds
3472 return timedelta(minutes=59)
3473
3474 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
3475 d0 = base.replace(minute=3)
3476 d1 = base.replace(minute=9)
3477 d2 = base.replace(minute=11)
3478 for x in d0, d1, d2:
3479 for y in d0, d1, d2:
3480 for op in lt, le, gt, ge, eq, ne:
3481 got = op(x, y)
3482 expected = op(x.minute, y.minute)
3483 self.assertEqual(got, expected)
3484
3485 # However, if they're different members, uctoffset is not ignored.
penguindustin96466302019-05-06 14:57:17 -04003486 # Note that a time can't actually have an operand-dependent offset,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003487 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
3488 # so skip this test for time.
3489 if cls is not time:
3490 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3491 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3492 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3493 for x in d0, d1, d2:
3494 for y in d0, d1, d2:
3495 got = (x > y) - (x < y)
3496 if (x is d0 or x is d1) and (y is d0 or y is d1):
3497 expected = 0
3498 elif x is y is d2:
3499 expected = 0
3500 elif x is d2:
3501 expected = -1
3502 else:
3503 assert y is d2
3504 expected = 1
3505 self.assertEqual(got, expected)
3506
3507
3508# Testing time objects with a non-None tzinfo.
3509class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
3510 theclass = time
3511
3512 def test_empty(self):
3513 t = self.theclass()
3514 self.assertEqual(t.hour, 0)
3515 self.assertEqual(t.minute, 0)
3516 self.assertEqual(t.second, 0)
3517 self.assertEqual(t.microsecond, 0)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003518 self.assertIsNone(t.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003519
3520 def test_zones(self):
3521 est = FixedOffset(-300, "EST", 1)
3522 utc = FixedOffset(0, "UTC", -2)
3523 met = FixedOffset(60, "MET", 3)
3524 t1 = time( 7, 47, tzinfo=est)
3525 t2 = time(12, 47, tzinfo=utc)
3526 t3 = time(13, 47, tzinfo=met)
3527 t4 = time(microsecond=40)
3528 t5 = time(microsecond=40, tzinfo=utc)
3529
3530 self.assertEqual(t1.tzinfo, est)
3531 self.assertEqual(t2.tzinfo, utc)
3532 self.assertEqual(t3.tzinfo, met)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003533 self.assertIsNone(t4.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003534 self.assertEqual(t5.tzinfo, utc)
3535
3536 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
3537 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
3538 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003539 self.assertIsNone(t4.utcoffset())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003540 self.assertRaises(TypeError, t1.utcoffset, "no args")
3541
3542 self.assertEqual(t1.tzname(), "EST")
3543 self.assertEqual(t2.tzname(), "UTC")
3544 self.assertEqual(t3.tzname(), "MET")
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003545 self.assertIsNone(t4.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003546 self.assertRaises(TypeError, t1.tzname, "no args")
3547
3548 self.assertEqual(t1.dst(), timedelta(minutes=1))
3549 self.assertEqual(t2.dst(), timedelta(minutes=-2))
3550 self.assertEqual(t3.dst(), timedelta(minutes=3))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003551 self.assertIsNone(t4.dst())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003552 self.assertRaises(TypeError, t1.dst, "no args")
3553
3554 self.assertEqual(hash(t1), hash(t2))
3555 self.assertEqual(hash(t1), hash(t3))
3556 self.assertEqual(hash(t2), hash(t3))
3557
3558 self.assertEqual(t1, t2)
3559 self.assertEqual(t1, t3)
3560 self.assertEqual(t2, t3)
Alexander Belopolsky08313822012-06-15 20:19:47 -04003561 self.assertNotEqual(t4, t5) # mixed tz-aware & naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003562 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
3563 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
3564
3565 self.assertEqual(str(t1), "07:47:00-05:00")
3566 self.assertEqual(str(t2), "12:47:00+00:00")
3567 self.assertEqual(str(t3), "13:47:00+01:00")
3568 self.assertEqual(str(t4), "00:00:00.000040")
3569 self.assertEqual(str(t5), "00:00:00.000040+00:00")
3570
3571 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
3572 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
3573 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
3574 self.assertEqual(t4.isoformat(), "00:00:00.000040")
3575 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
3576
3577 d = 'datetime.time'
3578 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
3579 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
3580 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
3581 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
3582 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
3583
3584 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
3585 "07:47:00 %Z=EST %z=-0500")
3586 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
3587 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
3588
3589 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
3590 t1 = time(23, 59, tzinfo=yuck)
3591 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
3592 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
3593
3594 # Check that an invalid tzname result raises an exception.
3595 class Badtzname(tzinfo):
Alexander Belopolskye239d232010-12-08 23:31:48 +00003596 tz = 42
3597 def tzname(self, dt): return self.tz
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003598 t = time(2, 3, 4, tzinfo=Badtzname())
3599 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
3600 self.assertRaises(TypeError, t.strftime, "%Z")
3601
Alexander Belopolskye239d232010-12-08 23:31:48 +00003602 # Issue #6697:
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +02003603 if '_Fast' in self.__class__.__name__:
Alexander Belopolskye239d232010-12-08 23:31:48 +00003604 Badtzname.tz = '\ud800'
3605 self.assertRaises(ValueError, t.strftime, "%Z")
3606
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003607 def test_hash_edge_cases(self):
3608 # Offsets that overflow a basic time.
3609 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
3610 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
3611 self.assertEqual(hash(t1), hash(t2))
3612
3613 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
3614 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
3615 self.assertEqual(hash(t1), hash(t2))
3616
3617 def test_pickling(self):
3618 # Try one without a tzinfo.
3619 args = 20, 59, 16, 64**2
3620 orig = self.theclass(*args)
3621 for pickler, unpickler, proto in pickle_choices:
3622 green = pickler.dumps(orig, proto)
3623 derived = unpickler.loads(green)
3624 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003625 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003626
3627 # Try one with a tzinfo.
3628 tinfo = PicklableFixedOffset(-300, 'cookie')
3629 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
3630 for pickler, unpickler, proto in pickle_choices:
3631 green = pickler.dumps(orig, proto)
3632 derived = unpickler.loads(green)
3633 self.assertEqual(orig, derived)
3634 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3635 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3636 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003637 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003638
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02003639 def test_compat_unpickle(self):
3640 tests = [
3641 b"cdatetime\ntime\n(S'\\x05\\x06\\x07\\x01\\xe2@'\n"
3642 b"ctest.datetimetester\nPicklableFixedOffset\n(tR"
3643 b"(dS'_FixedOffset__offset'\ncdatetime\ntimedelta\n"
3644 b"(I-1\nI68400\nI0\ntRs"
3645 b"S'_FixedOffset__dstoffset'\nNs"
3646 b"S'_FixedOffset__name'\nS'cookie'\nsbtR.",
3647
3648 b'cdatetime\ntime\n(U\x06\x05\x06\x07\x01\xe2@'
3649 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
3650 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
3651 b'(J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00tR'
3652 b'U\x17_FixedOffset__dstoffsetN'
3653 b'U\x12_FixedOffset__nameU\x06cookieubtR.',
3654
3655 b'\x80\x02cdatetime\ntime\nU\x06\x05\x06\x07\x01\xe2@'
3656 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
3657 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
3658 b'J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00\x87R'
3659 b'U\x17_FixedOffset__dstoffsetN'
3660 b'U\x12_FixedOffset__nameU\x06cookieub\x86R.',
3661 ]
3662
3663 tinfo = PicklableFixedOffset(-300, 'cookie')
3664 expected = self.theclass(5, 6, 7, 123456, tzinfo=tinfo)
3665 for data in tests:
3666 for loads in pickle_loads:
3667 derived = loads(data, encoding='latin1')
3668 self.assertEqual(derived, expected, repr(data))
3669 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3670 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3671 self.assertEqual(derived.tzname(), 'cookie')
3672
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003673 def test_more_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003674 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003675 cls = self.theclass
3676
3677 t = cls(0, tzinfo=FixedOffset(-300, ""))
3678 self.assertTrue(t)
3679
3680 t = cls(5, tzinfo=FixedOffset(-300, ""))
3681 self.assertTrue(t)
3682
3683 t = cls(5, tzinfo=FixedOffset(300, ""))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003684 self.assertTrue(t)
3685
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003686 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
3687 self.assertTrue(t)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003688
3689 def test_replace(self):
3690 cls = self.theclass
3691 z100 = FixedOffset(100, "+100")
3692 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3693 args = [1, 2, 3, 4, z100]
3694 base = cls(*args)
3695 self.assertEqual(base, base.replace())
3696
3697 i = 0
3698 for name, newval in (("hour", 5),
3699 ("minute", 6),
3700 ("second", 7),
3701 ("microsecond", 8),
3702 ("tzinfo", zm200)):
3703 newargs = args[:]
3704 newargs[i] = newval
3705 expected = cls(*newargs)
3706 got = base.replace(**{name: newval})
3707 self.assertEqual(expected, got)
3708 i += 1
3709
3710 # Ensure we can get rid of a tzinfo.
3711 self.assertEqual(base.tzname(), "+100")
3712 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003713 self.assertIsNone(base2.tzinfo)
3714 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003715
3716 # Ensure we can add one.
3717 base3 = base2.replace(tzinfo=z100)
3718 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003719 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003720
3721 # Out of bounds.
3722 base = cls(1)
3723 self.assertRaises(ValueError, base.replace, hour=24)
3724 self.assertRaises(ValueError, base.replace, minute=-1)
3725 self.assertRaises(ValueError, base.replace, second=100)
3726 self.assertRaises(ValueError, base.replace, microsecond=1000000)
3727
3728 def test_mixed_compare(self):
3729 t1 = time(1, 2, 3)
3730 t2 = time(1, 2, 3)
3731 self.assertEqual(t1, t2)
3732 t2 = t2.replace(tzinfo=None)
3733 self.assertEqual(t1, t2)
3734 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3735 self.assertEqual(t1, t2)
3736 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04003737 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003738
3739 # In time w/ identical tzinfo objects, utcoffset is ignored.
3740 class Varies(tzinfo):
3741 def __init__(self):
3742 self.offset = timedelta(minutes=22)
3743 def utcoffset(self, t):
3744 self.offset += timedelta(minutes=1)
3745 return self.offset
3746
3747 v = Varies()
3748 t1 = t2.replace(tzinfo=v)
3749 t2 = t2.replace(tzinfo=v)
3750 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3751 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3752 self.assertEqual(t1, t2)
3753
3754 # But if they're not identical, it isn't ignored.
3755 t2 = t2.replace(tzinfo=Varies())
3756 self.assertTrue(t1 < t2) # t1's offset counter still going up
3757
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003758 def test_fromisoformat(self):
3759 time_examples = [
3760 (0, 0, 0, 0),
3761 (23, 59, 59, 999999),
3762 ]
3763
3764 hh = (9, 12, 20)
3765 mm = (5, 30)
3766 ss = (4, 45)
3767 usec = (0, 245000, 678901)
3768
3769 time_examples += list(itertools.product(hh, mm, ss, usec))
3770
3771 tzinfos = [None, timezone.utc,
3772 timezone(timedelta(hours=2)),
3773 timezone(timedelta(hours=6, minutes=27))]
3774
3775 for ttup in time_examples:
3776 for tzi in tzinfos:
3777 t = self.theclass(*ttup, tzinfo=tzi)
3778 tstr = t.isoformat()
3779
3780 with self.subTest(tstr=tstr):
3781 t_rt = self.theclass.fromisoformat(tstr)
3782 self.assertEqual(t, t_rt)
3783
3784 def test_fromisoformat_timezone(self):
3785 base_time = self.theclass(12, 30, 45, 217456)
3786
3787 tzoffsets = [
3788 timedelta(hours=5), timedelta(hours=2),
3789 timedelta(hours=6, minutes=27),
3790 timedelta(hours=12, minutes=32, seconds=30),
3791 timedelta(hours=2, minutes=4, seconds=9, microseconds=123456)
3792 ]
3793
3794 tzoffsets += [-1 * td for td in tzoffsets]
3795
3796 tzinfos = [None, timezone.utc,
3797 timezone(timedelta(hours=0))]
3798
3799 tzinfos += [timezone(td) for td in tzoffsets]
3800
3801 for tzi in tzinfos:
3802 t = base_time.replace(tzinfo=tzi)
3803 tstr = t.isoformat()
3804
3805 with self.subTest(tstr=tstr):
3806 t_rt = self.theclass.fromisoformat(tstr)
3807 assert t == t_rt, t_rt
3808
3809 def test_fromisoformat_timespecs(self):
3810 time_bases = [
3811 (8, 17, 45, 123456),
3812 (8, 17, 45, 0)
3813 ]
3814
3815 tzinfos = [None, timezone.utc,
3816 timezone(timedelta(hours=-5)),
3817 timezone(timedelta(hours=2)),
3818 timezone(timedelta(hours=6, minutes=27))]
3819
3820 timespecs = ['hours', 'minutes', 'seconds',
3821 'milliseconds', 'microseconds']
3822
3823 for ip, ts in enumerate(timespecs):
3824 for tzi in tzinfos:
3825 for t_tuple in time_bases:
3826 if ts == 'milliseconds':
3827 new_microseconds = 1000 * (t_tuple[-1] // 1000)
3828 t_tuple = t_tuple[0:-1] + (new_microseconds,)
3829
3830 t = self.theclass(*(t_tuple[0:(1 + ip)]), tzinfo=tzi)
3831 tstr = t.isoformat(timespec=ts)
3832 with self.subTest(tstr=tstr):
3833 t_rt = self.theclass.fromisoformat(tstr)
3834 self.assertEqual(t, t_rt)
3835
3836 def test_fromisoformat_fails(self):
3837 bad_strs = [
3838 '', # Empty string
Paul Ganssle096329f2018-08-23 11:06:20 -04003839 '12\ud80000', # Invalid separator - surrogate char
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003840 '12:', # Ends on a separator
3841 '12:30:', # Ends on a separator
3842 '12:30:15.', # Ends on a separator
3843 '1', # Incomplete hours
3844 '12:3', # Incomplete minutes
3845 '12:30:1', # Incomplete seconds
3846 '1a:30:45.334034', # Invalid character in hours
3847 '12:a0:45.334034', # Invalid character in minutes
3848 '12:30:a5.334034', # Invalid character in seconds
3849 '12:30:45.1234', # Too many digits for milliseconds
3850 '12:30:45.1234567', # Too many digits for microseconds
3851 '12:30:45.123456+24:30', # Invalid time zone offset
3852 '12:30:45.123456-24:30', # Invalid negative offset
3853 '12:30:45', # Uses full-width unicode colons
3854 '12:30:45․123456', # Uses \u2024 in place of decimal point
3855 '12:30:45a', # Extra at tend of basic time
3856 '12:30:45.123a', # Extra at end of millisecond time
3857 '12:30:45.123456a', # Extra at end of microsecond time
3858 '12:30:45.123456+12:00:30a', # Extra at end of full time
3859 ]
3860
3861 for bad_str in bad_strs:
3862 with self.subTest(bad_str=bad_str):
3863 with self.assertRaises(ValueError):
3864 self.theclass.fromisoformat(bad_str)
3865
3866 def test_fromisoformat_fails_typeerror(self):
3867 # Test the fromisoformat fails when passed the wrong type
3868 import io
3869
3870 bad_types = [b'12:30:45', None, io.StringIO('12:30:45')]
3871
3872 for bad_type in bad_types:
3873 with self.assertRaises(TypeError):
3874 self.theclass.fromisoformat(bad_type)
3875
3876 def test_fromisoformat_subclass(self):
3877 class TimeSubclass(self.theclass):
3878 pass
3879
3880 tsc = TimeSubclass(12, 14, 45, 203745, tzinfo=timezone.utc)
3881 tsc_rt = TimeSubclass.fromisoformat(tsc.isoformat())
3882
3883 self.assertEqual(tsc, tsc_rt)
3884 self.assertIsInstance(tsc_rt, TimeSubclass)
3885
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003886 def test_subclass_timetz(self):
3887
3888 class C(self.theclass):
3889 theAnswer = 42
3890
3891 def __new__(cls, *args, **kws):
3892 temp = kws.copy()
3893 extra = temp.pop('extra')
3894 result = self.theclass.__new__(cls, *args, **temp)
3895 result.extra = extra
3896 return result
3897
3898 def newmeth(self, start):
3899 return start + self.hour + self.second
3900
3901 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3902
3903 dt1 = self.theclass(*args)
3904 dt2 = C(*args, **{'extra': 7})
3905
3906 self.assertEqual(dt2.__class__, C)
3907 self.assertEqual(dt2.theAnswer, 42)
3908 self.assertEqual(dt2.extra, 7)
3909 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3910 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
3911
3912
3913# Testing datetime objects with a non-None tzinfo.
3914
3915class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
3916 theclass = datetime
3917
3918 def test_trivial(self):
3919 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
3920 self.assertEqual(dt.year, 1)
3921 self.assertEqual(dt.month, 2)
3922 self.assertEqual(dt.day, 3)
3923 self.assertEqual(dt.hour, 4)
3924 self.assertEqual(dt.minute, 5)
3925 self.assertEqual(dt.second, 6)
3926 self.assertEqual(dt.microsecond, 7)
3927 self.assertEqual(dt.tzinfo, None)
3928
3929 def test_even_more_compare(self):
3930 # The test_compare() and test_more_compare() inherited from TestDate
3931 # and TestDateTime covered non-tzinfo cases.
3932
3933 # Smallest possible after UTC adjustment.
3934 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3935 # Largest possible after UTC adjustment.
3936 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3937 tzinfo=FixedOffset(-1439, ""))
3938
3939 # Make sure those compare correctly, and w/o overflow.
3940 self.assertTrue(t1 < t2)
3941 self.assertTrue(t1 != t2)
3942 self.assertTrue(t2 > t1)
3943
3944 self.assertEqual(t1, t1)
3945 self.assertEqual(t2, t2)
3946
3947 # Equal afer adjustment.
3948 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
3949 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
3950 self.assertEqual(t1, t2)
3951
3952 # Change t1 not to subtract a minute, and t1 should be larger.
3953 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
3954 self.assertTrue(t1 > t2)
3955
3956 # Change t1 to subtract 2 minutes, and t1 should be smaller.
3957 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
3958 self.assertTrue(t1 < t2)
3959
3960 # Back to the original t1, but make seconds resolve it.
3961 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3962 second=1)
3963 self.assertTrue(t1 > t2)
3964
3965 # Likewise, but make microseconds resolve it.
3966 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3967 microsecond=1)
3968 self.assertTrue(t1 > t2)
3969
Alexander Belopolsky08313822012-06-15 20:19:47 -04003970 # Make t2 naive and it should differ.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003971 t2 = self.theclass.min
Alexander Belopolsky08313822012-06-15 20:19:47 -04003972 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003973 self.assertEqual(t2, t2)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04003974 # and > comparison should fail
3975 with self.assertRaises(TypeError):
3976 t1 > t2
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003977
3978 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
3979 class Naive(tzinfo):
3980 def utcoffset(self, dt): return None
3981 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
Alexander Belopolsky08313822012-06-15 20:19:47 -04003982 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003983 self.assertEqual(t2, t2)
3984
3985 # OTOH, it's OK to compare two of these mixing the two ways of being
3986 # naive.
3987 t1 = self.theclass(5, 6, 7)
3988 self.assertEqual(t1, t2)
3989
3990 # Try a bogus uctoffset.
3991 class Bogus(tzinfo):
3992 def utcoffset(self, dt):
3993 return timedelta(minutes=1440) # out of bounds
3994 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
3995 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
3996 self.assertRaises(ValueError, lambda: t1 == t2)
3997
3998 def test_pickling(self):
3999 # Try one without a tzinfo.
4000 args = 6, 7, 23, 20, 59, 1, 64**2
4001 orig = self.theclass(*args)
4002 for pickler, unpickler, proto in pickle_choices:
4003 green = pickler.dumps(orig, proto)
4004 derived = unpickler.loads(green)
4005 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02004006 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004007
4008 # Try one with a tzinfo.
4009 tinfo = PicklableFixedOffset(-300, 'cookie')
4010 orig = self.theclass(*args, **{'tzinfo': tinfo})
4011 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
4012 for pickler, unpickler, proto in pickle_choices:
4013 green = pickler.dumps(orig, proto)
4014 derived = unpickler.loads(green)
4015 self.assertEqual(orig, derived)
4016 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
4017 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
4018 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02004019 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004020
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02004021 def test_compat_unpickle(self):
4022 tests = [
4023 b'cdatetime\ndatetime\n'
4024 b"(S'\\x07\\xdf\\x0b\\x1b\\x14;\\x01\\x01\\xe2@'\n"
4025 b'ctest.datetimetester\nPicklableFixedOffset\n(tR'
4026 b"(dS'_FixedOffset__offset'\ncdatetime\ntimedelta\n"
4027 b'(I-1\nI68400\nI0\ntRs'
4028 b"S'_FixedOffset__dstoffset'\nNs"
4029 b"S'_FixedOffset__name'\nS'cookie'\nsbtR.",
4030
4031 b'cdatetime\ndatetime\n'
4032 b'(U\n\x07\xdf\x0b\x1b\x14;\x01\x01\xe2@'
4033 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
4034 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
4035 b'(J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00tR'
4036 b'U\x17_FixedOffset__dstoffsetN'
4037 b'U\x12_FixedOffset__nameU\x06cookieubtR.',
4038
4039 b'\x80\x02cdatetime\ndatetime\n'
4040 b'U\n\x07\xdf\x0b\x1b\x14;\x01\x01\xe2@'
4041 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
4042 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
4043 b'J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00\x87R'
4044 b'U\x17_FixedOffset__dstoffsetN'
4045 b'U\x12_FixedOffset__nameU\x06cookieub\x86R.',
4046 ]
4047 args = 2015, 11, 27, 20, 59, 1, 123456
4048 tinfo = PicklableFixedOffset(-300, 'cookie')
4049 expected = self.theclass(*args, **{'tzinfo': tinfo})
4050 for data in tests:
4051 for loads in pickle_loads:
4052 derived = loads(data, encoding='latin1')
4053 self.assertEqual(derived, expected)
4054 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
4055 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
4056 self.assertEqual(derived.tzname(), 'cookie')
4057
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004058 def test_extreme_hashes(self):
4059 # If an attempt is made to hash these via subtracting the offset
4060 # then hashing a datetime object, OverflowError results. The
4061 # Python implementation used to blow up here.
4062 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
4063 hash(t)
4064 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
4065 tzinfo=FixedOffset(-1439, ""))
4066 hash(t)
4067
4068 # OTOH, an OOB offset should blow up.
4069 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
4070 self.assertRaises(ValueError, hash, t)
4071
4072 def test_zones(self):
4073 est = FixedOffset(-300, "EST")
4074 utc = FixedOffset(0, "UTC")
4075 met = FixedOffset(60, "MET")
4076 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
4077 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
4078 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
4079 self.assertEqual(t1.tzinfo, est)
4080 self.assertEqual(t2.tzinfo, utc)
4081 self.assertEqual(t3.tzinfo, met)
4082 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
4083 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
4084 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
4085 self.assertEqual(t1.tzname(), "EST")
4086 self.assertEqual(t2.tzname(), "UTC")
4087 self.assertEqual(t3.tzname(), "MET")
4088 self.assertEqual(hash(t1), hash(t2))
4089 self.assertEqual(hash(t1), hash(t3))
4090 self.assertEqual(hash(t2), hash(t3))
4091 self.assertEqual(t1, t2)
4092 self.assertEqual(t1, t3)
4093 self.assertEqual(t2, t3)
4094 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
4095 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
4096 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
4097 d = 'datetime.datetime(2002, 3, 19, '
4098 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
4099 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
4100 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
4101
4102 def test_combine(self):
4103 met = FixedOffset(60, "MET")
4104 d = date(2002, 3, 4)
4105 tz = time(18, 45, 3, 1234, tzinfo=met)
4106 dt = datetime.combine(d, tz)
4107 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
4108 tzinfo=met))
4109
4110 def test_extract(self):
4111 met = FixedOffset(60, "MET")
4112 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
4113 self.assertEqual(dt.date(), date(2002, 3, 4))
4114 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
4115 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
4116
4117 def test_tz_aware_arithmetic(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004118 now = self.theclass.now()
4119 tz55 = FixedOffset(-330, "west 5:30")
4120 timeaware = now.time().replace(tzinfo=tz55)
4121 nowaware = self.theclass.combine(now.date(), timeaware)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004122 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004123 self.assertEqual(nowaware.timetz(), timeaware)
4124
4125 # Can't mix aware and non-aware.
4126 self.assertRaises(TypeError, lambda: now - nowaware)
4127 self.assertRaises(TypeError, lambda: nowaware - now)
4128
4129 # And adding datetime's doesn't make sense, aware or not.
4130 self.assertRaises(TypeError, lambda: now + nowaware)
4131 self.assertRaises(TypeError, lambda: nowaware + now)
4132 self.assertRaises(TypeError, lambda: nowaware + nowaware)
4133
4134 # Subtracting should yield 0.
4135 self.assertEqual(now - now, timedelta(0))
4136 self.assertEqual(nowaware - nowaware, timedelta(0))
4137
4138 # Adding a delta should preserve tzinfo.
4139 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
4140 nowawareplus = nowaware + delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004141 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004142 nowawareplus2 = delta + nowaware
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004143 self.assertIs(nowawareplus2.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004144 self.assertEqual(nowawareplus, nowawareplus2)
4145
4146 # that - delta should be what we started with, and that - what we
4147 # started with should be delta.
4148 diff = nowawareplus - delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004149 self.assertIs(diff.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004150 self.assertEqual(nowaware, diff)
4151 self.assertRaises(TypeError, lambda: delta - nowawareplus)
4152 self.assertEqual(nowawareplus - nowaware, delta)
4153
4154 # Make up a random timezone.
4155 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
4156 # Attach it to nowawareplus.
4157 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004158 self.assertIs(nowawareplus.tzinfo, tzr)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004159 # Make sure the difference takes the timezone adjustments into account.
4160 got = nowaware - nowawareplus
4161 # Expected: (nowaware base - nowaware offset) -
4162 # (nowawareplus base - nowawareplus offset) =
4163 # (nowaware base - nowawareplus base) +
4164 # (nowawareplus offset - nowaware offset) =
4165 # -delta + nowawareplus offset - nowaware offset
4166 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
4167 self.assertEqual(got, expected)
4168
4169 # Try max possible difference.
4170 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
4171 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
4172 tzinfo=FixedOffset(-1439, "max"))
4173 maxdiff = max - min
4174 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
4175 timedelta(minutes=2*1439))
4176 # Different tzinfo, but the same offset
4177 tza = timezone(HOUR, 'A')
4178 tzb = timezone(HOUR, 'B')
4179 delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
4180 self.assertEqual(delta, self.theclass.min - self.theclass.max)
4181
4182 def test_tzinfo_now(self):
4183 meth = self.theclass.now
4184 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4185 base = meth()
4186 # Try with and without naming the keyword.
4187 off42 = FixedOffset(42, "42")
4188 another = meth(off42)
4189 again = meth(tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004190 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004191 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
4192 # Bad argument with and w/o naming the keyword.
4193 self.assertRaises(TypeError, meth, 16)
4194 self.assertRaises(TypeError, meth, tzinfo=16)
4195 # Bad keyword name.
4196 self.assertRaises(TypeError, meth, tinfo=off42)
4197 # Too many args.
4198 self.assertRaises(TypeError, meth, off42, off42)
4199
4200 # We don't know which time zone we're in, and don't have a tzinfo
4201 # class to represent it, so seeing whether a tz argument actually
4202 # does a conversion is tricky.
4203 utc = FixedOffset(0, "utc", 0)
4204 for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
4205 timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
4206 for dummy in range(3):
4207 now = datetime.now(weirdtz)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004208 self.assertIs(now.tzinfo, weirdtz)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004209 utcnow = datetime.utcnow().replace(tzinfo=utc)
4210 now2 = utcnow.astimezone(weirdtz)
4211 if abs(now - now2) < timedelta(seconds=30):
4212 break
4213 # Else the code is broken, or more than 30 seconds passed between
4214 # calls; assuming the latter, just try again.
4215 else:
4216 # Three strikes and we're out.
4217 self.fail("utcnow(), now(tz), or astimezone() may be broken")
4218
4219 def test_tzinfo_fromtimestamp(self):
4220 import time
4221 meth = self.theclass.fromtimestamp
4222 ts = time.time()
4223 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4224 base = meth(ts)
4225 # Try with and without naming the keyword.
4226 off42 = FixedOffset(42, "42")
4227 another = meth(ts, off42)
4228 again = meth(ts, tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004229 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004230 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
4231 # Bad argument with and w/o naming the keyword.
4232 self.assertRaises(TypeError, meth, ts, 16)
4233 self.assertRaises(TypeError, meth, ts, tzinfo=16)
4234 # Bad keyword name.
4235 self.assertRaises(TypeError, meth, ts, tinfo=off42)
4236 # Too many args.
4237 self.assertRaises(TypeError, meth, ts, off42, off42)
4238 # Too few args.
4239 self.assertRaises(TypeError, meth)
4240
4241 # Try to make sure tz= actually does some conversion.
4242 timestamp = 1000000000
4243 utcdatetime = datetime.utcfromtimestamp(timestamp)
4244 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
4245 # But on some flavor of Mac, it's nowhere near that. So we can't have
4246 # any idea here what time that actually is, we can only test that
4247 # relative changes match.
4248 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
4249 tz = FixedOffset(utcoffset, "tz", 0)
4250 expected = utcdatetime + utcoffset
4251 got = datetime.fromtimestamp(timestamp, tz)
4252 self.assertEqual(expected, got.replace(tzinfo=None))
4253
4254 def test_tzinfo_utcnow(self):
4255 meth = self.theclass.utcnow
4256 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4257 base = meth()
4258 # Try with and without naming the keyword; for whatever reason,
4259 # utcnow() doesn't accept a tzinfo argument.
4260 off42 = FixedOffset(42, "42")
4261 self.assertRaises(TypeError, meth, off42)
4262 self.assertRaises(TypeError, meth, tzinfo=off42)
4263
4264 def test_tzinfo_utcfromtimestamp(self):
4265 import time
4266 meth = self.theclass.utcfromtimestamp
4267 ts = time.time()
4268 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4269 base = meth(ts)
4270 # Try with and without naming the keyword; for whatever reason,
4271 # utcfromtimestamp() doesn't accept a tzinfo argument.
4272 off42 = FixedOffset(42, "42")
4273 self.assertRaises(TypeError, meth, ts, off42)
4274 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
4275
4276 def test_tzinfo_timetuple(self):
4277 # TestDateTime tested most of this. datetime adds a twist to the
4278 # DST flag.
4279 class DST(tzinfo):
4280 def __init__(self, dstvalue):
4281 if isinstance(dstvalue, int):
4282 dstvalue = timedelta(minutes=dstvalue)
4283 self.dstvalue = dstvalue
4284 def dst(self, dt):
4285 return self.dstvalue
4286
4287 cls = self.theclass
4288 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
4289 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
4290 t = d.timetuple()
4291 self.assertEqual(1, t.tm_year)
4292 self.assertEqual(1, t.tm_mon)
4293 self.assertEqual(1, t.tm_mday)
4294 self.assertEqual(10, t.tm_hour)
4295 self.assertEqual(20, t.tm_min)
4296 self.assertEqual(30, t.tm_sec)
4297 self.assertEqual(0, t.tm_wday)
4298 self.assertEqual(1, t.tm_yday)
4299 self.assertEqual(flag, t.tm_isdst)
4300
4301 # dst() returns wrong type.
4302 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
4303
4304 # dst() at the edge.
4305 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
4306 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
4307
4308 # dst() out of range.
4309 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
4310 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
4311
4312 def test_utctimetuple(self):
4313 class DST(tzinfo):
4314 def __init__(self, dstvalue=0):
4315 if isinstance(dstvalue, int):
4316 dstvalue = timedelta(minutes=dstvalue)
4317 self.dstvalue = dstvalue
4318 def dst(self, dt):
4319 return self.dstvalue
4320
4321 cls = self.theclass
4322 # This can't work: DST didn't implement utcoffset.
4323 self.assertRaises(NotImplementedError,
4324 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
4325
4326 class UOFS(DST):
4327 def __init__(self, uofs, dofs=None):
4328 DST.__init__(self, dofs)
4329 self.uofs = timedelta(minutes=uofs)
4330 def utcoffset(self, dt):
4331 return self.uofs
4332
4333 for dstvalue in -33, 33, 0, None:
4334 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
4335 t = d.utctimetuple()
4336 self.assertEqual(d.year, t.tm_year)
4337 self.assertEqual(d.month, t.tm_mon)
4338 self.assertEqual(d.day, t.tm_mday)
4339 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
4340 self.assertEqual(13, t.tm_min)
4341 self.assertEqual(d.second, t.tm_sec)
4342 self.assertEqual(d.weekday(), t.tm_wday)
4343 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
4344 t.tm_yday)
4345 # Ensure tm_isdst is 0 regardless of what dst() says: DST
4346 # is never in effect for a UTC time.
4347 self.assertEqual(0, t.tm_isdst)
4348
4349 # For naive datetime, utctimetuple == timetuple except for isdst
4350 d = cls(1, 2, 3, 10, 20, 30, 40)
4351 t = d.utctimetuple()
4352 self.assertEqual(t[:-1], d.timetuple()[:-1])
4353 self.assertEqual(0, t.tm_isdst)
4354 # Same if utcoffset is None
4355 class NOFS(DST):
4356 def utcoffset(self, dt):
4357 return None
4358 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
4359 t = d.utctimetuple()
4360 self.assertEqual(t[:-1], d.timetuple()[:-1])
4361 self.assertEqual(0, t.tm_isdst)
4362 # Check that bad tzinfo is detected
4363 class BOFS(DST):
4364 def utcoffset(self, dt):
4365 return "EST"
4366 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
4367 self.assertRaises(TypeError, d.utctimetuple)
4368
4369 # Check that utctimetuple() is the same as
4370 # astimezone(utc).timetuple()
4371 d = cls(2010, 11, 13, 14, 15, 16, 171819)
4372 for tz in [timezone.min, timezone.utc, timezone.max]:
4373 dtz = d.replace(tzinfo=tz)
4374 self.assertEqual(dtz.utctimetuple()[:-1],
4375 dtz.astimezone(timezone.utc).timetuple()[:-1])
4376 # At the edges, UTC adjustment can produce years out-of-range
4377 # for a datetime object. Ensure that an OverflowError is
4378 # raised.
4379 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
4380 # That goes back 1 minute less than a full day.
4381 self.assertRaises(OverflowError, tiny.utctimetuple)
4382
4383 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
4384 # That goes forward 1 minute less than a full day.
4385 self.assertRaises(OverflowError, huge.utctimetuple)
4386 # More overflow cases
4387 tiny = cls.min.replace(tzinfo=timezone(MINUTE))
4388 self.assertRaises(OverflowError, tiny.utctimetuple)
4389 huge = cls.max.replace(tzinfo=timezone(-MINUTE))
4390 self.assertRaises(OverflowError, huge.utctimetuple)
4391
4392 def test_tzinfo_isoformat(self):
4393 zero = FixedOffset(0, "+00:00")
4394 plus = FixedOffset(220, "+03:40")
4395 minus = FixedOffset(-231, "-03:51")
4396 unknown = FixedOffset(None, "")
4397
4398 cls = self.theclass
4399 datestr = '0001-02-03'
4400 for ofs in None, zero, plus, minus, unknown:
4401 for us in 0, 987001:
4402 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
4403 timestr = '04:05:59' + (us and '.987001' or '')
4404 ofsstr = ofs is not None and d.tzname() or ''
4405 tailstr = timestr + ofsstr
4406 iso = d.isoformat()
4407 self.assertEqual(iso, datestr + 'T' + tailstr)
4408 self.assertEqual(iso, d.isoformat('T'))
4409 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
4410 self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
4411 self.assertEqual(str(d), datestr + ' ' + tailstr)
4412
4413 def test_replace(self):
4414 cls = self.theclass
4415 z100 = FixedOffset(100, "+100")
4416 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
4417 args = [1, 2, 3, 4, 5, 6, 7, z100]
4418 base = cls(*args)
4419 self.assertEqual(base, base.replace())
4420
4421 i = 0
4422 for name, newval in (("year", 2),
4423 ("month", 3),
4424 ("day", 4),
4425 ("hour", 5),
4426 ("minute", 6),
4427 ("second", 7),
4428 ("microsecond", 8),
4429 ("tzinfo", zm200)):
4430 newargs = args[:]
4431 newargs[i] = newval
4432 expected = cls(*newargs)
4433 got = base.replace(**{name: newval})
4434 self.assertEqual(expected, got)
4435 i += 1
4436
4437 # Ensure we can get rid of a tzinfo.
4438 self.assertEqual(base.tzname(), "+100")
4439 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004440 self.assertIsNone(base2.tzinfo)
4441 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004442
4443 # Ensure we can add one.
4444 base3 = base2.replace(tzinfo=z100)
4445 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004446 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004447
4448 # Out of bounds.
4449 base = cls(2000, 2, 29)
4450 self.assertRaises(ValueError, base.replace, year=2001)
4451
4452 def test_more_astimezone(self):
4453 # The inherited test_astimezone covered some trivial and error cases.
4454 fnone = FixedOffset(None, "None")
4455 f44m = FixedOffset(44, "44")
4456 fm5h = FixedOffset(-timedelta(hours=5), "m300")
4457
4458 dt = self.theclass.now(tz=f44m)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004459 self.assertIs(dt.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004460 # Replacing with degenerate tzinfo raises an exception.
4461 self.assertRaises(ValueError, dt.astimezone, fnone)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004462 # Replacing with same tzinfo makes no change.
4463 x = dt.astimezone(dt.tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004464 self.assertIs(x.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004465 self.assertEqual(x.date(), dt.date())
4466 self.assertEqual(x.time(), dt.time())
4467
4468 # Replacing with different tzinfo does adjust.
4469 got = dt.astimezone(fm5h)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004470 self.assertIs(got.tzinfo, fm5h)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004471 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
4472 expected = dt - dt.utcoffset() # in effect, convert to UTC
4473 expected += fm5h.utcoffset(dt) # and from there to local time
4474 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
4475 self.assertEqual(got.date(), expected.date())
4476 self.assertEqual(got.time(), expected.time())
4477 self.assertEqual(got.timetz(), expected.timetz())
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004478 self.assertIs(got.tzinfo, expected.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004479 self.assertEqual(got, expected)
4480
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004481 @support.run_with_tz('UTC')
4482 def test_astimezone_default_utc(self):
4483 dt = self.theclass.now(timezone.utc)
4484 self.assertEqual(dt.astimezone(None), dt)
4485 self.assertEqual(dt.astimezone(), dt)
4486
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004487 # Note that offset in TZ variable has the opposite sign to that
4488 # produced by %z directive.
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004489 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4490 def test_astimezone_default_eastern(self):
4491 dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
4492 local = dt.astimezone()
4493 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004494 self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004495 dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
4496 local = dt.astimezone()
4497 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004498 self.assertEqual(local.strftime("%z %Z"), "-0400 EDT")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004499
Alexander Belopolsky1dcf4f92016-03-25 15:42:59 -04004500 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4501 def test_astimezone_default_near_fold(self):
4502 # Issue #26616.
4503 u = datetime(2015, 11, 1, 5, tzinfo=timezone.utc)
4504 t = u.astimezone()
4505 s = t.astimezone()
4506 self.assertEqual(t.tzinfo, s.tzinfo)
4507
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004508 def test_aware_subtract(self):
4509 cls = self.theclass
4510
4511 # Ensure that utcoffset() is ignored when the operands have the
4512 # same tzinfo member.
4513 class OperandDependentOffset(tzinfo):
4514 def utcoffset(self, t):
4515 if t.minute < 10:
4516 # d0 and d1 equal after adjustment
4517 return timedelta(minutes=t.minute)
4518 else:
4519 # d2 off in the weeds
4520 return timedelta(minutes=59)
4521
4522 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
4523 d0 = base.replace(minute=3)
4524 d1 = base.replace(minute=9)
4525 d2 = base.replace(minute=11)
4526 for x in d0, d1, d2:
4527 for y in d0, d1, d2:
4528 got = x - y
4529 expected = timedelta(minutes=x.minute - y.minute)
4530 self.assertEqual(got, expected)
4531
4532 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
4533 # ignored.
4534 base = cls(8, 9, 10, 11, 12, 13, 14)
4535 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
4536 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
4537 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
4538 for x in d0, d1, d2:
4539 for y in d0, d1, d2:
4540 got = x - y
4541 if (x is d0 or x is d1) and (y is d0 or y is d1):
4542 expected = timedelta(0)
4543 elif x is y is d2:
4544 expected = timedelta(0)
4545 elif x is d2:
4546 expected = timedelta(minutes=(11-59)-0)
4547 else:
4548 assert y is d2
4549 expected = timedelta(minutes=0-(11-59))
4550 self.assertEqual(got, expected)
4551
4552 def test_mixed_compare(self):
4553 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
4554 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
4555 self.assertEqual(t1, t2)
4556 t2 = t2.replace(tzinfo=None)
4557 self.assertEqual(t1, t2)
4558 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
4559 self.assertEqual(t1, t2)
4560 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04004561 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004562
4563 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
4564 class Varies(tzinfo):
4565 def __init__(self):
4566 self.offset = timedelta(minutes=22)
4567 def utcoffset(self, t):
4568 self.offset += timedelta(minutes=1)
4569 return self.offset
4570
4571 v = Varies()
4572 t1 = t2.replace(tzinfo=v)
4573 t2 = t2.replace(tzinfo=v)
4574 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
4575 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
4576 self.assertEqual(t1, t2)
4577
4578 # But if they're not identical, it isn't ignored.
4579 t2 = t2.replace(tzinfo=Varies())
4580 self.assertTrue(t1 < t2) # t1's offset counter still going up
4581
4582 def test_subclass_datetimetz(self):
4583
4584 class C(self.theclass):
4585 theAnswer = 42
4586
4587 def __new__(cls, *args, **kws):
4588 temp = kws.copy()
4589 extra = temp.pop('extra')
4590 result = self.theclass.__new__(cls, *args, **temp)
4591 result.extra = extra
4592 return result
4593
4594 def newmeth(self, start):
4595 return start + self.hour + self.year
4596
4597 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
4598
4599 dt1 = self.theclass(*args)
4600 dt2 = C(*args, **{'extra': 7})
4601
4602 self.assertEqual(dt2.__class__, C)
4603 self.assertEqual(dt2.theAnswer, 42)
4604 self.assertEqual(dt2.extra, 7)
4605 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
4606 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
4607
4608# Pain to set up DST-aware tzinfo classes.
4609
4610def first_sunday_on_or_after(dt):
4611 days_to_go = 6 - dt.weekday()
4612 if days_to_go:
4613 dt += timedelta(days_to_go)
4614 return dt
4615
4616ZERO = timedelta(0)
4617MINUTE = timedelta(minutes=1)
4618HOUR = timedelta(hours=1)
4619DAY = timedelta(days=1)
4620# In the US, DST starts at 2am (standard time) on the first Sunday in April.
4621DSTSTART = datetime(1, 4, 1, 2)
4622# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
4623# which is the first Sunday on or after Oct 25. Because we view 1:MM as
4624# being standard time on that day, there is no spelling in local time of
4625# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
4626DSTEND = datetime(1, 10, 25, 1)
4627
4628class USTimeZone(tzinfo):
4629
4630 def __init__(self, hours, reprname, stdname, dstname):
4631 self.stdoffset = timedelta(hours=hours)
4632 self.reprname = reprname
4633 self.stdname = stdname
4634 self.dstname = dstname
4635
4636 def __repr__(self):
4637 return self.reprname
4638
4639 def tzname(self, dt):
4640 if self.dst(dt):
4641 return self.dstname
4642 else:
4643 return self.stdname
4644
4645 def utcoffset(self, dt):
4646 return self.stdoffset + self.dst(dt)
4647
4648 def dst(self, dt):
4649 if dt is None or dt.tzinfo is None:
4650 # An exception instead may be sensible here, in one or more of
4651 # the cases.
4652 return ZERO
4653 assert dt.tzinfo is self
4654
4655 # Find first Sunday in April.
4656 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
4657 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
4658
4659 # Find last Sunday in October.
4660 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
4661 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
4662
4663 # Can't compare naive to aware objects, so strip the timezone from
4664 # dt first.
4665 if start <= dt.replace(tzinfo=None) < end:
4666 return HOUR
4667 else:
4668 return ZERO
4669
4670Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
4671Central = USTimeZone(-6, "Central", "CST", "CDT")
4672Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
4673Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
4674utc_real = FixedOffset(0, "UTC", 0)
4675# For better test coverage, we want another flavor of UTC that's west of
4676# the Eastern and Pacific timezones.
4677utc_fake = FixedOffset(-12*60, "UTCfake", 0)
4678
4679class TestTimezoneConversions(unittest.TestCase):
4680 # The DST switch times for 2002, in std time.
4681 dston = datetime(2002, 4, 7, 2)
4682 dstoff = datetime(2002, 10, 27, 1)
4683
4684 theclass = datetime
4685
4686 # Check a time that's inside DST.
4687 def checkinside(self, dt, tz, utc, dston, dstoff):
4688 self.assertEqual(dt.dst(), HOUR)
4689
4690 # Conversion to our own timezone is always an identity.
4691 self.assertEqual(dt.astimezone(tz), dt)
4692
4693 asutc = dt.astimezone(utc)
4694 there_and_back = asutc.astimezone(tz)
4695
4696 # Conversion to UTC and back isn't always an identity here,
4697 # because there are redundant spellings (in local time) of
4698 # UTC time when DST begins: the clock jumps from 1:59:59
4699 # to 3:00:00, and a local time of 2:MM:SS doesn't really
4700 # make sense then. The classes above treat 2:MM:SS as
4701 # daylight time then (it's "after 2am"), really an alias
4702 # for 1:MM:SS standard time. The latter form is what
4703 # conversion back from UTC produces.
4704 if dt.date() == dston.date() and dt.hour == 2:
4705 # We're in the redundant hour, and coming back from
4706 # UTC gives the 1:MM:SS standard-time spelling.
4707 self.assertEqual(there_and_back + HOUR, dt)
4708 # Although during was considered to be in daylight
4709 # time, there_and_back is not.
4710 self.assertEqual(there_and_back.dst(), ZERO)
4711 # They're the same times in UTC.
4712 self.assertEqual(there_and_back.astimezone(utc),
4713 dt.astimezone(utc))
4714 else:
4715 # We're not in the redundant hour.
4716 self.assertEqual(dt, there_and_back)
4717
4718 # Because we have a redundant spelling when DST begins, there is
Ezio Melotti3b3499b2011-03-16 11:35:38 +02004719 # (unfortunately) an hour when DST ends that can't be spelled at all in
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004720 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
4721 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
4722 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
4723 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
4724 # expressed in local time. Nevertheless, we want conversion back
4725 # from UTC to mimic the local clock's "repeat an hour" behavior.
4726 nexthour_utc = asutc + HOUR
4727 nexthour_tz = nexthour_utc.astimezone(tz)
4728 if dt.date() == dstoff.date() and dt.hour == 0:
4729 # We're in the hour before the last DST hour. The last DST hour
4730 # is ineffable. We want the conversion back to repeat 1:MM.
4731 self.assertEqual(nexthour_tz, dt.replace(hour=1))
4732 nexthour_utc += HOUR
4733 nexthour_tz = nexthour_utc.astimezone(tz)
4734 self.assertEqual(nexthour_tz, dt.replace(hour=1))
4735 else:
4736 self.assertEqual(nexthour_tz - dt, HOUR)
4737
4738 # Check a time that's outside DST.
4739 def checkoutside(self, dt, tz, utc):
4740 self.assertEqual(dt.dst(), ZERO)
4741
4742 # Conversion to our own timezone is always an identity.
4743 self.assertEqual(dt.astimezone(tz), dt)
4744
4745 # Converting to UTC and back is an identity too.
4746 asutc = dt.astimezone(utc)
4747 there_and_back = asutc.astimezone(tz)
4748 self.assertEqual(dt, there_and_back)
4749
4750 def convert_between_tz_and_utc(self, tz, utc):
4751 dston = self.dston.replace(tzinfo=tz)
4752 # Because 1:MM on the day DST ends is taken as being standard time,
4753 # there is no spelling in tz for the last hour of daylight time.
4754 # For purposes of the test, the last hour of DST is 0:MM, which is
4755 # taken as being daylight time (and 1:MM is taken as being standard
4756 # time).
4757 dstoff = self.dstoff.replace(tzinfo=tz)
4758 for delta in (timedelta(weeks=13),
4759 DAY,
4760 HOUR,
4761 timedelta(minutes=1),
4762 timedelta(microseconds=1)):
4763
4764 self.checkinside(dston, tz, utc, dston, dstoff)
4765 for during in dston + delta, dstoff - delta:
4766 self.checkinside(during, tz, utc, dston, dstoff)
4767
4768 self.checkoutside(dstoff, tz, utc)
4769 for outside in dston - delta, dstoff + delta:
4770 self.checkoutside(outside, tz, utc)
4771
4772 def test_easy(self):
4773 # Despite the name of this test, the endcases are excruciating.
4774 self.convert_between_tz_and_utc(Eastern, utc_real)
4775 self.convert_between_tz_and_utc(Pacific, utc_real)
4776 self.convert_between_tz_and_utc(Eastern, utc_fake)
4777 self.convert_between_tz_and_utc(Pacific, utc_fake)
4778 # The next is really dancing near the edge. It works because
4779 # Pacific and Eastern are far enough apart that their "problem
4780 # hours" don't overlap.
4781 self.convert_between_tz_and_utc(Eastern, Pacific)
4782 self.convert_between_tz_and_utc(Pacific, Eastern)
4783 # OTOH, these fail! Don't enable them. The difficulty is that
4784 # the edge case tests assume that every hour is representable in
4785 # the "utc" class. This is always true for a fixed-offset tzinfo
4786 # class (lke utc_real and utc_fake), but not for Eastern or Central.
4787 # For these adjacent DST-aware time zones, the range of time offsets
4788 # tested ends up creating hours in the one that aren't representable
4789 # in the other. For the same reason, we would see failures in the
4790 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
4791 # offset deltas in convert_between_tz_and_utc().
4792 #
4793 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
4794 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
4795
4796 def test_tricky(self):
4797 # 22:00 on day before daylight starts.
4798 fourback = self.dston - timedelta(hours=4)
4799 ninewest = FixedOffset(-9*60, "-0900", 0)
4800 fourback = fourback.replace(tzinfo=ninewest)
4801 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
4802 # 2", we should get the 3 spelling.
4803 # If we plug 22:00 the day before into Eastern, it "looks like std
4804 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
4805 # to 22:00 lands on 2:00, which makes no sense in local time (the
4806 # local clock jumps from 1 to 3). The point here is to make sure we
4807 # get the 3 spelling.
4808 expected = self.dston.replace(hour=3)
4809 got = fourback.astimezone(Eastern).replace(tzinfo=None)
4810 self.assertEqual(expected, got)
4811
4812 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
4813 # case we want the 1:00 spelling.
4814 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
4815 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
4816 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
4817 # spelling.
4818 expected = self.dston.replace(hour=1)
4819 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
4820 self.assertEqual(expected, got)
4821
4822 # Now on the day DST ends, we want "repeat an hour" behavior.
4823 # UTC 4:MM 5:MM 6:MM 7:MM checking these
4824 # EST 23:MM 0:MM 1:MM 2:MM
4825 # EDT 0:MM 1:MM 2:MM 3:MM
4826 # wall 0:MM 1:MM 1:MM 2:MM against these
4827 for utc in utc_real, utc_fake:
4828 for tz in Eastern, Pacific:
4829 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
4830 # Convert that to UTC.
4831 first_std_hour -= tz.utcoffset(None)
4832 # Adjust for possibly fake UTC.
4833 asutc = first_std_hour + utc.utcoffset(None)
4834 # First UTC hour to convert; this is 4:00 when utc=utc_real &
4835 # tz=Eastern.
4836 asutcbase = asutc.replace(tzinfo=utc)
4837 for tzhour in (0, 1, 1, 2):
4838 expectedbase = self.dstoff.replace(hour=tzhour)
4839 for minute in 0, 30, 59:
4840 expected = expectedbase.replace(minute=minute)
4841 asutc = asutcbase.replace(minute=minute)
4842 astz = asutc.astimezone(tz)
4843 self.assertEqual(astz.replace(tzinfo=None), expected)
4844 asutcbase += HOUR
4845
4846
4847 def test_bogus_dst(self):
4848 class ok(tzinfo):
4849 def utcoffset(self, dt): return HOUR
4850 def dst(self, dt): return HOUR
4851
4852 now = self.theclass.now().replace(tzinfo=utc_real)
4853 # Doesn't blow up.
4854 now.astimezone(ok())
4855
4856 # Does blow up.
4857 class notok(ok):
4858 def dst(self, dt): return None
4859 self.assertRaises(ValueError, now.astimezone, notok())
4860
4861 # Sometimes blow up. In the following, tzinfo.dst()
4862 # implementation may return None or not None depending on
4863 # whether DST is assumed to be in effect. In this situation,
4864 # a ValueError should be raised by astimezone().
4865 class tricky_notok(ok):
4866 def dst(self, dt):
4867 if dt.year == 2000:
4868 return None
4869 else:
4870 return 10*HOUR
4871 dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
4872 self.assertRaises(ValueError, dt.astimezone, tricky_notok())
4873
4874 def test_fromutc(self):
4875 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
4876 now = datetime.utcnow().replace(tzinfo=utc_real)
4877 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
4878 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
4879 enow = Eastern.fromutc(now) # doesn't blow up
4880 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
4881 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
4882 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
4883
4884 # Always converts UTC to standard time.
4885 class FauxUSTimeZone(USTimeZone):
4886 def fromutc(self, dt):
4887 return dt + self.stdoffset
4888 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
4889
4890 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
4891 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
4892 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
4893
4894 # Check around DST start.
4895 start = self.dston.replace(hour=4, tzinfo=Eastern)
4896 fstart = start.replace(tzinfo=FEastern)
4897 for wall in 23, 0, 1, 3, 4, 5:
4898 expected = start.replace(hour=wall)
4899 if wall == 23:
4900 expected -= timedelta(days=1)
4901 got = Eastern.fromutc(start)
4902 self.assertEqual(expected, got)
4903
4904 expected = fstart + FEastern.stdoffset
4905 got = FEastern.fromutc(fstart)
4906 self.assertEqual(expected, got)
4907
4908 # Ensure astimezone() calls fromutc() too.
4909 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
4910 self.assertEqual(expected, got)
4911
4912 start += HOUR
4913 fstart += HOUR
4914
4915 # Check around DST end.
4916 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
4917 fstart = start.replace(tzinfo=FEastern)
4918 for wall in 0, 1, 1, 2, 3, 4:
4919 expected = start.replace(hour=wall)
4920 got = Eastern.fromutc(start)
4921 self.assertEqual(expected, got)
4922
4923 expected = fstart + FEastern.stdoffset
4924 got = FEastern.fromutc(fstart)
4925 self.assertEqual(expected, got)
4926
4927 # Ensure astimezone() calls fromutc() too.
4928 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
4929 self.assertEqual(expected, got)
4930
4931 start += HOUR
4932 fstart += HOUR
4933
4934
4935#############################################################################
4936# oddballs
4937
4938class Oddballs(unittest.TestCase):
4939
4940 def test_bug_1028306(self):
4941 # Trying to compare a date to a datetime should act like a mixed-
4942 # type comparison, despite that datetime is a subclass of date.
4943 as_date = date.today()
4944 as_datetime = datetime.combine(as_date, time())
4945 self.assertTrue(as_date != as_datetime)
4946 self.assertTrue(as_datetime != as_date)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004947 self.assertFalse(as_date == as_datetime)
4948 self.assertFalse(as_datetime == as_date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004949 self.assertRaises(TypeError, lambda: as_date < as_datetime)
4950 self.assertRaises(TypeError, lambda: as_datetime < as_date)
4951 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
4952 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
4953 self.assertRaises(TypeError, lambda: as_date > as_datetime)
4954 self.assertRaises(TypeError, lambda: as_datetime > as_date)
4955 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
4956 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
4957
Raymond Hettinger15f44ab2016-08-30 10:47:49 -07004958 # Nevertheless, comparison should work with the base-class (date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004959 # projection if use of a date method is forced.
4960 self.assertEqual(as_date.__eq__(as_datetime), True)
4961 different_day = (as_date.day + 1) % 20 + 1
4962 as_different = as_datetime.replace(day= different_day)
4963 self.assertEqual(as_date.__eq__(as_different), False)
4964
4965 # And date should compare with other subclasses of date. If a
4966 # subclass wants to stop this, it's up to the subclass to do so.
4967 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
4968 self.assertEqual(as_date, date_sc)
4969 self.assertEqual(date_sc, as_date)
4970
4971 # Ditto for datetimes.
4972 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
4973 as_date.day, 0, 0, 0)
4974 self.assertEqual(as_datetime, datetime_sc)
4975 self.assertEqual(datetime_sc, as_datetime)
4976
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004977 def test_extra_attributes(self):
4978 for x in [date.today(),
4979 time(),
4980 datetime.utcnow(),
4981 timedelta(),
4982 tzinfo(),
4983 timezone(timedelta())]:
4984 with self.assertRaises(AttributeError):
4985 x.abc = 1
4986
4987 def test_check_arg_types(self):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004988 class Number:
4989 def __init__(self, value):
4990 self.value = value
4991 def __int__(self):
4992 return self.value
4993
4994 for xx in [decimal.Decimal(10),
4995 decimal.Decimal('10.9'),
4996 Number(10)]:
Serhiy Storchaka6a44f6e2019-02-25 17:57:58 +02004997 with self.assertWarns(DeprecationWarning):
4998 self.assertEqual(datetime(10, 10, 10, 10, 10, 10, 10),
4999 datetime(xx, xx, xx, xx, xx, xx, xx))
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04005000
5001 with self.assertRaisesRegex(TypeError, '^an integer is required '
R David Murray44b548d2016-09-08 13:59:53 -04005002 r'\(got type str\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04005003 datetime(10, 10, '10')
5004
5005 f10 = Number(10.9)
5006 with self.assertRaisesRegex(TypeError, '^__int__ returned non-int '
Serhiy Storchaka6a44f6e2019-02-25 17:57:58 +02005007 r'\(type float\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04005008 datetime(10, 10, f10)
5009
5010 class Float(float):
5011 pass
5012 s10 = Float(10.9)
5013 with self.assertRaisesRegex(TypeError, '^integer argument expected, '
5014 'got float$'):
5015 datetime(10, 10, s10)
5016
5017 with self.assertRaises(TypeError):
5018 datetime(10., 10, 10)
5019 with self.assertRaises(TypeError):
5020 datetime(10, 10., 10)
5021 with self.assertRaises(TypeError):
5022 datetime(10, 10, 10.)
5023 with self.assertRaises(TypeError):
5024 datetime(10, 10, 10, 10.)
5025 with self.assertRaises(TypeError):
5026 datetime(10, 10, 10, 10, 10.)
5027 with self.assertRaises(TypeError):
5028 datetime(10, 10, 10, 10, 10, 10.)
5029 with self.assertRaises(TypeError):
5030 datetime(10, 10, 10, 10, 10, 10, 10.)
5031
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005032#############################################################################
5033# Local Time Disambiguation
5034
5035# An experimental reimplementation of fromutc that respects the "fold" flag.
5036
5037class tzinfo2(tzinfo):
5038
5039 def fromutc(self, dt):
5040 "datetime in UTC -> datetime in local time."
5041
5042 if not isinstance(dt, datetime):
5043 raise TypeError("fromutc() requires a datetime argument")
5044 if dt.tzinfo is not self:
5045 raise ValueError("dt.tzinfo is not self")
5046 # Returned value satisfies
5047 # dt + ldt.utcoffset() = ldt
5048 off0 = dt.replace(fold=0).utcoffset()
5049 off1 = dt.replace(fold=1).utcoffset()
5050 if off0 is None or off1 is None or dt.dst() is None:
5051 raise ValueError
5052 if off0 == off1:
5053 ldt = dt + off0
5054 off1 = ldt.utcoffset()
5055 if off0 == off1:
5056 return ldt
5057 # Now, we discovered both possible offsets, so
5058 # we can just try four possible solutions:
5059 for off in [off0, off1]:
5060 ldt = dt + off
5061 if ldt.utcoffset() == off:
5062 return ldt
5063 ldt = ldt.replace(fold=1)
5064 if ldt.utcoffset() == off:
5065 return ldt
5066
5067 raise ValueError("No suitable local time found")
5068
5069# Reimplementing simplified US timezones to respect the "fold" flag:
5070
5071class USTimeZone2(tzinfo2):
5072
5073 def __init__(self, hours, reprname, stdname, dstname):
5074 self.stdoffset = timedelta(hours=hours)
5075 self.reprname = reprname
5076 self.stdname = stdname
5077 self.dstname = dstname
5078
5079 def __repr__(self):
5080 return self.reprname
5081
5082 def tzname(self, dt):
5083 if self.dst(dt):
5084 return self.dstname
5085 else:
5086 return self.stdname
5087
5088 def utcoffset(self, dt):
5089 return self.stdoffset + self.dst(dt)
5090
5091 def dst(self, dt):
5092 if dt is None or dt.tzinfo is None:
5093 # An exception instead may be sensible here, in one or more of
5094 # the cases.
5095 return ZERO
5096 assert dt.tzinfo is self
5097
5098 # Find first Sunday in April.
5099 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
5100 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
5101
5102 # Find last Sunday in October.
5103 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
5104 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
5105
5106 # Can't compare naive to aware objects, so strip the timezone from
5107 # dt first.
5108 dt = dt.replace(tzinfo=None)
5109 if start + HOUR <= dt < end:
5110 # DST is in effect.
5111 return HOUR
5112 elif end <= dt < end + HOUR:
5113 # Fold (an ambiguous hour): use dt.fold to disambiguate.
5114 return ZERO if dt.fold else HOUR
5115 elif start <= dt < start + HOUR:
5116 # Gap (a non-existent hour): reverse the fold rule.
5117 return HOUR if dt.fold else ZERO
5118 else:
5119 # DST is off.
5120 return ZERO
5121
5122Eastern2 = USTimeZone2(-5, "Eastern2", "EST", "EDT")
5123Central2 = USTimeZone2(-6, "Central2", "CST", "CDT")
5124Mountain2 = USTimeZone2(-7, "Mountain2", "MST", "MDT")
5125Pacific2 = USTimeZone2(-8, "Pacific2", "PST", "PDT")
5126
5127# Europe_Vilnius_1941 tzinfo implementation reproduces the following
5128# 1941 transition from Olson's tzdist:
5129#
5130# Zone NAME GMTOFF RULES FORMAT [UNTIL]
5131# ZoneEurope/Vilnius 1:00 - CET 1940 Aug 3
5132# 3:00 - MSK 1941 Jun 24
5133# 1:00 C-Eur CE%sT 1944 Aug
5134#
5135# $ zdump -v Europe/Vilnius | grep 1941
5136# Europe/Vilnius Mon Jun 23 20:59:59 1941 UTC = Mon Jun 23 23:59:59 1941 MSK isdst=0 gmtoff=10800
5137# Europe/Vilnius Mon Jun 23 21:00:00 1941 UTC = Mon Jun 23 23:00:00 1941 CEST isdst=1 gmtoff=7200
5138
5139class Europe_Vilnius_1941(tzinfo):
5140 def _utc_fold(self):
5141 return [datetime(1941, 6, 23, 21, tzinfo=self), # Mon Jun 23 21:00:00 1941 UTC
5142 datetime(1941, 6, 23, 22, tzinfo=self)] # Mon Jun 23 22:00:00 1941 UTC
5143
5144 def _loc_fold(self):
5145 return [datetime(1941, 6, 23, 23, tzinfo=self), # Mon Jun 23 23:00:00 1941 MSK / CEST
5146 datetime(1941, 6, 24, 0, tzinfo=self)] # Mon Jun 24 00:00:00 1941 CEST
5147
5148 def utcoffset(self, dt):
5149 fold_start, fold_stop = self._loc_fold()
5150 if dt < fold_start:
5151 return 3 * HOUR
5152 if dt < fold_stop:
5153 return (2 if dt.fold else 3) * HOUR
5154 # if dt >= fold_stop
5155 return 2 * HOUR
5156
5157 def dst(self, dt):
5158 fold_start, fold_stop = self._loc_fold()
5159 if dt < fold_start:
5160 return 0 * HOUR
5161 if dt < fold_stop:
5162 return (1 if dt.fold else 0) * HOUR
5163 # if dt >= fold_stop
5164 return 1 * HOUR
5165
5166 def tzname(self, dt):
5167 fold_start, fold_stop = self._loc_fold()
5168 if dt < fold_start:
5169 return 'MSK'
5170 if dt < fold_stop:
5171 return ('MSK', 'CEST')[dt.fold]
5172 # if dt >= fold_stop
5173 return 'CEST'
5174
5175 def fromutc(self, dt):
5176 assert dt.fold == 0
5177 assert dt.tzinfo is self
5178 if dt.year != 1941:
5179 raise NotImplementedError
5180 fold_start, fold_stop = self._utc_fold()
5181 if dt < fold_start:
5182 return dt + 3 * HOUR
5183 if dt < fold_stop:
5184 return (dt + 2 * HOUR).replace(fold=1)
5185 # if dt >= fold_stop
5186 return dt + 2 * HOUR
5187
5188
5189class TestLocalTimeDisambiguation(unittest.TestCase):
5190
5191 def test_vilnius_1941_fromutc(self):
5192 Vilnius = Europe_Vilnius_1941()
5193
5194 gdt = datetime(1941, 6, 23, 20, 59, 59, tzinfo=timezone.utc)
5195 ldt = gdt.astimezone(Vilnius)
5196 self.assertEqual(ldt.strftime("%c %Z%z"),
5197 'Mon Jun 23 23:59:59 1941 MSK+0300')
5198 self.assertEqual(ldt.fold, 0)
5199 self.assertFalse(ldt.dst())
5200
5201 gdt = datetime(1941, 6, 23, 21, tzinfo=timezone.utc)
5202 ldt = gdt.astimezone(Vilnius)
5203 self.assertEqual(ldt.strftime("%c %Z%z"),
5204 'Mon Jun 23 23:00:00 1941 CEST+0200')
5205 self.assertEqual(ldt.fold, 1)
5206 self.assertTrue(ldt.dst())
5207
5208 gdt = datetime(1941, 6, 23, 22, tzinfo=timezone.utc)
5209 ldt = gdt.astimezone(Vilnius)
5210 self.assertEqual(ldt.strftime("%c %Z%z"),
5211 'Tue Jun 24 00:00:00 1941 CEST+0200')
5212 self.assertEqual(ldt.fold, 0)
5213 self.assertTrue(ldt.dst())
5214
5215 def test_vilnius_1941_toutc(self):
5216 Vilnius = Europe_Vilnius_1941()
5217
5218 ldt = datetime(1941, 6, 23, 22, 59, 59, tzinfo=Vilnius)
5219 gdt = ldt.astimezone(timezone.utc)
5220 self.assertEqual(gdt.strftime("%c %Z"),
5221 'Mon Jun 23 19:59:59 1941 UTC')
5222
5223 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius)
5224 gdt = ldt.astimezone(timezone.utc)
5225 self.assertEqual(gdt.strftime("%c %Z"),
5226 'Mon Jun 23 20:59:59 1941 UTC')
5227
5228 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius, fold=1)
5229 gdt = ldt.astimezone(timezone.utc)
5230 self.assertEqual(gdt.strftime("%c %Z"),
5231 'Mon Jun 23 21:59:59 1941 UTC')
5232
5233 ldt = datetime(1941, 6, 24, 0, tzinfo=Vilnius)
5234 gdt = ldt.astimezone(timezone.utc)
5235 self.assertEqual(gdt.strftime("%c %Z"),
5236 'Mon Jun 23 22:00:00 1941 UTC')
5237
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005238 def test_constructors(self):
5239 t = time(0, fold=1)
5240 dt = datetime(1, 1, 1, fold=1)
5241 self.assertEqual(t.fold, 1)
5242 self.assertEqual(dt.fold, 1)
5243 with self.assertRaises(TypeError):
5244 time(0, 0, 0, 0, None, 0)
5245
5246 def test_member(self):
5247 dt = datetime(1, 1, 1, fold=1)
5248 t = dt.time()
5249 self.assertEqual(t.fold, 1)
5250 t = dt.timetz()
5251 self.assertEqual(t.fold, 1)
5252
5253 def test_replace(self):
5254 t = time(0)
5255 dt = datetime(1, 1, 1)
5256 self.assertEqual(t.replace(fold=1).fold, 1)
5257 self.assertEqual(dt.replace(fold=1).fold, 1)
5258 self.assertEqual(t.replace(fold=0).fold, 0)
5259 self.assertEqual(dt.replace(fold=0).fold, 0)
5260 # Check that replacement of other fields does not change "fold".
5261 t = t.replace(fold=1, tzinfo=Eastern)
5262 dt = dt.replace(fold=1, tzinfo=Eastern)
5263 self.assertEqual(t.replace(tzinfo=None).fold, 1)
5264 self.assertEqual(dt.replace(tzinfo=None).fold, 1)
Serhiy Storchaka314d6fc2017-03-31 22:48:16 +03005265 # Out of bounds.
5266 with self.assertRaises(ValueError):
5267 t.replace(fold=2)
5268 with self.assertRaises(ValueError):
5269 dt.replace(fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005270 # Check that fold is a keyword-only argument
5271 with self.assertRaises(TypeError):
5272 t.replace(1, 1, 1, None, 1)
5273 with self.assertRaises(TypeError):
5274 dt.replace(1, 1, 1, 1, 1, 1, 1, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04005275
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005276 def test_comparison(self):
5277 t = time(0)
5278 dt = datetime(1, 1, 1)
5279 self.assertEqual(t, t.replace(fold=1))
5280 self.assertEqual(dt, dt.replace(fold=1))
5281
5282 def test_hash(self):
5283 t = time(0)
5284 dt = datetime(1, 1, 1)
5285 self.assertEqual(hash(t), hash(t.replace(fold=1)))
5286 self.assertEqual(hash(dt), hash(dt.replace(fold=1)))
5287
5288 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5289 def test_fromtimestamp(self):
5290 s = 1414906200
5291 dt0 = datetime.fromtimestamp(s)
5292 dt1 = datetime.fromtimestamp(s + 3600)
5293 self.assertEqual(dt0.fold, 0)
5294 self.assertEqual(dt1.fold, 1)
5295
5296 @support.run_with_tz('Australia/Lord_Howe')
5297 def test_fromtimestamp_lord_howe(self):
5298 tm = _time.localtime(1.4e9)
5299 if _time.strftime('%Z%z', tm) != 'LHST+1030':
5300 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
5301 # $ TZ=Australia/Lord_Howe date -r 1428158700
5302 # Sun Apr 5 01:45:00 LHDT 2015
5303 # $ TZ=Australia/Lord_Howe date -r 1428160500
5304 # Sun Apr 5 01:45:00 LHST 2015
5305 s = 1428158700
5306 t0 = datetime.fromtimestamp(s)
5307 t1 = datetime.fromtimestamp(s + 1800)
5308 self.assertEqual(t0, t1)
5309 self.assertEqual(t0.fold, 0)
5310 self.assertEqual(t1.fold, 1)
5311
Ammar Askar96d1e692018-07-25 09:54:58 -07005312 def test_fromtimestamp_low_fold_detection(self):
5313 # Ensure that fold detection doesn't cause an
5314 # OSError for really low values, see bpo-29097
5315 self.assertEqual(datetime.fromtimestamp(0).fold, 0)
5316
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005317 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5318 def test_timestamp(self):
5319 dt0 = datetime(2014, 11, 2, 1, 30)
5320 dt1 = dt0.replace(fold=1)
5321 self.assertEqual(dt0.timestamp() + 3600,
5322 dt1.timestamp())
5323
5324 @support.run_with_tz('Australia/Lord_Howe')
5325 def test_timestamp_lord_howe(self):
5326 tm = _time.localtime(1.4e9)
5327 if _time.strftime('%Z%z', tm) != 'LHST+1030':
5328 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
5329 t = datetime(2015, 4, 5, 1, 45)
5330 s0 = t.replace(fold=0).timestamp()
5331 s1 = t.replace(fold=1).timestamp()
5332 self.assertEqual(s0 + 1800, s1)
5333
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005334 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5335 def test_astimezone(self):
5336 dt0 = datetime(2014, 11, 2, 1, 30)
5337 dt1 = dt0.replace(fold=1)
5338 # Convert both naive instances to aware.
5339 adt0 = dt0.astimezone()
5340 adt1 = dt1.astimezone()
5341 # Check that the first instance in DST zone and the second in STD
5342 self.assertEqual(adt0.tzname(), 'EDT')
5343 self.assertEqual(adt1.tzname(), 'EST')
5344 self.assertEqual(adt0 + HOUR, adt1)
5345 # Aware instances with fixed offset tzinfo's always have fold=0
5346 self.assertEqual(adt0.fold, 0)
5347 self.assertEqual(adt1.fold, 0)
5348
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005349 def test_pickle_fold(self):
5350 t = time(fold=1)
5351 dt = datetime(1, 1, 1, fold=1)
5352 for pickler, unpickler, proto in pickle_choices:
5353 for x in [t, dt]:
5354 s = pickler.dumps(x, proto)
5355 y = unpickler.loads(s)
5356 self.assertEqual(x, y)
5357 self.assertEqual((0 if proto < 4 else x.fold), y.fold)
5358
5359 def test_repr(self):
5360 t = time(fold=1)
5361 dt = datetime(1, 1, 1, fold=1)
5362 self.assertEqual(repr(t), 'datetime.time(0, 0, fold=1)')
5363 self.assertEqual(repr(dt),
5364 'datetime.datetime(1, 1, 1, 0, 0, fold=1)')
5365
5366 def test_dst(self):
5367 # Let's first establish that things work in regular times.
5368 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
5369 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
5370 self.assertEqual(dt_summer.dst(), HOUR)
5371 self.assertEqual(dt_winter.dst(), ZERO)
5372 # The disambiguation flag is ignored
5373 self.assertEqual(dt_summer.replace(fold=1).dst(), HOUR)
5374 self.assertEqual(dt_winter.replace(fold=1).dst(), ZERO)
5375
5376 # Pick local time in the fold.
5377 for minute in [0, 30, 59]:
5378 dt = datetime(2002, 10, 27, 1, minute, tzinfo=Eastern2)
5379 # With fold=0 (the default) it is in DST.
5380 self.assertEqual(dt.dst(), HOUR)
5381 # With fold=1 it is in STD.
5382 self.assertEqual(dt.replace(fold=1).dst(), ZERO)
5383
5384 # Pick local time in the gap.
5385 for minute in [0, 30, 59]:
5386 dt = datetime(2002, 4, 7, 2, minute, tzinfo=Eastern2)
5387 # With fold=0 (the default) it is in STD.
5388 self.assertEqual(dt.dst(), ZERO)
5389 # With fold=1 it is in DST.
5390 self.assertEqual(dt.replace(fold=1).dst(), HOUR)
5391
5392
5393 def test_utcoffset(self):
5394 # Let's first establish that things work in regular times.
5395 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
5396 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
5397 self.assertEqual(dt_summer.utcoffset(), -4 * HOUR)
5398 self.assertEqual(dt_winter.utcoffset(), -5 * HOUR)
5399 # The disambiguation flag is ignored
5400 self.assertEqual(dt_summer.replace(fold=1).utcoffset(), -4 * HOUR)
5401 self.assertEqual(dt_winter.replace(fold=1).utcoffset(), -5 * HOUR)
5402
5403 def test_fromutc(self):
5404 # Let's first establish that things work in regular times.
5405 u_summer = datetime(2002, 10, 27, 6, tzinfo=Eastern2) - timedelta.resolution
5406 u_winter = datetime(2002, 10, 27, 7, tzinfo=Eastern2)
5407 t_summer = Eastern2.fromutc(u_summer)
5408 t_winter = Eastern2.fromutc(u_winter)
5409 self.assertEqual(t_summer, u_summer - 4 * HOUR)
5410 self.assertEqual(t_winter, u_winter - 5 * HOUR)
5411 self.assertEqual(t_summer.fold, 0)
5412 self.assertEqual(t_winter.fold, 0)
5413
5414 # What happens in the fall-back fold?
5415 u = datetime(2002, 10, 27, 5, 30, tzinfo=Eastern2)
5416 t0 = Eastern2.fromutc(u)
5417 u += HOUR
5418 t1 = Eastern2.fromutc(u)
5419 self.assertEqual(t0, t1)
5420 self.assertEqual(t0.fold, 0)
5421 self.assertEqual(t1.fold, 1)
5422 # The tricky part is when u is in the local fold:
5423 u = datetime(2002, 10, 27, 1, 30, tzinfo=Eastern2)
5424 t = Eastern2.fromutc(u)
5425 self.assertEqual((t.day, t.hour), (26, 21))
5426 # .. or gets into the local fold after a standard time adjustment
5427 u = datetime(2002, 10, 27, 6, 30, tzinfo=Eastern2)
5428 t = Eastern2.fromutc(u)
5429 self.assertEqual((t.day, t.hour), (27, 1))
5430
5431 # What happens in the spring-forward gap?
5432 u = datetime(2002, 4, 7, 2, 0, tzinfo=Eastern2)
5433 t = Eastern2.fromutc(u)
5434 self.assertEqual((t.day, t.hour), (6, 21))
5435
5436 def test_mixed_compare_regular(self):
5437 t = datetime(2000, 1, 1, tzinfo=Eastern2)
5438 self.assertEqual(t, t.astimezone(timezone.utc))
5439 t = datetime(2000, 6, 1, tzinfo=Eastern2)
5440 self.assertEqual(t, t.astimezone(timezone.utc))
5441
5442 def test_mixed_compare_fold(self):
5443 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
5444 t_fold_utc = t_fold.astimezone(timezone.utc)
5445 self.assertNotEqual(t_fold, t_fold_utc)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04005446 self.assertNotEqual(t_fold_utc, t_fold)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005447
5448 def test_mixed_compare_gap(self):
5449 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
5450 t_gap_utc = t_gap.astimezone(timezone.utc)
5451 self.assertNotEqual(t_gap, t_gap_utc)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04005452 self.assertNotEqual(t_gap_utc, t_gap)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005453
5454 def test_hash_aware(self):
5455 t = datetime(2000, 1, 1, tzinfo=Eastern2)
5456 self.assertEqual(hash(t), hash(t.replace(fold=1)))
5457 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
5458 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
5459 self.assertEqual(hash(t_fold), hash(t_fold.replace(fold=1)))
5460 self.assertEqual(hash(t_gap), hash(t_gap.replace(fold=1)))
5461
5462SEC = timedelta(0, 1)
5463
5464def pairs(iterable):
5465 a, b = itertools.tee(iterable)
5466 next(b, None)
5467 return zip(a, b)
5468
5469class ZoneInfo(tzinfo):
5470 zoneroot = '/usr/share/zoneinfo'
5471 def __init__(self, ut, ti):
5472 """
5473
5474 :param ut: array
5475 Array of transition point timestamps
5476 :param ti: list
5477 A list of (offset, isdst, abbr) tuples
5478 :return: None
5479 """
5480 self.ut = ut
5481 self.ti = ti
5482 self.lt = self.invert(ut, ti)
5483
5484 @staticmethod
5485 def invert(ut, ti):
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04005486 lt = (array('q', ut), array('q', ut))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005487 if ut:
5488 offset = ti[0][0] // SEC
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04005489 lt[0][0] += offset
5490 lt[1][0] += offset
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005491 for i in range(1, len(ut)):
5492 lt[0][i] += ti[i-1][0] // SEC
5493 lt[1][i] += ti[i][0] // SEC
5494 return lt
5495
5496 @classmethod
5497 def fromfile(cls, fileobj):
5498 if fileobj.read(4).decode() != "TZif":
5499 raise ValueError("not a zoneinfo file")
5500 fileobj.seek(32)
5501 counts = array('i')
5502 counts.fromfile(fileobj, 3)
5503 if sys.byteorder != 'big':
5504 counts.byteswap()
5505
5506 ut = array('i')
5507 ut.fromfile(fileobj, counts[0])
5508 if sys.byteorder != 'big':
5509 ut.byteswap()
5510
5511 type_indices = array('B')
5512 type_indices.fromfile(fileobj, counts[0])
5513
5514 ttis = []
5515 for i in range(counts[1]):
5516 ttis.append(struct.unpack(">lbb", fileobj.read(6)))
5517
5518 abbrs = fileobj.read(counts[2])
5519
5520 # Convert ttis
5521 for i, (gmtoff, isdst, abbrind) in enumerate(ttis):
5522 abbr = abbrs[abbrind:abbrs.find(0, abbrind)].decode()
5523 ttis[i] = (timedelta(0, gmtoff), isdst, abbr)
5524
5525 ti = [None] * len(ut)
5526 for i, idx in enumerate(type_indices):
5527 ti[i] = ttis[idx]
5528
5529 self = cls(ut, ti)
5530
5531 return self
5532
5533 @classmethod
5534 def fromname(cls, name):
5535 path = os.path.join(cls.zoneroot, name)
5536 with open(path, 'rb') as f:
5537 return cls.fromfile(f)
5538
5539 EPOCHORDINAL = date(1970, 1, 1).toordinal()
5540
5541 def fromutc(self, dt):
5542 """datetime in UTC -> datetime in local time."""
5543
5544 if not isinstance(dt, datetime):
5545 raise TypeError("fromutc() requires a datetime argument")
5546 if dt.tzinfo is not self:
5547 raise ValueError("dt.tzinfo is not self")
5548
5549 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
5550 + dt.hour * 3600
5551 + dt.minute * 60
5552 + dt.second)
5553
5554 if timestamp < self.ut[1]:
5555 tti = self.ti[0]
5556 fold = 0
5557 else:
5558 idx = bisect.bisect_right(self.ut, timestamp)
5559 assert self.ut[idx-1] <= timestamp
5560 assert idx == len(self.ut) or timestamp < self.ut[idx]
5561 tti_prev, tti = self.ti[idx-2:idx]
5562 # Detect fold
5563 shift = tti_prev[0] - tti[0]
5564 fold = (shift > timedelta(0, timestamp - self.ut[idx-1]))
5565 dt += tti[0]
5566 if fold:
5567 return dt.replace(fold=1)
5568 else:
5569 return dt
5570
5571 def _find_ti(self, dt, i):
5572 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
5573 + dt.hour * 3600
5574 + dt.minute * 60
5575 + dt.second)
5576 lt = self.lt[dt.fold]
5577 idx = bisect.bisect_right(lt, timestamp)
5578
5579 return self.ti[max(0, idx - 1)][i]
5580
5581 def utcoffset(self, dt):
5582 return self._find_ti(dt, 0)
5583
5584 def dst(self, dt):
5585 isdst = self._find_ti(dt, 1)
5586 # XXX: We cannot accurately determine the "save" value,
5587 # so let's return 1h whenever DST is in effect. Since
5588 # we don't use dst() in fromutc(), it is unlikely that
5589 # it will be needed for anything more than bool(dst()).
5590 return ZERO if isdst else HOUR
5591
5592 def tzname(self, dt):
5593 return self._find_ti(dt, 2)
5594
5595 @classmethod
5596 def zonenames(cls, zonedir=None):
5597 if zonedir is None:
5598 zonedir = cls.zoneroot
Alexander Belopolsky1b8f26c2016-08-11 11:01:45 -04005599 zone_tab = os.path.join(zonedir, 'zone.tab')
5600 try:
5601 f = open(zone_tab)
5602 except OSError:
5603 return
5604 with f:
5605 for line in f:
5606 line = line.strip()
5607 if line and not line.startswith('#'):
5608 yield line.split()[2]
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005609
5610 @classmethod
5611 def stats(cls, start_year=1):
5612 count = gap_count = fold_count = zeros_count = 0
5613 min_gap = min_fold = timedelta.max
5614 max_gap = max_fold = ZERO
5615 min_gap_datetime = max_gap_datetime = datetime.min
5616 min_gap_zone = max_gap_zone = None
5617 min_fold_datetime = max_fold_datetime = datetime.min
5618 min_fold_zone = max_fold_zone = None
5619 stats_since = datetime(start_year, 1, 1) # Starting from 1970 eliminates a lot of noise
5620 for zonename in cls.zonenames():
5621 count += 1
5622 tz = cls.fromname(zonename)
5623 for dt, shift in tz.transitions():
5624 if dt < stats_since:
5625 continue
5626 if shift > ZERO:
5627 gap_count += 1
5628 if (shift, dt) > (max_gap, max_gap_datetime):
5629 max_gap = shift
5630 max_gap_zone = zonename
5631 max_gap_datetime = dt
5632 if (shift, datetime.max - dt) < (min_gap, datetime.max - min_gap_datetime):
5633 min_gap = shift
5634 min_gap_zone = zonename
5635 min_gap_datetime = dt
5636 elif shift < ZERO:
5637 fold_count += 1
5638 shift = -shift
5639 if (shift, dt) > (max_fold, max_fold_datetime):
5640 max_fold = shift
5641 max_fold_zone = zonename
5642 max_fold_datetime = dt
5643 if (shift, datetime.max - dt) < (min_fold, datetime.max - min_fold_datetime):
5644 min_fold = shift
5645 min_fold_zone = zonename
5646 min_fold_datetime = dt
5647 else:
5648 zeros_count += 1
5649 trans_counts = (gap_count, fold_count, zeros_count)
5650 print("Number of zones: %5d" % count)
5651 print("Number of transitions: %5d = %d (gaps) + %d (folds) + %d (zeros)" %
5652 ((sum(trans_counts),) + trans_counts))
5653 print("Min gap: %16s at %s in %s" % (min_gap, min_gap_datetime, min_gap_zone))
5654 print("Max gap: %16s at %s in %s" % (max_gap, max_gap_datetime, max_gap_zone))
5655 print("Min fold: %16s at %s in %s" % (min_fold, min_fold_datetime, min_fold_zone))
5656 print("Max fold: %16s at %s in %s" % (max_fold, max_fold_datetime, max_fold_zone))
5657
5658
5659 def transitions(self):
5660 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
5661 shift = ti[0] - prev_ti[0]
5662 yield datetime.utcfromtimestamp(t), shift
5663
5664 def nondst_folds(self):
5665 """Find all folds with the same value of isdst on both sides of the transition."""
5666 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
5667 shift = ti[0] - prev_ti[0]
5668 if shift < ZERO and ti[1] == prev_ti[1]:
5669 yield datetime.utcfromtimestamp(t), -shift, prev_ti[2], ti[2]
5670
5671 @classmethod
5672 def print_all_nondst_folds(cls, same_abbr=False, start_year=1):
5673 count = 0
5674 for zonename in cls.zonenames():
5675 tz = cls.fromname(zonename)
5676 for dt, shift, prev_abbr, abbr in tz.nondst_folds():
5677 if dt.year < start_year or same_abbr and prev_abbr != abbr:
5678 continue
5679 count += 1
5680 print("%3d) %-30s %s %10s %5s -> %s" %
5681 (count, zonename, dt, shift, prev_abbr, abbr))
5682
5683 def folds(self):
5684 for t, shift in self.transitions():
5685 if shift < ZERO:
5686 yield t, -shift
5687
5688 def gaps(self):
5689 for t, shift in self.transitions():
5690 if shift > ZERO:
5691 yield t, shift
5692
5693 def zeros(self):
5694 for t, shift in self.transitions():
5695 if not shift:
5696 yield t
5697
5698
5699class ZoneInfoTest(unittest.TestCase):
5700 zonename = 'America/New_York'
5701
5702 def setUp(self):
5703 if sys.platform == "win32":
5704 self.skipTest("Skipping zoneinfo tests on Windows")
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04005705 try:
5706 self.tz = ZoneInfo.fromname(self.zonename)
5707 except FileNotFoundError as err:
5708 self.skipTest("Skipping %s: %s" % (self.zonename, err))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005709
5710 def assertEquivDatetimes(self, a, b):
5711 self.assertEqual((a.replace(tzinfo=None), a.fold, id(a.tzinfo)),
5712 (b.replace(tzinfo=None), b.fold, id(b.tzinfo)))
5713
5714 def test_folds(self):
5715 tz = self.tz
5716 for dt, shift in tz.folds():
5717 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
5718 udt = dt + x
5719 ldt = tz.fromutc(udt.replace(tzinfo=tz))
5720 self.assertEqual(ldt.fold, 1)
5721 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
5722 self.assertEquivDatetimes(adt, ldt)
5723 utcoffset = ldt.utcoffset()
5724 self.assertEqual(ldt.replace(tzinfo=None), udt + utcoffset)
5725 # Round trip
5726 self.assertEquivDatetimes(ldt.astimezone(timezone.utc),
5727 udt.replace(tzinfo=timezone.utc))
5728
5729
5730 for x in [-timedelta.resolution, shift]:
5731 udt = dt + x
5732 udt = udt.replace(tzinfo=tz)
5733 ldt = tz.fromutc(udt)
5734 self.assertEqual(ldt.fold, 0)
5735
5736 def test_gaps(self):
5737 tz = self.tz
5738 for dt, shift in tz.gaps():
5739 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
5740 udt = dt + x
5741 udt = udt.replace(tzinfo=tz)
5742 ldt = tz.fromutc(udt)
5743 self.assertEqual(ldt.fold, 0)
5744 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
5745 self.assertEquivDatetimes(adt, ldt)
5746 utcoffset = ldt.utcoffset()
5747 self.assertEqual(ldt.replace(tzinfo=None), udt.replace(tzinfo=None) + utcoffset)
5748 # Create a local time inside the gap
5749 ldt = tz.fromutc(dt.replace(tzinfo=tz)) - shift + x
5750 self.assertLess(ldt.replace(fold=1).utcoffset(),
5751 ldt.replace(fold=0).utcoffset(),
5752 "At %s." % ldt)
5753
5754 for x in [-timedelta.resolution, shift]:
5755 udt = dt + x
5756 ldt = tz.fromutc(udt.replace(tzinfo=tz))
5757 self.assertEqual(ldt.fold, 0)
5758
5759 def test_system_transitions(self):
5760 if ('Riyadh8' in self.zonename or
5761 # From tzdata NEWS file:
5762 # The files solar87, solar88, and solar89 are no longer distributed.
5763 # They were a negative experiment - that is, a demonstration that
5764 # tz data can represent solar time only with some difficulty and error.
5765 # Their presence in the distribution caused confusion, as Riyadh
5766 # civil time was generally not solar time in those years.
5767 self.zonename.startswith('right/')):
5768 self.skipTest("Skipping %s" % self.zonename)
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04005769 tz = self.tz
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005770 TZ = os.environ.get('TZ')
5771 os.environ['TZ'] = self.zonename
5772 try:
5773 _time.tzset()
5774 for udt, shift in tz.transitions():
Alexander Belopolsky10c2dd22016-08-12 19:08:15 -04005775 if udt.year >= 2037:
5776 # System support for times around the end of 32-bit time_t
5777 # and later is flaky on many systems.
5778 break
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005779 s0 = (udt - datetime(1970, 1, 1)) // SEC
5780 ss = shift // SEC # shift seconds
5781 for x in [-40 * 3600, -20*3600, -1, 0,
5782 ss - 1, ss + 20 * 3600, ss + 40 * 3600]:
5783 s = s0 + x
5784 sdt = datetime.fromtimestamp(s)
5785 tzdt = datetime.fromtimestamp(s, tz).replace(tzinfo=None)
5786 self.assertEquivDatetimes(sdt, tzdt)
5787 s1 = sdt.timestamp()
5788 self.assertEqual(s, s1)
5789 if ss > 0: # gap
5790 # Create local time inside the gap
5791 dt = datetime.fromtimestamp(s0) - shift / 2
5792 ts0 = dt.timestamp()
5793 ts1 = dt.replace(fold=1).timestamp()
5794 self.assertEqual(ts0, s0 + ss / 2)
5795 self.assertEqual(ts1, s0 - ss / 2)
5796 finally:
5797 if TZ is None:
5798 del os.environ['TZ']
5799 else:
5800 os.environ['TZ'] = TZ
5801 _time.tzset()
5802
5803
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04005804class ZoneInfoCompleteTest(unittest.TestSuite):
5805 def __init__(self):
5806 tests = []
5807 if is_resource_enabled('tzdata'):
5808 for name in ZoneInfo.zonenames():
5809 Test = type('ZoneInfoTest[%s]' % name, (ZoneInfoTest,), {})
5810 Test.zonename = name
5811 for method in dir(Test):
5812 if method.startswith('test_'):
5813 tests.append(Test(method))
5814 super().__init__(tests)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005815
5816# Iran had a sub-minute UTC offset before 1946.
5817class IranTest(ZoneInfoTest):
Alexander Belopolsky07e2a0a2016-07-30 11:41:02 -04005818 zonename = 'Asia/Tehran'
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005819
Paul Ganssle04af5b12018-01-24 17:29:30 -05005820
5821class CapiTest(unittest.TestCase):
5822 def setUp(self):
5823 # Since the C API is not present in the _Pure tests, skip all tests
5824 if self.__class__.__name__.endswith('Pure'):
5825 self.skipTest('Not relevant in pure Python')
5826
5827 # This *must* be called, and it must be called first, so until either
5828 # restriction is loosened, we'll call it as part of test setup
5829 _testcapi.test_datetime_capi()
5830
5831 def test_utc_capi(self):
5832 for use_macro in (True, False):
5833 capi_utc = _testcapi.get_timezone_utc_capi(use_macro)
5834
5835 with self.subTest(use_macro=use_macro):
5836 self.assertIs(capi_utc, timezone.utc)
5837
5838 def test_timezones_capi(self):
5839 est_capi, est_macro, est_macro_nn = _testcapi.make_timezones_capi()
5840
5841 exp_named = timezone(timedelta(hours=-5), "EST")
5842 exp_unnamed = timezone(timedelta(hours=-5))
5843
5844 cases = [
5845 ('est_capi', est_capi, exp_named),
5846 ('est_macro', est_macro, exp_named),
5847 ('est_macro_nn', est_macro_nn, exp_unnamed)
5848 ]
5849
5850 for name, tz_act, tz_exp in cases:
5851 with self.subTest(name=name):
5852 self.assertEqual(tz_act, tz_exp)
5853
5854 dt1 = datetime(2000, 2, 4, tzinfo=tz_act)
5855 dt2 = datetime(2000, 2, 4, tzinfo=tz_exp)
5856
5857 self.assertEqual(dt1, dt2)
5858 self.assertEqual(dt1.tzname(), dt2.tzname())
5859
5860 dt_utc = datetime(2000, 2, 4, 5, tzinfo=timezone.utc)
5861
5862 self.assertEqual(dt1.astimezone(timezone.utc), dt_utc)
5863
Paul Gansslea049f572018-02-22 15:15:32 -05005864 def test_timezones_offset_zero(self):
5865 utc0, utc1, non_utc = _testcapi.get_timezones_offset_zero()
5866
5867 with self.subTest(testname="utc0"):
5868 self.assertIs(utc0, timezone.utc)
5869
5870 with self.subTest(testname="utc1"):
5871 self.assertIs(utc1, timezone.utc)
5872
5873 with self.subTest(testname="non_utc"):
5874 self.assertIsNot(non_utc, timezone.utc)
5875
5876 non_utc_exp = timezone(timedelta(hours=0), "")
5877
5878 self.assertEqual(non_utc, non_utc_exp)
5879
5880 dt1 = datetime(2000, 2, 4, tzinfo=non_utc)
5881 dt2 = datetime(2000, 2, 4, tzinfo=non_utc_exp)
5882
5883 self.assertEqual(dt1, dt2)
5884 self.assertEqual(dt1.tzname(), dt2.tzname())
5885
Paul Ganssle04af5b12018-01-24 17:29:30 -05005886 def test_check_date(self):
5887 class DateSubclass(date):
5888 pass
5889
5890 d = date(2011, 1, 1)
5891 ds = DateSubclass(2011, 1, 1)
5892 dt = datetime(2011, 1, 1)
5893
5894 is_date = _testcapi.datetime_check_date
5895
5896 # Check the ones that should be valid
5897 self.assertTrue(is_date(d))
5898 self.assertTrue(is_date(dt))
5899 self.assertTrue(is_date(ds))
5900 self.assertTrue(is_date(d, True))
5901
5902 # Check that the subclasses do not match exactly
5903 self.assertFalse(is_date(dt, True))
5904 self.assertFalse(is_date(ds, True))
5905
5906 # Check that various other things are not dates at all
5907 args = [tuple(), list(), 1, '2011-01-01',
5908 timedelta(1), timezone.utc, time(12, 00)]
5909 for arg in args:
5910 for exact in (True, False):
5911 with self.subTest(arg=arg, exact=exact):
5912 self.assertFalse(is_date(arg, exact))
5913
5914 def test_check_time(self):
5915 class TimeSubclass(time):
5916 pass
5917
5918 t = time(12, 30)
5919 ts = TimeSubclass(12, 30)
5920
5921 is_time = _testcapi.datetime_check_time
5922
5923 # Check the ones that should be valid
5924 self.assertTrue(is_time(t))
5925 self.assertTrue(is_time(ts))
5926 self.assertTrue(is_time(t, True))
5927
5928 # Check that the subclass does not match exactly
5929 self.assertFalse(is_time(ts, True))
5930
5931 # Check that various other things are not times
5932 args = [tuple(), list(), 1, '2011-01-01',
5933 timedelta(1), timezone.utc, date(2011, 1, 1)]
5934
5935 for arg in args:
5936 for exact in (True, False):
5937 with self.subTest(arg=arg, exact=exact):
5938 self.assertFalse(is_time(arg, exact))
5939
5940 def test_check_datetime(self):
5941 class DateTimeSubclass(datetime):
5942 pass
5943
5944 dt = datetime(2011, 1, 1, 12, 30)
5945 dts = DateTimeSubclass(2011, 1, 1, 12, 30)
5946
5947 is_datetime = _testcapi.datetime_check_datetime
5948
5949 # Check the ones that should be valid
5950 self.assertTrue(is_datetime(dt))
5951 self.assertTrue(is_datetime(dts))
5952 self.assertTrue(is_datetime(dt, True))
5953
5954 # Check that the subclass does not match exactly
5955 self.assertFalse(is_datetime(dts, True))
5956
5957 # Check that various other things are not datetimes
5958 args = [tuple(), list(), 1, '2011-01-01',
5959 timedelta(1), timezone.utc, date(2011, 1, 1)]
5960
5961 for arg in args:
5962 for exact in (True, False):
5963 with self.subTest(arg=arg, exact=exact):
5964 self.assertFalse(is_datetime(arg, exact))
5965
5966 def test_check_delta(self):
5967 class TimeDeltaSubclass(timedelta):
5968 pass
5969
5970 td = timedelta(1)
5971 tds = TimeDeltaSubclass(1)
5972
5973 is_timedelta = _testcapi.datetime_check_delta
5974
5975 # Check the ones that should be valid
5976 self.assertTrue(is_timedelta(td))
5977 self.assertTrue(is_timedelta(tds))
5978 self.assertTrue(is_timedelta(td, True))
5979
5980 # Check that the subclass does not match exactly
5981 self.assertFalse(is_timedelta(tds, True))
5982
5983 # Check that various other things are not timedeltas
5984 args = [tuple(), list(), 1, '2011-01-01',
5985 timezone.utc, date(2011, 1, 1), datetime(2011, 1, 1)]
5986
5987 for arg in args:
5988 for exact in (True, False):
5989 with self.subTest(arg=arg, exact=exact):
5990 self.assertFalse(is_timedelta(arg, exact))
5991
5992 def test_check_tzinfo(self):
5993 class TZInfoSubclass(tzinfo):
5994 pass
5995
5996 tzi = tzinfo()
5997 tzis = TZInfoSubclass()
5998 tz = timezone(timedelta(hours=-5))
5999
6000 is_tzinfo = _testcapi.datetime_check_tzinfo
6001
6002 # Check the ones that should be valid
6003 self.assertTrue(is_tzinfo(tzi))
6004 self.assertTrue(is_tzinfo(tz))
6005 self.assertTrue(is_tzinfo(tzis))
6006 self.assertTrue(is_tzinfo(tzi, True))
6007
6008 # Check that the subclasses do not match exactly
6009 self.assertFalse(is_tzinfo(tz, True))
6010 self.assertFalse(is_tzinfo(tzis, True))
6011
6012 # Check that various other things are not tzinfos
6013 args = [tuple(), list(), 1, '2011-01-01',
6014 date(2011, 1, 1), datetime(2011, 1, 1)]
6015
6016 for arg in args:
6017 for exact in (True, False):
6018 with self.subTest(arg=arg, exact=exact):
6019 self.assertFalse(is_tzinfo(arg, exact))
6020
Paul Ganssle4d8c8c02019-04-27 15:39:40 -04006021 def test_date_from_timestamp(self):
6022 ts = datetime(1995, 4, 12).timestamp()
6023
6024 for macro in [0, 1]:
6025 with self.subTest(macro=macro):
6026 d = _testcapi.get_date_fromtimestamp(int(ts), macro)
6027
6028 self.assertEqual(d, date(1995, 4, 12))
6029
6030 def test_datetime_from_timestamp(self):
6031 ts0 = datetime(1995, 4, 12).timestamp()
6032 ts1 = datetime(1995, 4, 12, 12, 30).timestamp()
6033
6034 cases = [
6035 ((1995, 4, 12), None, False),
6036 ((1995, 4, 12), None, True),
6037 ((1995, 4, 12), timezone(timedelta(hours=1)), True),
6038 ((1995, 4, 12, 14, 30), None, False),
6039 ((1995, 4, 12, 14, 30), None, True),
6040 ((1995, 4, 12, 14, 30), timezone(timedelta(hours=1)), True),
6041 ]
6042
6043 from_timestamp = _testcapi.get_datetime_fromtimestamp
6044 for case in cases:
6045 for macro in [0, 1]:
6046 with self.subTest(case=case, macro=macro):
6047 dtup, tzinfo, usetz = case
6048 dt_orig = datetime(*dtup, tzinfo=tzinfo)
6049 ts = int(dt_orig.timestamp())
6050
6051 dt_rt = from_timestamp(ts, tzinfo, usetz, macro)
6052
6053 self.assertEqual(dt_orig, dt_rt)
6054
6055
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04006056def load_tests(loader, standard_tests, pattern):
6057 standard_tests.addTest(ZoneInfoCompleteTest())
6058 return standard_tests
6059
6060
Alexander Belopolskycf86e362010-07-23 19:25:47 +00006061if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -05006062 unittest.main()