blob: d729c7efd52fde3ccb5fc12a74497f66ea253b96 [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
823 def test_division(self):
824 t = timedelta(hours=1, minutes=24, seconds=19)
825 second = timedelta(seconds=1)
826 self.assertEqual(t / second, 5059.0)
827 self.assertEqual(t // second, 5059)
828
829 t = timedelta(minutes=2, seconds=30)
830 minute = timedelta(minutes=1)
831 self.assertEqual(t / minute, 2.5)
832 self.assertEqual(t // minute, 2)
833
834 zerotd = timedelta(0)
835 self.assertRaises(ZeroDivisionError, truediv, t, zerotd)
836 self.assertRaises(ZeroDivisionError, floordiv, t, zerotd)
837
838 # self.assertRaises(TypeError, truediv, t, 2)
839 # note: floor division of a timedelta by an integer *is*
840 # currently permitted.
841
842 def test_remainder(self):
843 t = timedelta(minutes=2, seconds=30)
844 minute = timedelta(minutes=1)
845 r = t % minute
846 self.assertEqual(r, timedelta(seconds=30))
847
848 t = timedelta(minutes=-2, seconds=30)
849 r = t % minute
850 self.assertEqual(r, timedelta(seconds=30))
851
852 zerotd = timedelta(0)
853 self.assertRaises(ZeroDivisionError, mod, t, zerotd)
854
855 self.assertRaises(TypeError, mod, t, 10)
856
857 def test_divmod(self):
858 t = timedelta(minutes=2, seconds=30)
859 minute = timedelta(minutes=1)
860 q, r = divmod(t, minute)
861 self.assertEqual(q, 2)
862 self.assertEqual(r, timedelta(seconds=30))
863
864 t = timedelta(minutes=-2, seconds=30)
865 q, r = divmod(t, minute)
866 self.assertEqual(q, -2)
867 self.assertEqual(r, timedelta(seconds=30))
868
869 zerotd = timedelta(0)
870 self.assertRaises(ZeroDivisionError, divmod, t, zerotd)
871
872 self.assertRaises(TypeError, divmod, t, 10)
873
Oren Milman865e4b42017-09-19 15:58:11 +0300874 def test_issue31293(self):
875 # The interpreter shouldn't crash in case a timedelta is divided or
876 # multiplied by a float with a bad as_integer_ratio() method.
877 def get_bad_float(bad_ratio):
878 class BadFloat(float):
879 def as_integer_ratio(self):
880 return bad_ratio
881 return BadFloat()
882
883 with self.assertRaises(TypeError):
884 timedelta() / get_bad_float(1 << 1000)
885 with self.assertRaises(TypeError):
886 timedelta() * get_bad_float(1 << 1000)
887
888 for bad_ratio in [(), (42, ), (1, 2, 3)]:
889 with self.assertRaises(ValueError):
890 timedelta() / get_bad_float(bad_ratio)
891 with self.assertRaises(ValueError):
892 timedelta() * get_bad_float(bad_ratio)
893
Serhiy Storchaka4ffd4652017-10-23 17:12:28 +0300894 def test_issue31752(self):
895 # The interpreter shouldn't crash because divmod() returns negative
896 # remainder.
897 class BadInt(int):
898 def __mul__(self, other):
899 return Prod()
Serhiy Storchaka3ec0f492018-11-20 20:41:09 +0200900 def __rmul__(self, other):
901 return Prod()
902 def __floordiv__(self, other):
903 return Prod()
904 def __rfloordiv__(self, other):
905 return Prod()
Serhiy Storchaka4ffd4652017-10-23 17:12:28 +0300906
907 class Prod:
Serhiy Storchaka3ec0f492018-11-20 20:41:09 +0200908 def __add__(self, other):
909 return Sum()
Serhiy Storchaka4ffd4652017-10-23 17:12:28 +0300910 def __radd__(self, other):
911 return Sum()
912
913 class Sum(int):
914 def __divmod__(self, other):
Serhiy Storchaka3ec0f492018-11-20 20:41:09 +0200915 return divmodresult
Serhiy Storchaka4ffd4652017-10-23 17:12:28 +0300916
Serhiy Storchaka3ec0f492018-11-20 20:41:09 +0200917 for divmodresult in [None, (), (0, 1, 2), (0, -1)]:
918 with self.subTest(divmodresult=divmodresult):
919 # The following examples should not crash.
920 try:
921 timedelta(microseconds=BadInt(1))
922 except TypeError:
923 pass
924 try:
925 timedelta(hours=BadInt(1))
926 except TypeError:
927 pass
928 try:
929 timedelta(weeks=BadInt(1))
930 except (TypeError, ValueError):
931 pass
932 try:
933 timedelta(1) * BadInt(1)
934 except (TypeError, ValueError):
935 pass
936 try:
937 BadInt(1) * timedelta(1)
938 except TypeError:
939 pass
940 try:
941 timedelta(1) // BadInt(1)
942 except TypeError:
943 pass
Serhiy Storchaka4ffd4652017-10-23 17:12:28 +0300944
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000945
946#############################################################################
947# date tests
948
949class TestDateOnly(unittest.TestCase):
950 # Tests here won't pass if also run on datetime objects, so don't
951 # subclass this to test datetimes too.
952
953 def test_delta_non_days_ignored(self):
954 dt = date(2000, 1, 2)
955 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
956 microseconds=5)
957 days = timedelta(delta.days)
958 self.assertEqual(days, timedelta(1))
959
960 dt2 = dt + delta
961 self.assertEqual(dt2, dt + days)
962
963 dt2 = delta + dt
964 self.assertEqual(dt2, dt + days)
965
966 dt2 = dt - delta
967 self.assertEqual(dt2, dt - days)
968
969 delta = -delta
970 days = timedelta(delta.days)
971 self.assertEqual(days, timedelta(-2))
972
973 dt2 = dt + delta
974 self.assertEqual(dt2, dt + days)
975
976 dt2 = delta + dt
977 self.assertEqual(dt2, dt + days)
978
979 dt2 = dt - delta
980 self.assertEqual(dt2, dt - days)
981
982class SubclassDate(date):
983 sub_var = 1
984
985class TestDate(HarmlessMixedComparison, unittest.TestCase):
986 # Tests here should pass for both dates and datetimes, except for a
987 # few tests that TestDateTime overrides.
988
989 theclass = date
990
991 def test_basic_attributes(self):
992 dt = self.theclass(2002, 3, 1)
993 self.assertEqual(dt.year, 2002)
994 self.assertEqual(dt.month, 3)
995 self.assertEqual(dt.day, 1)
996
997 def test_roundtrip(self):
998 for dt in (self.theclass(1, 2, 3),
999 self.theclass.today()):
1000 # Verify dt -> string -> date identity.
1001 s = repr(dt)
1002 self.assertTrue(s.startswith('datetime.'))
1003 s = s[9:]
1004 dt2 = eval(s)
1005 self.assertEqual(dt, dt2)
1006
1007 # Verify identity via reconstructing from pieces.
1008 dt2 = self.theclass(dt.year, dt.month, dt.day)
1009 self.assertEqual(dt, dt2)
1010
1011 def test_ordinal_conversions(self):
1012 # Check some fixed values.
1013 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
1014 (1, 12, 31, 365),
1015 (2, 1, 1, 366),
1016 # first example from "Calendrical Calculations"
1017 (1945, 11, 12, 710347)]:
1018 d = self.theclass(y, m, d)
1019 self.assertEqual(n, d.toordinal())
1020 fromord = self.theclass.fromordinal(n)
1021 self.assertEqual(d, fromord)
1022 if hasattr(fromord, "hour"):
1023 # if we're checking something fancier than a date, verify
1024 # the extra fields have been zeroed out
1025 self.assertEqual(fromord.hour, 0)
1026 self.assertEqual(fromord.minute, 0)
1027 self.assertEqual(fromord.second, 0)
1028 self.assertEqual(fromord.microsecond, 0)
1029
1030 # Check first and last days of year spottily across the whole
1031 # range of years supported.
1032 for year in range(MINYEAR, MAXYEAR+1, 7):
1033 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
1034 d = self.theclass(year, 1, 1)
1035 n = d.toordinal()
1036 d2 = self.theclass.fromordinal(n)
1037 self.assertEqual(d, d2)
1038 # Verify that moving back a day gets to the end of year-1.
1039 if year > 1:
1040 d = self.theclass.fromordinal(n-1)
1041 d2 = self.theclass(year-1, 12, 31)
1042 self.assertEqual(d, d2)
1043 self.assertEqual(d2.toordinal(), n-1)
1044
1045 # Test every day in a leap-year and a non-leap year.
1046 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
1047 for year, isleap in (2000, True), (2002, False):
1048 n = self.theclass(year, 1, 1).toordinal()
1049 for month, maxday in zip(range(1, 13), dim):
1050 if month == 2 and isleap:
1051 maxday += 1
1052 for day in range(1, maxday+1):
1053 d = self.theclass(year, month, day)
1054 self.assertEqual(d.toordinal(), n)
1055 self.assertEqual(d, self.theclass.fromordinal(n))
1056 n += 1
1057
1058 def test_extreme_ordinals(self):
1059 a = self.theclass.min
1060 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
1061 aord = a.toordinal()
1062 b = a.fromordinal(aord)
1063 self.assertEqual(a, b)
1064
1065 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
1066
1067 b = a + timedelta(days=1)
1068 self.assertEqual(b.toordinal(), aord + 1)
1069 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
1070
1071 a = self.theclass.max
1072 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
1073 aord = a.toordinal()
1074 b = a.fromordinal(aord)
1075 self.assertEqual(a, b)
1076
1077 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
1078
1079 b = a - timedelta(days=1)
1080 self.assertEqual(b.toordinal(), aord - 1)
1081 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
1082
1083 def test_bad_constructor_arguments(self):
1084 # bad years
1085 self.theclass(MINYEAR, 1, 1) # no exception
1086 self.theclass(MAXYEAR, 1, 1) # no exception
1087 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1088 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1089 # bad months
1090 self.theclass(2000, 1, 1) # no exception
1091 self.theclass(2000, 12, 1) # no exception
1092 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1093 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1094 # bad days
1095 self.theclass(2000, 2, 29) # no exception
1096 self.theclass(2004, 2, 29) # no exception
1097 self.theclass(2400, 2, 29) # no exception
1098 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1099 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1100 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1101 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1102 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1103 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1104
1105 def test_hash_equality(self):
1106 d = self.theclass(2000, 12, 31)
1107 # same thing
1108 e = self.theclass(2000, 12, 31)
1109 self.assertEqual(d, e)
1110 self.assertEqual(hash(d), hash(e))
1111
1112 dic = {d: 1}
1113 dic[e] = 2
1114 self.assertEqual(len(dic), 1)
1115 self.assertEqual(dic[d], 2)
1116 self.assertEqual(dic[e], 2)
1117
1118 d = self.theclass(2001, 1, 1)
1119 # same thing
1120 e = self.theclass(2001, 1, 1)
1121 self.assertEqual(d, e)
1122 self.assertEqual(hash(d), hash(e))
1123
1124 dic = {d: 1}
1125 dic[e] = 2
1126 self.assertEqual(len(dic), 1)
1127 self.assertEqual(dic[d], 2)
1128 self.assertEqual(dic[e], 2)
1129
1130 def test_computations(self):
1131 a = self.theclass(2002, 1, 31)
1132 b = self.theclass(1956, 1, 31)
1133 c = self.theclass(2001,2,1)
1134
1135 diff = a-b
1136 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1137 self.assertEqual(diff.seconds, 0)
1138 self.assertEqual(diff.microseconds, 0)
1139
1140 day = timedelta(1)
1141 week = timedelta(7)
1142 a = self.theclass(2002, 3, 2)
1143 self.assertEqual(a + day, self.theclass(2002, 3, 3))
1144 self.assertEqual(day + a, self.theclass(2002, 3, 3))
1145 self.assertEqual(a - day, self.theclass(2002, 3, 1))
1146 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
1147 self.assertEqual(a + week, self.theclass(2002, 3, 9))
1148 self.assertEqual(a - week, self.theclass(2002, 2, 23))
1149 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
1150 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
1151 self.assertEqual((a + week) - a, week)
1152 self.assertEqual((a + day) - a, day)
1153 self.assertEqual((a - week) - a, -week)
1154 self.assertEqual((a - day) - a, -day)
1155 self.assertEqual(a - (a + week), -week)
1156 self.assertEqual(a - (a + day), -day)
1157 self.assertEqual(a - (a - week), week)
1158 self.assertEqual(a - (a - day), day)
1159 self.assertEqual(c - (c - day), day)
1160
1161 # Add/sub ints or floats should be illegal
1162 for i in 1, 1.0:
1163 self.assertRaises(TypeError, lambda: a+i)
1164 self.assertRaises(TypeError, lambda: a-i)
1165 self.assertRaises(TypeError, lambda: i+a)
1166 self.assertRaises(TypeError, lambda: i-a)
1167
1168 # delta - date is senseless.
1169 self.assertRaises(TypeError, lambda: day - a)
1170 # mixing date and (delta or date) via * or // is senseless
1171 self.assertRaises(TypeError, lambda: day * a)
1172 self.assertRaises(TypeError, lambda: a * day)
1173 self.assertRaises(TypeError, lambda: day // a)
1174 self.assertRaises(TypeError, lambda: a // day)
1175 self.assertRaises(TypeError, lambda: a * a)
1176 self.assertRaises(TypeError, lambda: a // a)
1177 # date + date is senseless
1178 self.assertRaises(TypeError, lambda: a + a)
1179
1180 def test_overflow(self):
1181 tiny = self.theclass.resolution
1182
1183 for delta in [tiny, timedelta(1), timedelta(2)]:
1184 dt = self.theclass.min + delta
1185 dt -= delta # no problem
1186 self.assertRaises(OverflowError, dt.__sub__, delta)
1187 self.assertRaises(OverflowError, dt.__add__, -delta)
1188
1189 dt = self.theclass.max - delta
1190 dt += delta # no problem
1191 self.assertRaises(OverflowError, dt.__add__, delta)
1192 self.assertRaises(OverflowError, dt.__sub__, -delta)
1193
1194 def test_fromtimestamp(self):
1195 import time
1196
1197 # Try an arbitrary fixed value.
1198 year, month, day = 1999, 9, 19
1199 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
1200 d = self.theclass.fromtimestamp(ts)
1201 self.assertEqual(d.year, year)
1202 self.assertEqual(d.month, month)
1203 self.assertEqual(d.day, day)
1204
1205 def test_insane_fromtimestamp(self):
1206 # It's possible that some platform maps time_t to double,
1207 # and that this test will fail there. This test should
1208 # exempt such platforms (provided they return reasonable
1209 # results!).
1210 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001211 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001212 insane)
1213
1214 def test_today(self):
1215 import time
1216
1217 # We claim that today() is like fromtimestamp(time.time()), so
1218 # prove it.
1219 for dummy in range(3):
1220 today = self.theclass.today()
1221 ts = time.time()
1222 todayagain = self.theclass.fromtimestamp(ts)
1223 if today == todayagain:
1224 break
1225 # There are several legit reasons that could fail:
1226 # 1. It recently became midnight, between the today() and the
1227 # time() calls.
1228 # 2. The platform time() has such fine resolution that we'll
1229 # never get the same value twice.
1230 # 3. The platform time() has poor resolution, and we just
1231 # happened to call today() right before a resolution quantum
1232 # boundary.
1233 # 4. The system clock got fiddled between calls.
1234 # In any case, wait a little while and try again.
1235 time.sleep(0.1)
1236
1237 # It worked or it didn't. If it didn't, assume it's reason #2, and
1238 # let the test pass if they're within half a second of each other.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001239 if today != todayagain:
1240 self.assertAlmostEqual(todayagain, today,
1241 delta=timedelta(seconds=0.5))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001242
1243 def test_weekday(self):
1244 for i in range(7):
1245 # March 4, 2002 is a Monday
1246 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
1247 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
1248 # January 2, 1956 is a Monday
1249 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
1250 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
1251
1252 def test_isocalendar(self):
1253 # Check examples from
1254 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1255 for i in range(7):
1256 d = self.theclass(2003, 12, 22+i)
1257 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
1258 d = self.theclass(2003, 12, 29) + timedelta(i)
1259 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
1260 d = self.theclass(2004, 1, 5+i)
1261 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
1262 d = self.theclass(2009, 12, 21+i)
1263 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
1264 d = self.theclass(2009, 12, 28) + timedelta(i)
1265 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
1266 d = self.theclass(2010, 1, 4+i)
1267 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
1268
1269 def test_iso_long_years(self):
1270 # Calculate long ISO years and compare to table from
1271 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1272 ISO_LONG_YEARS_TABLE = """
1273 4 32 60 88
1274 9 37 65 93
1275 15 43 71 99
1276 20 48 76
1277 26 54 82
1278
1279 105 133 161 189
1280 111 139 167 195
1281 116 144 172
1282 122 150 178
1283 128 156 184
1284
1285 201 229 257 285
1286 207 235 263 291
1287 212 240 268 296
1288 218 246 274
1289 224 252 280
1290
1291 303 331 359 387
1292 308 336 364 392
1293 314 342 370 398
1294 320 348 376
1295 325 353 381
1296 """
1297 iso_long_years = sorted(map(int, ISO_LONG_YEARS_TABLE.split()))
1298 L = []
1299 for i in range(400):
1300 d = self.theclass(2000+i, 12, 31)
1301 d1 = self.theclass(1600+i, 12, 31)
1302 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
1303 if d.isocalendar()[1] == 53:
1304 L.append(i)
1305 self.assertEqual(L, iso_long_years)
1306
1307 def test_isoformat(self):
1308 t = self.theclass(2, 3, 2)
1309 self.assertEqual(t.isoformat(), "0002-03-02")
1310
1311 def test_ctime(self):
1312 t = self.theclass(2002, 3, 2)
1313 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
1314
1315 def test_strftime(self):
1316 t = self.theclass(2005, 3, 2)
1317 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
1318 self.assertEqual(t.strftime(""), "") # SF bug #761337
1319 self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784
1320
1321 self.assertRaises(TypeError, t.strftime) # needs an arg
1322 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
1323 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
1324
1325 # test that unicode input is allowed (issue 2782)
1326 self.assertEqual(t.strftime("%m"), "03")
1327
1328 # A naive object replaces %z and %Z w/ empty strings.
1329 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1330
1331 #make sure that invalid format specifiers are handled correctly
1332 #self.assertRaises(ValueError, t.strftime, "%e")
1333 #self.assertRaises(ValueError, t.strftime, "%")
1334 #self.assertRaises(ValueError, t.strftime, "%#")
1335
1336 #oh well, some systems just ignore those invalid ones.
Martin Panter46f50722016-05-26 05:35:26 +00001337 #at least, exercise them to make sure that no crashes
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001338 #are generated
1339 for f in ["%e", "%", "%#"]:
1340 try:
1341 t.strftime(f)
1342 except ValueError:
1343 pass
1344
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03001345 # bpo-34482: Check that surrogates don't cause a crash.
1346 try:
1347 t.strftime('%y\ud800%m')
1348 except UnicodeEncodeError:
1349 pass
1350
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001351 #check that this standard extension works
1352 t.strftime("%f")
1353
MichaelSaah454b3d42019-01-14 05:23:39 -05001354 def test_strftime_trailing_percent(self):
1355 # bpo-35066: make sure trailing '%' doesn't cause
1356 # datetime's strftime to complain
1357 t = self.theclass(2005, 3, 2)
1358 try:
1359 _time.strftime('%')
1360 except ValueError:
1361 self.skipTest('time module does not support trailing %')
1362 self.assertEqual(t.strftime('%'), '%')
1363 self.assertEqual(t.strftime("m:%m d:%d y:%y %"), "m:03 d:02 y:05 %")
1364
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001365 def test_format(self):
1366 dt = self.theclass(2007, 9, 10)
1367 self.assertEqual(dt.__format__(''), str(dt))
1368
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02001369 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001370 dt.__format__(123)
1371
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001372 # check that a derived class's __str__() gets called
1373 class A(self.theclass):
1374 def __str__(self):
1375 return 'A'
1376 a = A(2007, 9, 10)
1377 self.assertEqual(a.__format__(''), 'A')
1378
1379 # check that a derived class's strftime gets called
1380 class B(self.theclass):
1381 def strftime(self, format_spec):
1382 return 'B'
1383 b = B(2007, 9, 10)
1384 self.assertEqual(b.__format__(''), str(dt))
1385
1386 for fmt in ["m:%m d:%d y:%y",
1387 "m:%m d:%d y:%y H:%H M:%M S:%S",
1388 "%z %Z",
1389 ]:
1390 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1391 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1392 self.assertEqual(b.__format__(fmt), 'B')
1393
1394 def test_resolution_info(self):
1395 # XXX: Should min and max respect subclassing?
1396 if issubclass(self.theclass, datetime):
1397 expected_class = datetime
1398 else:
1399 expected_class = date
1400 self.assertIsInstance(self.theclass.min, expected_class)
1401 self.assertIsInstance(self.theclass.max, expected_class)
1402 self.assertIsInstance(self.theclass.resolution, timedelta)
1403 self.assertTrue(self.theclass.max > self.theclass.min)
1404
1405 def test_extreme_timedelta(self):
1406 big = self.theclass.max - self.theclass.min
1407 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
1408 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
1409 # n == 315537897599999999 ~= 2**58.13
1410 justasbig = timedelta(0, 0, n)
1411 self.assertEqual(big, justasbig)
1412 self.assertEqual(self.theclass.min + big, self.theclass.max)
1413 self.assertEqual(self.theclass.max - big, self.theclass.min)
1414
1415 def test_timetuple(self):
1416 for i in range(7):
1417 # January 2, 1956 is a Monday (0)
1418 d = self.theclass(1956, 1, 2+i)
1419 t = d.timetuple()
1420 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
1421 # February 1, 1956 is a Wednesday (2)
1422 d = self.theclass(1956, 2, 1+i)
1423 t = d.timetuple()
1424 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
1425 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
1426 # of the year.
1427 d = self.theclass(1956, 3, 1+i)
1428 t = d.timetuple()
1429 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
1430 self.assertEqual(t.tm_year, 1956)
1431 self.assertEqual(t.tm_mon, 3)
1432 self.assertEqual(t.tm_mday, 1+i)
1433 self.assertEqual(t.tm_hour, 0)
1434 self.assertEqual(t.tm_min, 0)
1435 self.assertEqual(t.tm_sec, 0)
1436 self.assertEqual(t.tm_wday, (3+i)%7)
1437 self.assertEqual(t.tm_yday, 61+i)
1438 self.assertEqual(t.tm_isdst, -1)
1439
1440 def test_pickling(self):
1441 args = 6, 7, 23
1442 orig = self.theclass(*args)
1443 for pickler, unpickler, proto in pickle_choices:
1444 green = pickler.dumps(orig, proto)
1445 derived = unpickler.loads(green)
1446 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02001447 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001448
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02001449 def test_compat_unpickle(self):
1450 tests = [
1451 b"cdatetime\ndate\n(S'\\x07\\xdf\\x0b\\x1b'\ntR.",
1452 b'cdatetime\ndate\n(U\x04\x07\xdf\x0b\x1btR.',
1453 b'\x80\x02cdatetime\ndate\nU\x04\x07\xdf\x0b\x1b\x85R.',
1454 ]
1455 args = 2015, 11, 27
1456 expected = self.theclass(*args)
1457 for data in tests:
1458 for loads in pickle_loads:
1459 derived = loads(data, encoding='latin1')
1460 self.assertEqual(derived, expected)
1461
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001462 def test_compare(self):
1463 t1 = self.theclass(2, 3, 4)
1464 t2 = self.theclass(2, 3, 4)
1465 self.assertEqual(t1, t2)
1466 self.assertTrue(t1 <= t2)
1467 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001468 self.assertFalse(t1 != t2)
1469 self.assertFalse(t1 < t2)
1470 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001471
1472 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
1473 t2 = self.theclass(*args) # this is larger than t1
1474 self.assertTrue(t1 < t2)
1475 self.assertTrue(t2 > t1)
1476 self.assertTrue(t1 <= t2)
1477 self.assertTrue(t2 >= t1)
1478 self.assertTrue(t1 != t2)
1479 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001480 self.assertFalse(t1 == t2)
1481 self.assertFalse(t2 == t1)
1482 self.assertFalse(t1 > t2)
1483 self.assertFalse(t2 < t1)
1484 self.assertFalse(t1 >= t2)
1485 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001486
1487 for badarg in OTHERSTUFF:
1488 self.assertEqual(t1 == badarg, False)
1489 self.assertEqual(t1 != badarg, True)
1490 self.assertEqual(badarg == t1, False)
1491 self.assertEqual(badarg != t1, True)
1492
1493 self.assertRaises(TypeError, lambda: t1 < badarg)
1494 self.assertRaises(TypeError, lambda: t1 > badarg)
1495 self.assertRaises(TypeError, lambda: t1 >= badarg)
1496 self.assertRaises(TypeError, lambda: badarg <= t1)
1497 self.assertRaises(TypeError, lambda: badarg < t1)
1498 self.assertRaises(TypeError, lambda: badarg > t1)
1499 self.assertRaises(TypeError, lambda: badarg >= t1)
1500
1501 def test_mixed_compare(self):
1502 our = self.theclass(2000, 4, 5)
1503
1504 # Our class can be compared for equality to other classes
1505 self.assertEqual(our == 1, False)
1506 self.assertEqual(1 == our, False)
1507 self.assertEqual(our != 1, True)
1508 self.assertEqual(1 != our, True)
1509
1510 # But the ordering is undefined
1511 self.assertRaises(TypeError, lambda: our < 1)
1512 self.assertRaises(TypeError, lambda: 1 < our)
1513
1514 # Repeat those tests with a different class
1515
1516 class SomeClass:
1517 pass
1518
1519 their = SomeClass()
1520 self.assertEqual(our == their, False)
1521 self.assertEqual(their == our, False)
1522 self.assertEqual(our != their, True)
1523 self.assertEqual(their != our, True)
1524 self.assertRaises(TypeError, lambda: our < their)
1525 self.assertRaises(TypeError, lambda: their < our)
1526
1527 # However, if the other class explicitly defines ordering
1528 # relative to our class, it is allowed to do so
1529
1530 class LargerThanAnything:
1531 def __lt__(self, other):
1532 return False
1533 def __le__(self, other):
1534 return isinstance(other, LargerThanAnything)
1535 def __eq__(self, other):
1536 return isinstance(other, LargerThanAnything)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001537 def __gt__(self, other):
1538 return not isinstance(other, LargerThanAnything)
1539 def __ge__(self, other):
1540 return True
1541
1542 their = LargerThanAnything()
1543 self.assertEqual(our == their, False)
1544 self.assertEqual(their == our, False)
1545 self.assertEqual(our != their, True)
1546 self.assertEqual(their != our, True)
1547 self.assertEqual(our < their, True)
1548 self.assertEqual(their < our, False)
1549
1550 def test_bool(self):
1551 # All dates are considered true.
1552 self.assertTrue(self.theclass.min)
1553 self.assertTrue(self.theclass.max)
1554
Alexander Belopolsky89da3492011-05-02 13:14:24 -04001555 def test_strftime_y2k(self):
1556 for y in (1, 49, 70, 99, 100, 999, 1000, 1970):
Florent Xicluna49ce0682011-11-01 12:56:14 +01001557 d = self.theclass(y, 1, 1)
1558 # Issue 13305: For years < 1000, the value is not always
1559 # padded to 4 digits across platforms. The C standard
1560 # assumes year >= 1900, so it does not specify the number
1561 # of digits.
1562 if d.strftime("%Y") != '%04d' % y:
1563 # Year 42 returns '42', not padded
1564 self.assertEqual(d.strftime("%Y"), '%d' % y)
Victor Stinner15a83e82016-03-12 08:16:48 +01001565 # '0042' is obtained anyway
1566 self.assertEqual(d.strftime("%4Y"), '%04d' % y)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001567
1568 def test_replace(self):
1569 cls = self.theclass
1570 args = [1, 2, 3]
1571 base = cls(*args)
1572 self.assertEqual(base, base.replace())
1573
1574 i = 0
1575 for name, newval in (("year", 2),
1576 ("month", 3),
1577 ("day", 4)):
1578 newargs = args[:]
1579 newargs[i] = newval
1580 expected = cls(*newargs)
1581 got = base.replace(**{name: newval})
1582 self.assertEqual(expected, got)
1583 i += 1
1584
1585 # Out of bounds.
1586 base = cls(2000, 2, 29)
1587 self.assertRaises(ValueError, base.replace, year=2001)
1588
Paul Ganssle191e9932017-11-09 16:34:29 -05001589 def test_subclass_replace(self):
1590 class DateSubclass(self.theclass):
1591 pass
1592
1593 dt = DateSubclass(2012, 1, 1)
1594 self.assertIs(type(dt.replace(year=2013)), DateSubclass)
1595
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001596 def test_subclass_date(self):
1597
1598 class C(self.theclass):
1599 theAnswer = 42
1600
1601 def __new__(cls, *args, **kws):
1602 temp = kws.copy()
1603 extra = temp.pop('extra')
1604 result = self.theclass.__new__(cls, *args, **temp)
1605 result.extra = extra
1606 return result
1607
1608 def newmeth(self, start):
1609 return start + self.year + self.month
1610
1611 args = 2003, 4, 14
1612
1613 dt1 = self.theclass(*args)
1614 dt2 = C(*args, **{'extra': 7})
1615
1616 self.assertEqual(dt2.__class__, C)
1617 self.assertEqual(dt2.theAnswer, 42)
1618 self.assertEqual(dt2.extra, 7)
1619 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1620 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1621
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05001622 def test_subclass_alternate_constructors(self):
1623 # Test that alternate constructors call the constructor
1624 class DateSubclass(self.theclass):
1625 def __new__(cls, *args, **kwargs):
1626 result = self.theclass.__new__(cls, *args, **kwargs)
1627 result.extra = 7
1628
1629 return result
1630
1631 args = (2003, 4, 14)
1632 d_ord = 731319 # Equivalent ordinal date
1633 d_isoformat = '2003-04-14' # Equivalent isoformat()
1634
1635 base_d = DateSubclass(*args)
1636 self.assertIsInstance(base_d, DateSubclass)
1637 self.assertEqual(base_d.extra, 7)
1638
1639 # Timestamp depends on time zone, so we'll calculate the equivalent here
1640 ts = datetime.combine(base_d, time(0)).timestamp()
1641
1642 test_cases = [
1643 ('fromordinal', (d_ord,)),
1644 ('fromtimestamp', (ts,)),
1645 ('fromisoformat', (d_isoformat,)),
1646 ]
1647
1648 for constr_name, constr_args in test_cases:
1649 for base_obj in (DateSubclass, base_d):
1650 # Test both the classmethod and method
1651 with self.subTest(base_obj_type=type(base_obj),
1652 constr_name=constr_name):
1653 constr = getattr(base_obj, constr_name)
1654
1655 dt = constr(*constr_args)
1656
1657 # Test that it creates the right subclass
1658 self.assertIsInstance(dt, DateSubclass)
1659
1660 # Test that it's equal to the base object
1661 self.assertEqual(dt, base_d)
1662
1663 # Test that it called the constructor
1664 self.assertEqual(dt.extra, 7)
1665
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001666 def test_pickling_subclass_date(self):
1667
1668 args = 6, 7, 23
1669 orig = SubclassDate(*args)
1670 for pickler, unpickler, proto in pickle_choices:
1671 green = pickler.dumps(orig, proto)
1672 derived = unpickler.loads(green)
1673 self.assertEqual(orig, derived)
1674
1675 def test_backdoor_resistance(self):
1676 # For fast unpickling, the constructor accepts a pickle byte string.
1677 # This is a low-overhead backdoor. A user can (by intent or
1678 # mistake) pass a string directly, which (if it's the right length)
1679 # will get treated like a pickle, and bypass the normal sanity
1680 # checks in the constructor. This can create insane objects.
1681 # The constructor doesn't want to burn the time to validate all
1682 # fields, but does check the month field. This stops, e.g.,
1683 # datetime.datetime('1995-03-25') from yielding an insane object.
1684 base = b'1995-03-25'
1685 if not issubclass(self.theclass, datetime):
1686 base = base[:4]
1687 for month_byte in b'9', b'\0', b'\r', b'\xff':
1688 self.assertRaises(TypeError, self.theclass,
1689 base[:2] + month_byte + base[3:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001690 if issubclass(self.theclass, datetime):
1691 # Good bytes, but bad tzinfo:
1692 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
1693 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001694
1695 for ord_byte in range(1, 13):
1696 # This shouldn't blow up because of the month byte alone. If
1697 # the implementation changes to do more-careful checking, it may
1698 # blow up because other fields are insane.
1699 self.theclass(base[:2] + bytes([ord_byte]) + base[3:])
1700
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001701 def test_fromisoformat(self):
1702 # Test that isoformat() is reversible
1703 base_dates = [
1704 (1, 1, 1),
1705 (1000, 2, 14),
1706 (1900, 1, 1),
1707 (2000, 2, 29),
1708 (2004, 11, 12),
1709 (2004, 4, 3),
1710 (2017, 5, 30)
1711 ]
1712
1713 for dt_tuple in base_dates:
1714 dt = self.theclass(*dt_tuple)
1715 dt_str = dt.isoformat()
1716 with self.subTest(dt_str=dt_str):
1717 dt_rt = self.theclass.fromisoformat(dt.isoformat())
1718
1719 self.assertEqual(dt, dt_rt)
1720
1721 def test_fromisoformat_subclass(self):
1722 class DateSubclass(self.theclass):
1723 pass
1724
1725 dt = DateSubclass(2014, 12, 14)
1726
1727 dt_rt = DateSubclass.fromisoformat(dt.isoformat())
1728
1729 self.assertIsInstance(dt_rt, DateSubclass)
1730
1731 def test_fromisoformat_fails(self):
1732 # Test that fromisoformat() fails on invalid values
1733 bad_strs = [
1734 '', # Empty string
Paul Ganssle096329f2018-08-23 11:06:20 -04001735 '\ud800', # bpo-34454: Surrogate code point
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001736 '009-03-04', # Not 10 characters
1737 '123456789', # Not a date
1738 '200a-12-04', # Invalid character in year
1739 '2009-1a-04', # Invalid character in month
1740 '2009-12-0a', # Invalid character in day
1741 '2009-01-32', # Invalid day
1742 '2009-02-29', # Invalid leap day
1743 '20090228', # Valid ISO8601 output not from isoformat()
Paul Ganssle096329f2018-08-23 11:06:20 -04001744 '2009\ud80002\ud80028', # Separators are surrogate codepoints
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001745 ]
1746
1747 for bad_str in bad_strs:
1748 with self.assertRaises(ValueError):
1749 self.theclass.fromisoformat(bad_str)
1750
1751 def test_fromisoformat_fails_typeerror(self):
1752 # Test that fromisoformat fails when passed the wrong type
1753 import io
1754
1755 bad_types = [b'2009-03-01', None, io.StringIO('2009-03-01')]
1756 for bad_type in bad_types:
1757 with self.assertRaises(TypeError):
1758 self.theclass.fromisoformat(bad_type)
1759
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001760#############################################################################
1761# datetime tests
1762
1763class SubclassDatetime(datetime):
1764 sub_var = 1
1765
1766class TestDateTime(TestDate):
1767
1768 theclass = datetime
1769
1770 def test_basic_attributes(self):
1771 dt = self.theclass(2002, 3, 1, 12, 0)
1772 self.assertEqual(dt.year, 2002)
1773 self.assertEqual(dt.month, 3)
1774 self.assertEqual(dt.day, 1)
1775 self.assertEqual(dt.hour, 12)
1776 self.assertEqual(dt.minute, 0)
1777 self.assertEqual(dt.second, 0)
1778 self.assertEqual(dt.microsecond, 0)
1779
1780 def test_basic_attributes_nonzero(self):
1781 # Make sure all attributes are non-zero so bugs in
1782 # bit-shifting access show up.
1783 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1784 self.assertEqual(dt.year, 2002)
1785 self.assertEqual(dt.month, 3)
1786 self.assertEqual(dt.day, 1)
1787 self.assertEqual(dt.hour, 12)
1788 self.assertEqual(dt.minute, 59)
1789 self.assertEqual(dt.second, 59)
1790 self.assertEqual(dt.microsecond, 8000)
1791
1792 def test_roundtrip(self):
1793 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1794 self.theclass.now()):
1795 # Verify dt -> string -> datetime identity.
1796 s = repr(dt)
1797 self.assertTrue(s.startswith('datetime.'))
1798 s = s[9:]
1799 dt2 = eval(s)
1800 self.assertEqual(dt, dt2)
1801
1802 # Verify identity via reconstructing from pieces.
1803 dt2 = self.theclass(dt.year, dt.month, dt.day,
1804 dt.hour, dt.minute, dt.second,
1805 dt.microsecond)
1806 self.assertEqual(dt, dt2)
1807
1808 def test_isoformat(self):
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001809 t = self.theclass(1, 2, 3, 4, 5, 1, 123)
1810 self.assertEqual(t.isoformat(), "0001-02-03T04:05:01.000123")
1811 self.assertEqual(t.isoformat('T'), "0001-02-03T04:05:01.000123")
1812 self.assertEqual(t.isoformat(' '), "0001-02-03 04:05:01.000123")
1813 self.assertEqual(t.isoformat('\x00'), "0001-02-03\x0004:05:01.000123")
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03001814 # bpo-34482: Check that surrogates are handled properly.
1815 self.assertEqual(t.isoformat('\ud800'),
1816 "0001-02-03\ud80004:05:01.000123")
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001817 self.assertEqual(t.isoformat(timespec='hours'), "0001-02-03T04")
1818 self.assertEqual(t.isoformat(timespec='minutes'), "0001-02-03T04:05")
1819 self.assertEqual(t.isoformat(timespec='seconds'), "0001-02-03T04:05:01")
1820 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1821 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000123")
1822 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01.000123")
1823 self.assertEqual(t.isoformat(sep=' ', timespec='minutes'), "0001-02-03 04:05")
1824 self.assertRaises(ValueError, t.isoformat, timespec='foo')
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03001825 # bpo-34482: Check that surrogates are handled properly.
1826 self.assertRaises(ValueError, t.isoformat, timespec='\ud800')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001827 # str is ISO format with the separator forced to a blank.
Alexander Belopolskya2998a62016-03-06 14:58:43 -05001828 self.assertEqual(str(t), "0001-02-03 04:05:01.000123")
1829
1830 t = self.theclass(1, 2, 3, 4, 5, 1, 999500, tzinfo=timezone.utc)
1831 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999+00:00")
1832
1833 t = self.theclass(1, 2, 3, 4, 5, 1, 999500)
1834 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999")
1835
1836 t = self.theclass(1, 2, 3, 4, 5, 1)
1837 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01")
1838 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1839 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000000")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001840
1841 t = self.theclass(2, 3, 2)
1842 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1843 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1844 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1845 # str is ISO format with the separator forced to a blank.
1846 self.assertEqual(str(t), "0002-03-02 00:00:00")
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04001847 # ISO format with timezone
1848 tz = FixedOffset(timedelta(seconds=16), 'XXX')
1849 t = self.theclass(2, 3, 2, tzinfo=tz)
1850 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00+00:00:16")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001851
Paul Ganssle09dc2f52017-12-21 00:33:49 -05001852 def test_isoformat_timezone(self):
1853 tzoffsets = [
1854 ('05:00', timedelta(hours=5)),
1855 ('02:00', timedelta(hours=2)),
1856 ('06:27', timedelta(hours=6, minutes=27)),
1857 ('12:32:30', timedelta(hours=12, minutes=32, seconds=30)),
1858 ('02:04:09.123456', timedelta(hours=2, minutes=4, seconds=9, microseconds=123456))
1859 ]
1860
1861 tzinfos = [
1862 ('', None),
1863 ('+00:00', timezone.utc),
1864 ('+00:00', timezone(timedelta(0))),
1865 ]
1866
1867 tzinfos += [
1868 (prefix + expected, timezone(sign * td))
1869 for expected, td in tzoffsets
1870 for prefix, sign in [('-', -1), ('+', 1)]
1871 ]
1872
1873 dt_base = self.theclass(2016, 4, 1, 12, 37, 9)
1874 exp_base = '2016-04-01T12:37:09'
1875
1876 for exp_tz, tzi in tzinfos:
1877 dt = dt_base.replace(tzinfo=tzi)
1878 exp = exp_base + exp_tz
1879 with self.subTest(tzi=tzi):
1880 assert dt.isoformat() == exp
1881
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001882 def test_format(self):
1883 dt = self.theclass(2007, 9, 10, 4, 5, 1, 123)
1884 self.assertEqual(dt.__format__(''), str(dt))
1885
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02001886 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001887 dt.__format__(123)
1888
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001889 # check that a derived class's __str__() gets called
1890 class A(self.theclass):
1891 def __str__(self):
1892 return 'A'
1893 a = A(2007, 9, 10, 4, 5, 1, 123)
1894 self.assertEqual(a.__format__(''), 'A')
1895
1896 # check that a derived class's strftime gets called
1897 class B(self.theclass):
1898 def strftime(self, format_spec):
1899 return 'B'
1900 b = B(2007, 9, 10, 4, 5, 1, 123)
1901 self.assertEqual(b.__format__(''), str(dt))
1902
1903 for fmt in ["m:%m d:%d y:%y",
1904 "m:%m d:%d y:%y H:%H M:%M S:%S",
1905 "%z %Z",
1906 ]:
1907 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1908 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1909 self.assertEqual(b.__format__(fmt), 'B')
1910
1911 def test_more_ctime(self):
1912 # Test fields that TestDate doesn't touch.
1913 import time
1914
1915 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1916 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1917 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1918 # out. The difference is that t.ctime() produces " 2" for the day,
1919 # but platform ctime() produces "02" for the day. According to
1920 # C99, t.ctime() is correct here.
1921 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1922
1923 # So test a case where that difference doesn't matter.
1924 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1925 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1926
1927 def test_tz_independent_comparing(self):
1928 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1929 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1930 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1931 self.assertEqual(dt1, dt3)
1932 self.assertTrue(dt2 > dt3)
1933
1934 # Make sure comparison doesn't forget microseconds, and isn't done
1935 # via comparing a float timestamp (an IEEE double doesn't have enough
Leo Ariasc3d95082018-02-03 18:36:10 -06001936 # precision to span microsecond resolution across years 1 through 9999,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001937 # so comparing via timestamp necessarily calls some distinct values
1938 # equal).
1939 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1940 us = timedelta(microseconds=1)
1941 dt2 = dt1 + us
1942 self.assertEqual(dt2 - dt1, us)
1943 self.assertTrue(dt1 < dt2)
1944
1945 def test_strftime_with_bad_tzname_replace(self):
1946 # verify ok if tzinfo.tzname().replace() returns a non-string
1947 class MyTzInfo(FixedOffset):
1948 def tzname(self, dt):
1949 class MyStr(str):
1950 def replace(self, *args):
1951 return None
1952 return MyStr('name')
1953 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
1954 self.assertRaises(TypeError, t.strftime, '%Z')
1955
1956 def test_bad_constructor_arguments(self):
1957 # bad years
1958 self.theclass(MINYEAR, 1, 1) # no exception
1959 self.theclass(MAXYEAR, 1, 1) # no exception
1960 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1961 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1962 # bad months
1963 self.theclass(2000, 1, 1) # no exception
1964 self.theclass(2000, 12, 1) # no exception
1965 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1966 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1967 # bad days
1968 self.theclass(2000, 2, 29) # no exception
1969 self.theclass(2004, 2, 29) # no exception
1970 self.theclass(2400, 2, 29) # no exception
1971 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1972 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1973 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1974 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1975 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1976 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1977 # bad hours
1978 self.theclass(2000, 1, 31, 0) # no exception
1979 self.theclass(2000, 1, 31, 23) # no exception
1980 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1981 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1982 # bad minutes
1983 self.theclass(2000, 1, 31, 23, 0) # no exception
1984 self.theclass(2000, 1, 31, 23, 59) # no exception
1985 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1986 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1987 # bad seconds
1988 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1989 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1990 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1991 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1992 # bad microseconds
1993 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1994 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1995 self.assertRaises(ValueError, self.theclass,
1996 2000, 1, 31, 23, 59, 59, -1)
1997 self.assertRaises(ValueError, self.theclass,
1998 2000, 1, 31, 23, 59, 59,
1999 1000000)
Alexander Belopolsky47649ab2016-08-08 17:05:40 -04002000 # bad fold
2001 self.assertRaises(ValueError, self.theclass,
2002 2000, 1, 31, fold=-1)
2003 self.assertRaises(ValueError, self.theclass,
2004 2000, 1, 31, fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002005 # Positional fold:
2006 self.assertRaises(TypeError, self.theclass,
2007 2000, 1, 31, 23, 59, 59, 0, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04002008
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002009 def test_hash_equality(self):
2010 d = self.theclass(2000, 12, 31, 23, 30, 17)
2011 e = self.theclass(2000, 12, 31, 23, 30, 17)
2012 self.assertEqual(d, e)
2013 self.assertEqual(hash(d), hash(e))
2014
2015 dic = {d: 1}
2016 dic[e] = 2
2017 self.assertEqual(len(dic), 1)
2018 self.assertEqual(dic[d], 2)
2019 self.assertEqual(dic[e], 2)
2020
2021 d = self.theclass(2001, 1, 1, 0, 5, 17)
2022 e = self.theclass(2001, 1, 1, 0, 5, 17)
2023 self.assertEqual(d, e)
2024 self.assertEqual(hash(d), hash(e))
2025
2026 dic = {d: 1}
2027 dic[e] = 2
2028 self.assertEqual(len(dic), 1)
2029 self.assertEqual(dic[d], 2)
2030 self.assertEqual(dic[e], 2)
2031
2032 def test_computations(self):
2033 a = self.theclass(2002, 1, 31)
2034 b = self.theclass(1956, 1, 31)
2035 diff = a-b
2036 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
2037 self.assertEqual(diff.seconds, 0)
2038 self.assertEqual(diff.microseconds, 0)
2039 a = self.theclass(2002, 3, 2, 17, 6)
2040 millisec = timedelta(0, 0, 1000)
2041 hour = timedelta(0, 3600)
2042 day = timedelta(1)
2043 week = timedelta(7)
2044 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
2045 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
2046 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
2047 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
2048 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
2049 self.assertEqual(a - hour, a + -hour)
2050 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
2051 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
2052 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
2053 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
2054 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
2055 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
2056 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
2057 self.assertEqual((a + week) - a, week)
2058 self.assertEqual((a + day) - a, day)
2059 self.assertEqual((a + hour) - a, hour)
2060 self.assertEqual((a + millisec) - a, millisec)
2061 self.assertEqual((a - week) - a, -week)
2062 self.assertEqual((a - day) - a, -day)
2063 self.assertEqual((a - hour) - a, -hour)
2064 self.assertEqual((a - millisec) - a, -millisec)
2065 self.assertEqual(a - (a + week), -week)
2066 self.assertEqual(a - (a + day), -day)
2067 self.assertEqual(a - (a + hour), -hour)
2068 self.assertEqual(a - (a + millisec), -millisec)
2069 self.assertEqual(a - (a - week), week)
2070 self.assertEqual(a - (a - day), day)
2071 self.assertEqual(a - (a - hour), hour)
2072 self.assertEqual(a - (a - millisec), millisec)
2073 self.assertEqual(a + (week + day + hour + millisec),
2074 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
2075 self.assertEqual(a + (week + day + hour + millisec),
2076 (((a + week) + day) + hour) + millisec)
2077 self.assertEqual(a - (week + day + hour + millisec),
2078 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
2079 self.assertEqual(a - (week + day + hour + millisec),
2080 (((a - week) - day) - hour) - millisec)
2081 # Add/sub ints or floats should be illegal
2082 for i in 1, 1.0:
2083 self.assertRaises(TypeError, lambda: a+i)
2084 self.assertRaises(TypeError, lambda: a-i)
2085 self.assertRaises(TypeError, lambda: i+a)
2086 self.assertRaises(TypeError, lambda: i-a)
2087
2088 # delta - datetime is senseless.
2089 self.assertRaises(TypeError, lambda: day - a)
2090 # mixing datetime and (delta or datetime) via * or // is senseless
2091 self.assertRaises(TypeError, lambda: day * a)
2092 self.assertRaises(TypeError, lambda: a * day)
2093 self.assertRaises(TypeError, lambda: day // a)
2094 self.assertRaises(TypeError, lambda: a // day)
2095 self.assertRaises(TypeError, lambda: a * a)
2096 self.assertRaises(TypeError, lambda: a // a)
2097 # datetime + datetime is senseless
2098 self.assertRaises(TypeError, lambda: a + a)
2099
2100 def test_pickling(self):
2101 args = 6, 7, 23, 20, 59, 1, 64**2
2102 orig = self.theclass(*args)
2103 for pickler, unpickler, proto in pickle_choices:
2104 green = pickler.dumps(orig, proto)
2105 derived = unpickler.loads(green)
2106 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02002107 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002108
2109 def test_more_pickling(self):
2110 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
Serhiy Storchakabad12572014-12-15 14:03:42 +02002111 for proto in range(pickle.HIGHEST_PROTOCOL + 1):
2112 s = pickle.dumps(a, proto)
2113 b = pickle.loads(s)
2114 self.assertEqual(b.year, 2003)
2115 self.assertEqual(b.month, 2)
2116 self.assertEqual(b.day, 7)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002117
2118 def test_pickling_subclass_datetime(self):
2119 args = 6, 7, 23, 20, 59, 1, 64**2
2120 orig = SubclassDatetime(*args)
2121 for pickler, unpickler, proto in pickle_choices:
2122 green = pickler.dumps(orig, proto)
2123 derived = unpickler.loads(green)
2124 self.assertEqual(orig, derived)
2125
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02002126 def test_compat_unpickle(self):
2127 tests = [
2128 b'cdatetime\ndatetime\n('
2129 b"S'\\x07\\xdf\\x0b\\x1b\\x14;\\x01\\x00\\x10\\x00'\ntR.",
2130
2131 b'cdatetime\ndatetime\n('
2132 b'U\n\x07\xdf\x0b\x1b\x14;\x01\x00\x10\x00tR.',
2133
2134 b'\x80\x02cdatetime\ndatetime\n'
2135 b'U\n\x07\xdf\x0b\x1b\x14;\x01\x00\x10\x00\x85R.',
2136 ]
2137 args = 2015, 11, 27, 20, 59, 1, 64**2
2138 expected = self.theclass(*args)
2139 for data in tests:
2140 for loads in pickle_loads:
2141 derived = loads(data, encoding='latin1')
2142 self.assertEqual(derived, expected)
2143
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002144 def test_more_compare(self):
2145 # The test_compare() inherited from TestDate covers the error cases.
2146 # We just want to test lexicographic ordering on the members datetime
2147 # has that date lacks.
2148 args = [2000, 11, 29, 20, 58, 16, 999998]
2149 t1 = self.theclass(*args)
2150 t2 = self.theclass(*args)
2151 self.assertEqual(t1, t2)
2152 self.assertTrue(t1 <= t2)
2153 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002154 self.assertFalse(t1 != t2)
2155 self.assertFalse(t1 < t2)
2156 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002157
2158 for i in range(len(args)):
2159 newargs = args[:]
2160 newargs[i] = args[i] + 1
2161 t2 = self.theclass(*newargs) # this is larger than t1
2162 self.assertTrue(t1 < t2)
2163 self.assertTrue(t2 > t1)
2164 self.assertTrue(t1 <= t2)
2165 self.assertTrue(t2 >= t1)
2166 self.assertTrue(t1 != t2)
2167 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002168 self.assertFalse(t1 == t2)
2169 self.assertFalse(t2 == t1)
2170 self.assertFalse(t1 > t2)
2171 self.assertFalse(t2 < t1)
2172 self.assertFalse(t1 >= t2)
2173 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002174
2175
2176 # A helper for timestamp constructor tests.
2177 def verify_field_equality(self, expected, got):
2178 self.assertEqual(expected.tm_year, got.year)
2179 self.assertEqual(expected.tm_mon, got.month)
2180 self.assertEqual(expected.tm_mday, got.day)
2181 self.assertEqual(expected.tm_hour, got.hour)
2182 self.assertEqual(expected.tm_min, got.minute)
2183 self.assertEqual(expected.tm_sec, got.second)
2184
2185 def test_fromtimestamp(self):
2186 import time
2187
2188 ts = time.time()
2189 expected = time.localtime(ts)
2190 got = self.theclass.fromtimestamp(ts)
2191 self.verify_field_equality(expected, got)
2192
2193 def test_utcfromtimestamp(self):
2194 import time
2195
2196 ts = time.time()
2197 expected = time.gmtime(ts)
2198 got = self.theclass.utcfromtimestamp(ts)
2199 self.verify_field_equality(expected, got)
2200
Alexander Belopolskya4415142012-06-08 12:33:09 -04002201 # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in
2202 # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0).
2203 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
2204 def test_timestamp_naive(self):
2205 t = self.theclass(1970, 1, 1)
2206 self.assertEqual(t.timestamp(), 18000.0)
2207 t = self.theclass(1970, 1, 1, 1, 2, 3, 4)
2208 self.assertEqual(t.timestamp(),
2209 18000.0 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002210 # Missing hour
2211 t0 = self.theclass(2012, 3, 11, 2, 30)
2212 t1 = t0.replace(fold=1)
2213 self.assertEqual(self.theclass.fromtimestamp(t1.timestamp()),
2214 t0 - timedelta(hours=1))
2215 self.assertEqual(self.theclass.fromtimestamp(t0.timestamp()),
2216 t1 + timedelta(hours=1))
Alexander Belopolskya4415142012-06-08 12:33:09 -04002217 # Ambiguous hour defaults to DST
2218 t = self.theclass(2012, 11, 4, 1, 30)
2219 self.assertEqual(self.theclass.fromtimestamp(t.timestamp()), t)
2220
2221 # Timestamp may raise an overflow error on some platforms
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002222 # XXX: Do we care to support the first and last year?
2223 for t in [self.theclass(2,1,1), self.theclass(9998,12,12)]:
Alexander Belopolskya4415142012-06-08 12:33:09 -04002224 try:
2225 s = t.timestamp()
2226 except OverflowError:
2227 pass
2228 else:
2229 self.assertEqual(self.theclass.fromtimestamp(s), t)
2230
2231 def test_timestamp_aware(self):
2232 t = self.theclass(1970, 1, 1, tzinfo=timezone.utc)
2233 self.assertEqual(t.timestamp(), 0.0)
2234 t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone.utc)
2235 self.assertEqual(t.timestamp(),
2236 3600 + 2*60 + 3 + 4*1e-6)
2237 t = self.theclass(1970, 1, 1, 1, 2, 3, 4,
2238 tzinfo=timezone(timedelta(hours=-5), 'EST'))
2239 self.assertEqual(t.timestamp(),
2240 18000 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002241
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002242 @support.run_with_tz('MSK-03') # Something east of Greenwich
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002243 def test_microsecond_rounding(self):
Victor Stinner5ebfe422015-09-18 14:52:15 +02002244 for fts in [self.theclass.fromtimestamp,
2245 self.theclass.utcfromtimestamp]:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002246 zero = fts(0)
2247 self.assertEqual(zero.second, 0)
2248 self.assertEqual(zero.microsecond, 0)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002249 one = fts(1e-6)
Victor Stinner8050ca92012-03-14 00:17:05 +01002250 try:
2251 minus_one = fts(-1e-6)
2252 except OSError:
2253 # localtime(-1) and gmtime(-1) is not supported on Windows
2254 pass
2255 else:
2256 self.assertEqual(minus_one.second, 59)
2257 self.assertEqual(minus_one.microsecond, 999999)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002258
Victor Stinner8050ca92012-03-14 00:17:05 +01002259 t = fts(-1e-8)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002260 self.assertEqual(t, zero)
Victor Stinner8050ca92012-03-14 00:17:05 +01002261 t = fts(-9e-7)
2262 self.assertEqual(t, minus_one)
2263 t = fts(-1e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002264 self.assertEqual(t, zero)
Victor Stinner8820a352015-09-05 10:50:20 +02002265 t = fts(-1/2**7)
2266 self.assertEqual(t.second, 59)
Victor Stinner7667f582015-09-09 01:02:23 +02002267 self.assertEqual(t.microsecond, 992188)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002268
2269 t = fts(1e-7)
2270 self.assertEqual(t, zero)
2271 t = fts(9e-7)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002272 self.assertEqual(t, one)
Victor Stinner5d272cc2012-03-13 13:35:55 +01002273 t = fts(0.99999949)
2274 self.assertEqual(t.second, 0)
2275 self.assertEqual(t.microsecond, 999999)
2276 t = fts(0.9999999)
Victor Stinner2ec5bd62015-09-03 09:06:44 +02002277 self.assertEqual(t.second, 1)
2278 self.assertEqual(t.microsecond, 0)
Victor Stinneradfefa52015-09-04 23:57:25 +02002279 t = fts(1/2**7)
2280 self.assertEqual(t.second, 0)
Victor Stinner7667f582015-09-09 01:02:23 +02002281 self.assertEqual(t.microsecond, 7812)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002282
Victor Stinnerb67f0962017-02-10 10:34:02 +01002283 def test_timestamp_limits(self):
2284 # minimum timestamp
2285 min_dt = self.theclass.min.replace(tzinfo=timezone.utc)
2286 min_ts = min_dt.timestamp()
Victor Stinner6f37e362017-02-10 11:45:14 +01002287 try:
2288 # date 0001-01-01 00:00:00+00:00: timestamp=-62135596800
2289 self.assertEqual(self.theclass.fromtimestamp(min_ts, tz=timezone.utc),
2290 min_dt)
Victor Stinner2a35c932017-02-10 12:37:21 +01002291 except (OverflowError, OSError) as exc:
2292 # the date 0001-01-01 doesn't fit into 32-bit time_t,
2293 # or platform doesn't support such very old date
Victor Stinner6f37e362017-02-10 11:45:14 +01002294 self.skipTest(str(exc))
Victor Stinnerb67f0962017-02-10 10:34:02 +01002295
2296 # maximum timestamp: set seconds to zero to avoid rounding issues
2297 max_dt = self.theclass.max.replace(tzinfo=timezone.utc,
2298 second=0, microsecond=0)
2299 max_ts = max_dt.timestamp()
2300 # date 9999-12-31 23:59:00+00:00: timestamp 253402300740
2301 self.assertEqual(self.theclass.fromtimestamp(max_ts, tz=timezone.utc),
2302 max_dt)
2303
2304 # number of seconds greater than 1 year: make sure that the new date
2305 # is not valid in datetime.datetime limits
2306 delta = 3600 * 24 * 400
2307
2308 # too small
2309 ts = min_ts - delta
2310 # converting a Python int to C time_t can raise a OverflowError,
2311 # especially on 32-bit platforms.
2312 with self.assertRaises((ValueError, OverflowError)):
2313 self.theclass.fromtimestamp(ts)
2314 with self.assertRaises((ValueError, OverflowError)):
2315 self.theclass.utcfromtimestamp(ts)
2316
2317 # too big
2318 ts = max_dt.timestamp() + delta
2319 with self.assertRaises((ValueError, OverflowError)):
2320 self.theclass.fromtimestamp(ts)
2321 with self.assertRaises((ValueError, OverflowError)):
2322 self.theclass.utcfromtimestamp(ts)
2323
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002324 def test_insane_fromtimestamp(self):
2325 # It's possible that some platform maps time_t to double,
2326 # and that this test will fail there. This test should
2327 # exempt such platforms (provided they return reasonable
2328 # results!).
2329 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002330 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002331 insane)
2332
2333 def test_insane_utcfromtimestamp(self):
2334 # It's possible that some platform maps time_t to double,
2335 # and that this test will fail there. This test should
2336 # exempt such platforms (provided they return reasonable
2337 # results!).
2338 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01002339 self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002340 insane)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002341
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002342 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2343 def test_negative_float_fromtimestamp(self):
2344 # The result is tz-dependent; at least test that this doesn't
2345 # fail (like it did before bug 1646728 was fixed).
2346 self.theclass.fromtimestamp(-1.05)
2347
2348 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2349 def test_negative_float_utcfromtimestamp(self):
2350 d = self.theclass.utcfromtimestamp(-1.05)
2351 self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
2352
2353 def test_utcnow(self):
2354 import time
2355
2356 # Call it a success if utcnow() and utcfromtimestamp() are within
2357 # a second of each other.
2358 tolerance = timedelta(seconds=1)
2359 for dummy in range(3):
2360 from_now = self.theclass.utcnow()
2361 from_timestamp = self.theclass.utcfromtimestamp(time.time())
2362 if abs(from_timestamp - from_now) <= tolerance:
2363 break
2364 # Else try again a few times.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002365 self.assertLessEqual(abs(from_timestamp - from_now), tolerance)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002366
2367 def test_strptime(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002368 string = '2004-12-01 13:02:47.197'
2369 format = '%Y-%m-%d %H:%M:%S.%f'
2370 expected = _strptime._strptime_datetime(self.theclass, string, format)
2371 got = self.theclass.strptime(string, format)
2372 self.assertEqual(expected, got)
2373 self.assertIs(type(expected), self.theclass)
2374 self.assertIs(type(got), self.theclass)
2375
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03002376 # bpo-34482: Check that surrogates are handled properly.
2377 inputs = [
2378 ('2004-12-01\ud80013:02:47.197', '%Y-%m-%d\ud800%H:%M:%S.%f'),
2379 ('2004\ud80012-01 13:02:47.197', '%Y\ud800%m-%d %H:%M:%S.%f'),
2380 ('2004-12-01 13:02\ud80047.197', '%Y-%m-%d %H:%M\ud800%S.%f'),
2381 ]
2382 for string, format in inputs:
2383 with self.subTest(string=string, format=format):
2384 expected = _strptime._strptime_datetime(self.theclass, string,
2385 format)
2386 got = self.theclass.strptime(string, format)
2387 self.assertEqual(expected, got)
2388
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002389 strptime = self.theclass.strptime
2390 self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
2391 self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
Mario Corchero32318932017-10-26 01:35:41 +01002392 self.assertEqual(
2393 strptime("-00:02:01.000003", "%z").utcoffset(),
2394 -timedelta(minutes=2, seconds=1, microseconds=3)
2395 )
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002396 # Only local timezone and UTC are supported
2397 for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
2398 (-_time.timezone, _time.tzname[0])):
2399 if tzseconds < 0:
2400 sign = '-'
2401 seconds = -tzseconds
2402 else:
2403 sign ='+'
2404 seconds = tzseconds
2405 hours, minutes = divmod(seconds//60, 60)
2406 dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
Martin Panterfca22322015-11-16 09:22:19 +00002407 dt = strptime(dtstr, "%z %Z")
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002408 self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
2409 self.assertEqual(dt.tzname(), tzname)
2410 # Can produce inconsistent datetime
2411 dtstr, fmt = "+1234 UTC", "%z %Z"
2412 dt = strptime(dtstr, fmt)
2413 self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
2414 self.assertEqual(dt.tzname(), 'UTC')
2415 # yet will roundtrip
2416 self.assertEqual(dt.strftime(fmt), dtstr)
2417
2418 # Produce naive datetime if no %z is provided
2419 self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
2420
2421 with self.assertRaises(ValueError): strptime("-2400", "%z")
2422 with self.assertRaises(ValueError): strptime("-000", "%z")
2423
2424 def test_more_timetuple(self):
2425 # This tests fields beyond those tested by the TestDate.test_timetuple.
2426 t = self.theclass(2004, 12, 31, 6, 22, 33)
2427 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
2428 self.assertEqual(t.timetuple(),
2429 (t.year, t.month, t.day,
2430 t.hour, t.minute, t.second,
2431 t.weekday(),
2432 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
2433 -1))
2434 tt = t.timetuple()
2435 self.assertEqual(tt.tm_year, t.year)
2436 self.assertEqual(tt.tm_mon, t.month)
2437 self.assertEqual(tt.tm_mday, t.day)
2438 self.assertEqual(tt.tm_hour, t.hour)
2439 self.assertEqual(tt.tm_min, t.minute)
2440 self.assertEqual(tt.tm_sec, t.second)
2441 self.assertEqual(tt.tm_wday, t.weekday())
2442 self.assertEqual(tt.tm_yday, t.toordinal() -
2443 date(t.year, 1, 1).toordinal() + 1)
2444 self.assertEqual(tt.tm_isdst, -1)
2445
2446 def test_more_strftime(self):
2447 # This tests fields beyond those tested by the TestDate.test_strftime.
2448 t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
2449 self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
2450 "12 31 04 000047 33 22 06 366")
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04002451 for (s, us), z in [((33, 123), "33.000123"), ((33, 0), "33"),]:
2452 tz = timezone(-timedelta(hours=2, seconds=s, microseconds=us))
2453 t = t.replace(tzinfo=tz)
2454 self.assertEqual(t.strftime("%z"), "-0200" + z)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002455
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03002456 # bpo-34482: Check that surrogates don't cause a crash.
2457 try:
2458 t.strftime('%y\ud800%m %H\ud800%M')
2459 except UnicodeEncodeError:
2460 pass
2461
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002462 def test_extract(self):
2463 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2464 self.assertEqual(dt.date(), date(2002, 3, 4))
2465 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2466
2467 def test_combine(self):
2468 d = date(2002, 3, 4)
2469 t = time(18, 45, 3, 1234)
2470 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2471 combine = self.theclass.combine
2472 dt = combine(d, t)
2473 self.assertEqual(dt, expected)
2474
2475 dt = combine(time=t, date=d)
2476 self.assertEqual(dt, expected)
2477
2478 self.assertEqual(d, dt.date())
2479 self.assertEqual(t, dt.time())
2480 self.assertEqual(dt, combine(dt.date(), dt.time()))
2481
2482 self.assertRaises(TypeError, combine) # need an arg
2483 self.assertRaises(TypeError, combine, d) # need two args
2484 self.assertRaises(TypeError, combine, t, d) # args reversed
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002485 self.assertRaises(TypeError, combine, d, t, 1) # wrong tzinfo type
2486 self.assertRaises(TypeError, combine, d, t, 1, 2) # too many args
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002487 self.assertRaises(TypeError, combine, "date", "time") # wrong types
2488 self.assertRaises(TypeError, combine, d, "time") # wrong type
2489 self.assertRaises(TypeError, combine, "date", t) # wrong type
2490
Alexander Belopolsky43746c32016-08-02 17:49:30 -04002491 # tzinfo= argument
2492 dt = combine(d, t, timezone.utc)
2493 self.assertIs(dt.tzinfo, timezone.utc)
2494 dt = combine(d, t, tzinfo=timezone.utc)
2495 self.assertIs(dt.tzinfo, timezone.utc)
2496 t = time()
2497 dt = combine(dt, t)
2498 self.assertEqual(dt.date(), d)
2499 self.assertEqual(dt.time(), t)
2500
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002501 def test_replace(self):
2502 cls = self.theclass
2503 args = [1, 2, 3, 4, 5, 6, 7]
2504 base = cls(*args)
2505 self.assertEqual(base, base.replace())
2506
2507 i = 0
2508 for name, newval in (("year", 2),
2509 ("month", 3),
2510 ("day", 4),
2511 ("hour", 5),
2512 ("minute", 6),
2513 ("second", 7),
2514 ("microsecond", 8)):
2515 newargs = args[:]
2516 newargs[i] = newval
2517 expected = cls(*newargs)
2518 got = base.replace(**{name: newval})
2519 self.assertEqual(expected, got)
2520 i += 1
2521
2522 # Out of bounds.
2523 base = cls(2000, 2, 29)
2524 self.assertRaises(ValueError, base.replace, year=2001)
2525
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002526 @support.run_with_tz('EDT4')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002527 def test_astimezone(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002528 dt = self.theclass.now()
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002529 f = FixedOffset(44, "0044")
2530 dt_utc = dt.replace(tzinfo=timezone(timedelta(hours=-4), 'EDT'))
2531 self.assertEqual(dt.astimezone(), dt_utc) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002532 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
2533 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002534 dt_f = dt.replace(tzinfo=f) + timedelta(hours=4, minutes=44)
2535 self.assertEqual(dt.astimezone(f), dt_f) # naive
2536 self.assertEqual(dt.astimezone(tz=f), dt_f) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002537
2538 class Bogus(tzinfo):
2539 def utcoffset(self, dt): return None
2540 def dst(self, dt): return timedelta(0)
2541 bog = Bogus()
2542 self.assertRaises(ValueError, dt.astimezone, bog) # naive
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002543 self.assertEqual(dt.replace(tzinfo=bog).astimezone(f), dt_f)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002544
2545 class AlsoBogus(tzinfo):
2546 def utcoffset(self, dt): return timedelta(0)
2547 def dst(self, dt): return None
2548 alsobog = AlsoBogus()
2549 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
2550
Alexander Belopolsky877b2322018-06-10 17:02:58 -04002551 class Broken(tzinfo):
2552 def utcoffset(self, dt): return 1
2553 def dst(self, dt): return 1
2554 broken = Broken()
2555 dt_broken = dt.replace(tzinfo=broken)
2556 with self.assertRaises(TypeError):
2557 dt_broken.astimezone()
2558
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002559 def test_subclass_datetime(self):
2560
2561 class C(self.theclass):
2562 theAnswer = 42
2563
2564 def __new__(cls, *args, **kws):
2565 temp = kws.copy()
2566 extra = temp.pop('extra')
2567 result = self.theclass.__new__(cls, *args, **temp)
2568 result.extra = extra
2569 return result
2570
2571 def newmeth(self, start):
2572 return start + self.year + self.month + self.second
2573
2574 args = 2003, 4, 14, 12, 13, 41
2575
2576 dt1 = self.theclass(*args)
2577 dt2 = C(*args, **{'extra': 7})
2578
2579 self.assertEqual(dt2.__class__, C)
2580 self.assertEqual(dt2.theAnswer, 42)
2581 self.assertEqual(dt2.extra, 7)
2582 self.assertEqual(dt1.toordinal(), dt2.toordinal())
2583 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
2584 dt1.second - 7)
2585
Paul Ganssle9f1b7b92018-01-16 13:06:31 -05002586 def test_subclass_alternate_constructors_datetime(self):
2587 # Test that alternate constructors call the constructor
2588 class DateTimeSubclass(self.theclass):
2589 def __new__(cls, *args, **kwargs):
2590 result = self.theclass.__new__(cls, *args, **kwargs)
2591 result.extra = 7
2592
2593 return result
2594
2595 args = (2003, 4, 14, 12, 30, 15, 123456)
2596 d_isoformat = '2003-04-14T12:30:15.123456' # Equivalent isoformat()
2597 utc_ts = 1050323415.123456 # UTC timestamp
2598
2599 base_d = DateTimeSubclass(*args)
2600 self.assertIsInstance(base_d, DateTimeSubclass)
2601 self.assertEqual(base_d.extra, 7)
2602
2603 # Timestamp depends on time zone, so we'll calculate the equivalent here
2604 ts = base_d.timestamp()
2605
2606 test_cases = [
2607 ('fromtimestamp', (ts,)),
2608 # See https://bugs.python.org/issue32417
2609 # ('fromtimestamp', (ts, timezone.utc)),
2610 ('utcfromtimestamp', (utc_ts,)),
2611 ('fromisoformat', (d_isoformat,)),
2612 ('strptime', (d_isoformat, '%Y-%m-%dT%H:%M:%S.%f')),
2613 ('combine', (date(*args[0:3]), time(*args[3:]))),
2614 ]
2615
2616 for constr_name, constr_args in test_cases:
2617 for base_obj in (DateTimeSubclass, base_d):
2618 # Test both the classmethod and method
2619 with self.subTest(base_obj_type=type(base_obj),
2620 constr_name=constr_name):
2621 constr = getattr(base_obj, constr_name)
2622
2623 dt = constr(*constr_args)
2624
2625 # Test that it creates the right subclass
2626 self.assertIsInstance(dt, DateTimeSubclass)
2627
2628 # Test that it's equal to the base object
2629 self.assertEqual(dt, base_d.replace(tzinfo=None))
2630
2631 # Test that it called the constructor
2632 self.assertEqual(dt.extra, 7)
2633
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002634 def test_fromisoformat_datetime(self):
2635 # Test that isoformat() is reversible
2636 base_dates = [
2637 (1, 1, 1),
2638 (1900, 1, 1),
2639 (2004, 11, 12),
2640 (2017, 5, 30)
2641 ]
2642
2643 base_times = [
2644 (0, 0, 0, 0),
2645 (0, 0, 0, 241000),
2646 (0, 0, 0, 234567),
2647 (12, 30, 45, 234567)
2648 ]
2649
2650 separators = [' ', 'T']
2651
2652 tzinfos = [None, timezone.utc,
2653 timezone(timedelta(hours=-5)),
2654 timezone(timedelta(hours=2))]
2655
2656 dts = [self.theclass(*date_tuple, *time_tuple, tzinfo=tzi)
2657 for date_tuple in base_dates
2658 for time_tuple in base_times
2659 for tzi in tzinfos]
2660
2661 for dt in dts:
2662 for sep in separators:
2663 dtstr = dt.isoformat(sep=sep)
2664
2665 with self.subTest(dtstr=dtstr):
2666 dt_rt = self.theclass.fromisoformat(dtstr)
2667 self.assertEqual(dt, dt_rt)
2668
2669 def test_fromisoformat_timezone(self):
2670 base_dt = self.theclass(2014, 12, 30, 12, 30, 45, 217456)
2671
2672 tzoffsets = [
2673 timedelta(hours=5), timedelta(hours=2),
2674 timedelta(hours=6, minutes=27),
2675 timedelta(hours=12, minutes=32, seconds=30),
2676 timedelta(hours=2, minutes=4, seconds=9, microseconds=123456)
2677 ]
2678
2679 tzoffsets += [-1 * td for td in tzoffsets]
2680
2681 tzinfos = [None, timezone.utc,
2682 timezone(timedelta(hours=0))]
2683
2684 tzinfos += [timezone(td) for td in tzoffsets]
2685
2686 for tzi in tzinfos:
2687 dt = base_dt.replace(tzinfo=tzi)
2688 dtstr = dt.isoformat()
2689
2690 with self.subTest(tstr=dtstr):
2691 dt_rt = self.theclass.fromisoformat(dtstr)
2692 assert dt == dt_rt, dt_rt
2693
2694 def test_fromisoformat_separators(self):
2695 separators = [
2696 ' ', 'T', '\u007f', # 1-bit widths
2697 '\u0080', 'ʁ', # 2-bit widths
2698 'ᛇ', '時', # 3-bit widths
Paul Ganssle096329f2018-08-23 11:06:20 -04002699 '🐍', # 4-bit widths
2700 '\ud800', # bpo-34454: Surrogate code point
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002701 ]
2702
2703 for sep in separators:
2704 dt = self.theclass(2018, 1, 31, 23, 59, 47, 124789)
2705 dtstr = dt.isoformat(sep=sep)
2706
2707 with self.subTest(dtstr=dtstr):
2708 dt_rt = self.theclass.fromisoformat(dtstr)
2709 self.assertEqual(dt, dt_rt)
2710
2711 def test_fromisoformat_ambiguous(self):
2712 # Test strings like 2018-01-31+12:15 (where +12:15 is not a time zone)
2713 separators = ['+', '-']
2714 for sep in separators:
2715 dt = self.theclass(2018, 1, 31, 12, 15)
2716 dtstr = dt.isoformat(sep=sep)
2717
2718 with self.subTest(dtstr=dtstr):
2719 dt_rt = self.theclass.fromisoformat(dtstr)
2720 self.assertEqual(dt, dt_rt)
2721
2722 def test_fromisoformat_timespecs(self):
2723 datetime_bases = [
2724 (2009, 12, 4, 8, 17, 45, 123456),
2725 (2009, 12, 4, 8, 17, 45, 0)]
2726
2727 tzinfos = [None, timezone.utc,
2728 timezone(timedelta(hours=-5)),
2729 timezone(timedelta(hours=2)),
2730 timezone(timedelta(hours=6, minutes=27))]
2731
2732 timespecs = ['hours', 'minutes', 'seconds',
2733 'milliseconds', 'microseconds']
2734
2735 for ip, ts in enumerate(timespecs):
2736 for tzi in tzinfos:
2737 for dt_tuple in datetime_bases:
2738 if ts == 'milliseconds':
2739 new_microseconds = 1000 * (dt_tuple[6] // 1000)
2740 dt_tuple = dt_tuple[0:6] + (new_microseconds,)
2741
2742 dt = self.theclass(*(dt_tuple[0:(4 + ip)]), tzinfo=tzi)
2743 dtstr = dt.isoformat(timespec=ts)
2744 with self.subTest(dtstr=dtstr):
2745 dt_rt = self.theclass.fromisoformat(dtstr)
2746 self.assertEqual(dt, dt_rt)
2747
2748 def test_fromisoformat_fails_datetime(self):
2749 # Test that fromisoformat() fails on invalid values
2750 bad_strs = [
2751 '', # Empty string
Paul Ganssle096329f2018-08-23 11:06:20 -04002752 '\ud800', # bpo-34454: Surrogate code point
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002753 '2009.04-19T03', # Wrong first separator
2754 '2009-04.19T03', # Wrong second separator
2755 '2009-04-19T0a', # Invalid hours
2756 '2009-04-19T03:1a:45', # Invalid minutes
2757 '2009-04-19T03:15:4a', # Invalid seconds
2758 '2009-04-19T03;15:45', # Bad first time separator
2759 '2009-04-19T03:15;45', # Bad second time separator
2760 '2009-04-19T03:15:4500:00', # Bad time zone separator
2761 '2009-04-19T03:15:45.2345', # Too many digits for milliseconds
2762 '2009-04-19T03:15:45.1234567', # Too many digits for microseconds
2763 '2009-04-19T03:15:45.123456+24:30', # Invalid time zone offset
2764 '2009-04-19T03:15:45.123456-24:30', # Invalid negative offset
2765 '2009-04-10ᛇᛇᛇᛇᛇ12:15', # Too many unicode separators
Paul Ganssle096329f2018-08-23 11:06:20 -04002766 '2009-04\ud80010T12:15', # Surrogate char in date
2767 '2009-04-10T12\ud80015', # Surrogate char in time
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002768 '2009-04-19T1', # Incomplete hours
2769 '2009-04-19T12:3', # Incomplete minutes
2770 '2009-04-19T12:30:4', # Incomplete seconds
2771 '2009-04-19T12:', # Ends with time separator
2772 '2009-04-19T12:30:', # Ends with time separator
2773 '2009-04-19T12:30:45.', # Ends with time separator
2774 '2009-04-19T12:30:45.123456+', # Ends with timzone separator
2775 '2009-04-19T12:30:45.123456-', # Ends with timzone separator
2776 '2009-04-19T12:30:45.123456-05:00a', # Extra text
2777 '2009-04-19T12:30:45.123-05:00a', # Extra text
2778 '2009-04-19T12:30:45-05:00a', # Extra text
2779 ]
2780
2781 for bad_str in bad_strs:
2782 with self.subTest(bad_str=bad_str):
2783 with self.assertRaises(ValueError):
2784 self.theclass.fromisoformat(bad_str)
2785
Paul Ganssle3df85402018-10-22 12:32:52 -04002786 def test_fromisoformat_fails_surrogate(self):
2787 # Test that when fromisoformat() fails with a surrogate character as
2788 # the separator, the error message contains the original string
2789 dtstr = "2018-01-03\ud80001:0113"
2790
2791 with self.assertRaisesRegex(ValueError, re.escape(repr(dtstr))):
2792 self.theclass.fromisoformat(dtstr)
2793
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002794 def test_fromisoformat_utc(self):
2795 dt_str = '2014-04-19T13:21:13+00:00'
2796 dt = self.theclass.fromisoformat(dt_str)
2797
2798 self.assertIs(dt.tzinfo, timezone.utc)
2799
2800 def test_fromisoformat_subclass(self):
2801 class DateTimeSubclass(self.theclass):
2802 pass
2803
2804 dt = DateTimeSubclass(2014, 12, 14, 9, 30, 45, 457390,
2805 tzinfo=timezone(timedelta(hours=10, minutes=45)))
2806
2807 dt_rt = DateTimeSubclass.fromisoformat(dt.isoformat())
2808
2809 self.assertEqual(dt, dt_rt)
2810 self.assertIsInstance(dt_rt, DateTimeSubclass)
2811
2812
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002813class TestSubclassDateTime(TestDateTime):
2814 theclass = SubclassDatetime
2815 # Override tests not designed for subclass
Zachary Ware9fe6d862013-12-08 00:20:35 -06002816 @unittest.skip('not appropriate for subclasses')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002817 def test_roundtrip(self):
2818 pass
2819
2820class SubclassTime(time):
2821 sub_var = 1
2822
2823class TestTime(HarmlessMixedComparison, unittest.TestCase):
2824
2825 theclass = time
2826
2827 def test_basic_attributes(self):
2828 t = self.theclass(12, 0)
2829 self.assertEqual(t.hour, 12)
2830 self.assertEqual(t.minute, 0)
2831 self.assertEqual(t.second, 0)
2832 self.assertEqual(t.microsecond, 0)
2833
2834 def test_basic_attributes_nonzero(self):
2835 # Make sure all attributes are non-zero so bugs in
2836 # bit-shifting access show up.
2837 t = self.theclass(12, 59, 59, 8000)
2838 self.assertEqual(t.hour, 12)
2839 self.assertEqual(t.minute, 59)
2840 self.assertEqual(t.second, 59)
2841 self.assertEqual(t.microsecond, 8000)
2842
2843 def test_roundtrip(self):
2844 t = self.theclass(1, 2, 3, 4)
2845
2846 # Verify t -> string -> time identity.
2847 s = repr(t)
2848 self.assertTrue(s.startswith('datetime.'))
2849 s = s[9:]
2850 t2 = eval(s)
2851 self.assertEqual(t, t2)
2852
2853 # Verify identity via reconstructing from pieces.
2854 t2 = self.theclass(t.hour, t.minute, t.second,
2855 t.microsecond)
2856 self.assertEqual(t, t2)
2857
2858 def test_comparing(self):
2859 args = [1, 2, 3, 4]
2860 t1 = self.theclass(*args)
2861 t2 = self.theclass(*args)
2862 self.assertEqual(t1, t2)
2863 self.assertTrue(t1 <= t2)
2864 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002865 self.assertFalse(t1 != t2)
2866 self.assertFalse(t1 < t2)
2867 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002868
2869 for i in range(len(args)):
2870 newargs = args[:]
2871 newargs[i] = args[i] + 1
2872 t2 = self.theclass(*newargs) # this is larger than t1
2873 self.assertTrue(t1 < t2)
2874 self.assertTrue(t2 > t1)
2875 self.assertTrue(t1 <= t2)
2876 self.assertTrue(t2 >= t1)
2877 self.assertTrue(t1 != t2)
2878 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002879 self.assertFalse(t1 == t2)
2880 self.assertFalse(t2 == t1)
2881 self.assertFalse(t1 > t2)
2882 self.assertFalse(t2 < t1)
2883 self.assertFalse(t1 >= t2)
2884 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002885
2886 for badarg in OTHERSTUFF:
2887 self.assertEqual(t1 == badarg, False)
2888 self.assertEqual(t1 != badarg, True)
2889 self.assertEqual(badarg == t1, False)
2890 self.assertEqual(badarg != t1, True)
2891
2892 self.assertRaises(TypeError, lambda: t1 <= badarg)
2893 self.assertRaises(TypeError, lambda: t1 < badarg)
2894 self.assertRaises(TypeError, lambda: t1 > badarg)
2895 self.assertRaises(TypeError, lambda: t1 >= badarg)
2896 self.assertRaises(TypeError, lambda: badarg <= t1)
2897 self.assertRaises(TypeError, lambda: badarg < t1)
2898 self.assertRaises(TypeError, lambda: badarg > t1)
2899 self.assertRaises(TypeError, lambda: badarg >= t1)
2900
2901 def test_bad_constructor_arguments(self):
2902 # bad hours
2903 self.theclass(0, 0) # no exception
2904 self.theclass(23, 0) # no exception
2905 self.assertRaises(ValueError, self.theclass, -1, 0)
2906 self.assertRaises(ValueError, self.theclass, 24, 0)
2907 # bad minutes
2908 self.theclass(23, 0) # no exception
2909 self.theclass(23, 59) # no exception
2910 self.assertRaises(ValueError, self.theclass, 23, -1)
2911 self.assertRaises(ValueError, self.theclass, 23, 60)
2912 # bad seconds
2913 self.theclass(23, 59, 0) # no exception
2914 self.theclass(23, 59, 59) # no exception
2915 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
2916 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
2917 # bad microseconds
2918 self.theclass(23, 59, 59, 0) # no exception
2919 self.theclass(23, 59, 59, 999999) # no exception
2920 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
2921 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
2922
2923 def test_hash_equality(self):
2924 d = self.theclass(23, 30, 17)
2925 e = self.theclass(23, 30, 17)
2926 self.assertEqual(d, e)
2927 self.assertEqual(hash(d), hash(e))
2928
2929 dic = {d: 1}
2930 dic[e] = 2
2931 self.assertEqual(len(dic), 1)
2932 self.assertEqual(dic[d], 2)
2933 self.assertEqual(dic[e], 2)
2934
2935 d = self.theclass(0, 5, 17)
2936 e = self.theclass(0, 5, 17)
2937 self.assertEqual(d, e)
2938 self.assertEqual(hash(d), hash(e))
2939
2940 dic = {d: 1}
2941 dic[e] = 2
2942 self.assertEqual(len(dic), 1)
2943 self.assertEqual(dic[d], 2)
2944 self.assertEqual(dic[e], 2)
2945
2946 def test_isoformat(self):
2947 t = self.theclass(4, 5, 1, 123)
2948 self.assertEqual(t.isoformat(), "04:05:01.000123")
2949 self.assertEqual(t.isoformat(), str(t))
2950
2951 t = self.theclass()
2952 self.assertEqual(t.isoformat(), "00:00:00")
2953 self.assertEqual(t.isoformat(), str(t))
2954
2955 t = self.theclass(microsecond=1)
2956 self.assertEqual(t.isoformat(), "00:00:00.000001")
2957 self.assertEqual(t.isoformat(), str(t))
2958
2959 t = self.theclass(microsecond=10)
2960 self.assertEqual(t.isoformat(), "00:00:00.000010")
2961 self.assertEqual(t.isoformat(), str(t))
2962
2963 t = self.theclass(microsecond=100)
2964 self.assertEqual(t.isoformat(), "00:00:00.000100")
2965 self.assertEqual(t.isoformat(), str(t))
2966
2967 t = self.theclass(microsecond=1000)
2968 self.assertEqual(t.isoformat(), "00:00:00.001000")
2969 self.assertEqual(t.isoformat(), str(t))
2970
2971 t = self.theclass(microsecond=10000)
2972 self.assertEqual(t.isoformat(), "00:00:00.010000")
2973 self.assertEqual(t.isoformat(), str(t))
2974
2975 t = self.theclass(microsecond=100000)
2976 self.assertEqual(t.isoformat(), "00:00:00.100000")
2977 self.assertEqual(t.isoformat(), str(t))
2978
Alexander Belopolskya2998a62016-03-06 14:58:43 -05002979 t = self.theclass(hour=12, minute=34, second=56, microsecond=123456)
2980 self.assertEqual(t.isoformat(timespec='hours'), "12")
2981 self.assertEqual(t.isoformat(timespec='minutes'), "12:34")
2982 self.assertEqual(t.isoformat(timespec='seconds'), "12:34:56")
2983 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.123")
2984 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.123456")
2985 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56.123456")
2986 self.assertRaises(ValueError, t.isoformat, timespec='monkey')
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03002987 # bpo-34482: Check that surrogates are handled properly.
2988 self.assertRaises(ValueError, t.isoformat, timespec='\ud800')
Alexander Belopolskya2998a62016-03-06 14:58:43 -05002989
2990 t = self.theclass(hour=12, minute=34, second=56, microsecond=999500)
2991 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.999")
2992
2993 t = self.theclass(hour=12, minute=34, second=56, microsecond=0)
2994 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.000")
2995 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.000000")
2996 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56")
2997
Paul Ganssle09dc2f52017-12-21 00:33:49 -05002998 def test_isoformat_timezone(self):
2999 tzoffsets = [
3000 ('05:00', timedelta(hours=5)),
3001 ('02:00', timedelta(hours=2)),
3002 ('06:27', timedelta(hours=6, minutes=27)),
3003 ('12:32:30', timedelta(hours=12, minutes=32, seconds=30)),
3004 ('02:04:09.123456', timedelta(hours=2, minutes=4, seconds=9, microseconds=123456))
3005 ]
3006
3007 tzinfos = [
3008 ('', None),
3009 ('+00:00', timezone.utc),
3010 ('+00:00', timezone(timedelta(0))),
3011 ]
3012
3013 tzinfos += [
3014 (prefix + expected, timezone(sign * td))
3015 for expected, td in tzoffsets
3016 for prefix, sign in [('-', -1), ('+', 1)]
3017 ]
3018
3019 t_base = self.theclass(12, 37, 9)
3020 exp_base = '12:37:09'
3021
3022 for exp_tz, tzi in tzinfos:
3023 t = t_base.replace(tzinfo=tzi)
3024 exp = exp_base + exp_tz
3025 with self.subTest(tzi=tzi):
3026 assert t.isoformat() == exp
3027
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003028 def test_1653736(self):
3029 # verify it doesn't accept extra keyword arguments
3030 t = self.theclass(second=1)
3031 self.assertRaises(TypeError, t.isoformat, foo=3)
3032
3033 def test_strftime(self):
3034 t = self.theclass(1, 2, 3, 4)
3035 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
3036 # A naive object replaces %z and %Z with empty strings.
3037 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
3038
Alexey Izbyshev3b0047d2018-10-23 09:36:08 +03003039 # bpo-34482: Check that surrogates don't cause a crash.
3040 try:
3041 t.strftime('%H\ud800%M')
3042 except UnicodeEncodeError:
3043 pass
3044
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003045 def test_format(self):
3046 t = self.theclass(1, 2, 3, 4)
3047 self.assertEqual(t.__format__(''), str(t))
3048
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02003049 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04003050 t.__format__(123)
3051
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003052 # check that a derived class's __str__() gets called
3053 class A(self.theclass):
3054 def __str__(self):
3055 return 'A'
3056 a = A(1, 2, 3, 4)
3057 self.assertEqual(a.__format__(''), 'A')
3058
3059 # check that a derived class's strftime gets called
3060 class B(self.theclass):
3061 def strftime(self, format_spec):
3062 return 'B'
3063 b = B(1, 2, 3, 4)
3064 self.assertEqual(b.__format__(''), str(t))
3065
3066 for fmt in ['%H %M %S',
3067 ]:
3068 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
3069 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
3070 self.assertEqual(b.__format__(fmt), 'B')
3071
3072 def test_str(self):
3073 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
3074 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
3075 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
3076 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
3077 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
3078
3079 def test_repr(self):
3080 name = 'datetime.' + self.theclass.__name__
3081 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
3082 "%s(1, 2, 3, 4)" % name)
3083 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
3084 "%s(10, 2, 3, 4000)" % name)
3085 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
3086 "%s(0, 2, 3, 400000)" % name)
3087 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
3088 "%s(12, 2, 3)" % name)
3089 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
3090 "%s(23, 15)" % name)
3091
3092 def test_resolution_info(self):
3093 self.assertIsInstance(self.theclass.min, self.theclass)
3094 self.assertIsInstance(self.theclass.max, self.theclass)
3095 self.assertIsInstance(self.theclass.resolution, timedelta)
3096 self.assertTrue(self.theclass.max > self.theclass.min)
3097
3098 def test_pickling(self):
3099 args = 20, 59, 16, 64**2
3100 orig = self.theclass(*args)
3101 for pickler, unpickler, proto in pickle_choices:
3102 green = pickler.dumps(orig, proto)
3103 derived = unpickler.loads(green)
3104 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003105 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003106
3107 def test_pickling_subclass_time(self):
3108 args = 20, 59, 16, 64**2
3109 orig = SubclassTime(*args)
3110 for pickler, unpickler, proto in pickle_choices:
3111 green = pickler.dumps(orig, proto)
3112 derived = unpickler.loads(green)
3113 self.assertEqual(orig, derived)
3114
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02003115 def test_compat_unpickle(self):
3116 tests = [
3117 b"cdatetime\ntime\n(S'\\x14;\\x10\\x00\\x10\\x00'\ntR.",
3118 b'cdatetime\ntime\n(U\x06\x14;\x10\x00\x10\x00tR.',
3119 b'\x80\x02cdatetime\ntime\nU\x06\x14;\x10\x00\x10\x00\x85R.',
3120 ]
3121 args = 20, 59, 16, 64**2
3122 expected = self.theclass(*args)
3123 for data in tests:
3124 for loads in pickle_loads:
3125 derived = loads(data, encoding='latin1')
3126 self.assertEqual(derived, expected)
3127
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003128 def test_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003129 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003130 cls = self.theclass
3131 self.assertTrue(cls(1))
3132 self.assertTrue(cls(0, 1))
3133 self.assertTrue(cls(0, 0, 1))
3134 self.assertTrue(cls(0, 0, 0, 1))
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003135 self.assertTrue(cls(0))
3136 self.assertTrue(cls())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003137
3138 def test_replace(self):
3139 cls = self.theclass
3140 args = [1, 2, 3, 4]
3141 base = cls(*args)
3142 self.assertEqual(base, base.replace())
3143
3144 i = 0
3145 for name, newval in (("hour", 5),
3146 ("minute", 6),
3147 ("second", 7),
3148 ("microsecond", 8)):
3149 newargs = args[:]
3150 newargs[i] = newval
3151 expected = cls(*newargs)
3152 got = base.replace(**{name: newval})
3153 self.assertEqual(expected, got)
3154 i += 1
3155
3156 # Out of bounds.
3157 base = cls(1)
3158 self.assertRaises(ValueError, base.replace, hour=24)
3159 self.assertRaises(ValueError, base.replace, minute=-1)
3160 self.assertRaises(ValueError, base.replace, second=100)
3161 self.assertRaises(ValueError, base.replace, microsecond=1000000)
3162
Paul Ganssle191e9932017-11-09 16:34:29 -05003163 def test_subclass_replace(self):
3164 class TimeSubclass(self.theclass):
3165 pass
3166
3167 ctime = TimeSubclass(12, 30)
3168 self.assertIs(type(ctime.replace(hour=10)), TimeSubclass)
3169
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003170 def test_subclass_time(self):
3171
3172 class C(self.theclass):
3173 theAnswer = 42
3174
3175 def __new__(cls, *args, **kws):
3176 temp = kws.copy()
3177 extra = temp.pop('extra')
3178 result = self.theclass.__new__(cls, *args, **temp)
3179 result.extra = extra
3180 return result
3181
3182 def newmeth(self, start):
3183 return start + self.hour + self.second
3184
3185 args = 4, 5, 6
3186
3187 dt1 = self.theclass(*args)
3188 dt2 = C(*args, **{'extra': 7})
3189
3190 self.assertEqual(dt2.__class__, C)
3191 self.assertEqual(dt2.theAnswer, 42)
3192 self.assertEqual(dt2.extra, 7)
3193 self.assertEqual(dt1.isoformat(), dt2.isoformat())
3194 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
3195
3196 def test_backdoor_resistance(self):
3197 # see TestDate.test_backdoor_resistance().
3198 base = '2:59.0'
3199 for hour_byte in ' ', '9', chr(24), '\xff':
3200 self.assertRaises(TypeError, self.theclass,
3201 hour_byte + base[1:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04003202 # Good bytes, but bad tzinfo:
3203 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
3204 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003205
3206# A mixin for classes with a tzinfo= argument. Subclasses must define
Martin Panter46f50722016-05-26 05:35:26 +00003207# theclass as a class attribute, and theclass(1, 1, 1, tzinfo=whatever)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003208# must be legit (which is true for time and datetime).
3209class TZInfoBase:
3210
3211 def test_argument_passing(self):
3212 cls = self.theclass
3213 # A datetime passes itself on, a time passes None.
3214 class introspective(tzinfo):
3215 def tzname(self, dt): return dt and "real" or "none"
3216 def utcoffset(self, dt):
3217 return timedelta(minutes = dt and 42 or -42)
3218 dst = utcoffset
3219
3220 obj = cls(1, 2, 3, tzinfo=introspective())
3221
3222 expected = cls is time and "none" or "real"
3223 self.assertEqual(obj.tzname(), expected)
3224
3225 expected = timedelta(minutes=(cls is time and -42 or 42))
3226 self.assertEqual(obj.utcoffset(), expected)
3227 self.assertEqual(obj.dst(), expected)
3228
3229 def test_bad_tzinfo_classes(self):
3230 cls = self.theclass
3231 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
3232
3233 class NiceTry(object):
3234 def __init__(self): pass
3235 def utcoffset(self, dt): pass
3236 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
3237
3238 class BetterTry(tzinfo):
3239 def __init__(self): pass
3240 def utcoffset(self, dt): pass
3241 b = BetterTry()
3242 t = cls(1, 1, 1, tzinfo=b)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003243 self.assertIs(t.tzinfo, b)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003244
3245 def test_utc_offset_out_of_bounds(self):
3246 class Edgy(tzinfo):
3247 def __init__(self, offset):
3248 self.offset = timedelta(minutes=offset)
3249 def utcoffset(self, dt):
3250 return self.offset
3251
3252 cls = self.theclass
3253 for offset, legit in ((-1440, False),
3254 (-1439, True),
3255 (1439, True),
3256 (1440, False)):
3257 if cls is time:
3258 t = cls(1, 2, 3, tzinfo=Edgy(offset))
3259 elif cls is datetime:
3260 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
3261 else:
3262 assert 0, "impossible"
3263 if legit:
3264 aofs = abs(offset)
3265 h, m = divmod(aofs, 60)
3266 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
3267 if isinstance(t, datetime):
3268 t = t.timetz()
3269 self.assertEqual(str(t), "01:02:03" + tag)
3270 else:
3271 self.assertRaises(ValueError, str, t)
3272
3273 def test_tzinfo_classes(self):
3274 cls = self.theclass
3275 class C1(tzinfo):
3276 def utcoffset(self, dt): return None
3277 def dst(self, dt): return None
3278 def tzname(self, dt): return None
3279 for t in (cls(1, 1, 1),
3280 cls(1, 1, 1, tzinfo=None),
3281 cls(1, 1, 1, tzinfo=C1())):
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003282 self.assertIsNone(t.utcoffset())
3283 self.assertIsNone(t.dst())
3284 self.assertIsNone(t.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003285
3286 class C3(tzinfo):
3287 def utcoffset(self, dt): return timedelta(minutes=-1439)
3288 def dst(self, dt): return timedelta(minutes=1439)
3289 def tzname(self, dt): return "aname"
3290 t = cls(1, 1, 1, tzinfo=C3())
3291 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
3292 self.assertEqual(t.dst(), timedelta(minutes=1439))
3293 self.assertEqual(t.tzname(), "aname")
3294
3295 # Wrong types.
3296 class C4(tzinfo):
3297 def utcoffset(self, dt): return "aname"
3298 def dst(self, dt): return 7
3299 def tzname(self, dt): return 0
3300 t = cls(1, 1, 1, tzinfo=C4())
3301 self.assertRaises(TypeError, t.utcoffset)
3302 self.assertRaises(TypeError, t.dst)
3303 self.assertRaises(TypeError, t.tzname)
3304
3305 # Offset out of range.
3306 class C6(tzinfo):
3307 def utcoffset(self, dt): return timedelta(hours=-24)
3308 def dst(self, dt): return timedelta(hours=24)
3309 t = cls(1, 1, 1, tzinfo=C6())
3310 self.assertRaises(ValueError, t.utcoffset)
3311 self.assertRaises(ValueError, t.dst)
3312
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04003313 # Not a whole number of seconds.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003314 class C7(tzinfo):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04003315 def utcoffset(self, dt): return timedelta(microseconds=61)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003316 def dst(self, dt): return timedelta(microseconds=-81)
3317 t = cls(1, 1, 1, tzinfo=C7())
Alexander Belopolsky018d3532017-07-31 10:26:50 -04003318 self.assertEqual(t.utcoffset(), timedelta(microseconds=61))
3319 self.assertEqual(t.dst(), timedelta(microseconds=-81))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003320
3321 def test_aware_compare(self):
3322 cls = self.theclass
3323
3324 # Ensure that utcoffset() gets ignored if the comparands have
3325 # the same tzinfo member.
3326 class OperandDependentOffset(tzinfo):
3327 def utcoffset(self, t):
3328 if t.minute < 10:
3329 # d0 and d1 equal after adjustment
3330 return timedelta(minutes=t.minute)
3331 else:
3332 # d2 off in the weeds
3333 return timedelta(minutes=59)
3334
3335 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
3336 d0 = base.replace(minute=3)
3337 d1 = base.replace(minute=9)
3338 d2 = base.replace(minute=11)
3339 for x in d0, d1, d2:
3340 for y in d0, d1, d2:
3341 for op in lt, le, gt, ge, eq, ne:
3342 got = op(x, y)
3343 expected = op(x.minute, y.minute)
3344 self.assertEqual(got, expected)
3345
3346 # However, if they're different members, uctoffset is not ignored.
3347 # Note that a time can't actually have an operand-depedent offset,
3348 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
3349 # so skip this test for time.
3350 if cls is not time:
3351 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3352 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3353 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3354 for x in d0, d1, d2:
3355 for y in d0, d1, d2:
3356 got = (x > y) - (x < y)
3357 if (x is d0 or x is d1) and (y is d0 or y is d1):
3358 expected = 0
3359 elif x is y is d2:
3360 expected = 0
3361 elif x is d2:
3362 expected = -1
3363 else:
3364 assert y is d2
3365 expected = 1
3366 self.assertEqual(got, expected)
3367
3368
3369# Testing time objects with a non-None tzinfo.
3370class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
3371 theclass = time
3372
3373 def test_empty(self):
3374 t = self.theclass()
3375 self.assertEqual(t.hour, 0)
3376 self.assertEqual(t.minute, 0)
3377 self.assertEqual(t.second, 0)
3378 self.assertEqual(t.microsecond, 0)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003379 self.assertIsNone(t.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003380
3381 def test_zones(self):
3382 est = FixedOffset(-300, "EST", 1)
3383 utc = FixedOffset(0, "UTC", -2)
3384 met = FixedOffset(60, "MET", 3)
3385 t1 = time( 7, 47, tzinfo=est)
3386 t2 = time(12, 47, tzinfo=utc)
3387 t3 = time(13, 47, tzinfo=met)
3388 t4 = time(microsecond=40)
3389 t5 = time(microsecond=40, tzinfo=utc)
3390
3391 self.assertEqual(t1.tzinfo, est)
3392 self.assertEqual(t2.tzinfo, utc)
3393 self.assertEqual(t3.tzinfo, met)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003394 self.assertIsNone(t4.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003395 self.assertEqual(t5.tzinfo, utc)
3396
3397 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
3398 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
3399 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003400 self.assertIsNone(t4.utcoffset())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003401 self.assertRaises(TypeError, t1.utcoffset, "no args")
3402
3403 self.assertEqual(t1.tzname(), "EST")
3404 self.assertEqual(t2.tzname(), "UTC")
3405 self.assertEqual(t3.tzname(), "MET")
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003406 self.assertIsNone(t4.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003407 self.assertRaises(TypeError, t1.tzname, "no args")
3408
3409 self.assertEqual(t1.dst(), timedelta(minutes=1))
3410 self.assertEqual(t2.dst(), timedelta(minutes=-2))
3411 self.assertEqual(t3.dst(), timedelta(minutes=3))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003412 self.assertIsNone(t4.dst())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003413 self.assertRaises(TypeError, t1.dst, "no args")
3414
3415 self.assertEqual(hash(t1), hash(t2))
3416 self.assertEqual(hash(t1), hash(t3))
3417 self.assertEqual(hash(t2), hash(t3))
3418
3419 self.assertEqual(t1, t2)
3420 self.assertEqual(t1, t3)
3421 self.assertEqual(t2, t3)
Alexander Belopolsky08313822012-06-15 20:19:47 -04003422 self.assertNotEqual(t4, t5) # mixed tz-aware & naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003423 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
3424 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
3425
3426 self.assertEqual(str(t1), "07:47:00-05:00")
3427 self.assertEqual(str(t2), "12:47:00+00:00")
3428 self.assertEqual(str(t3), "13:47:00+01:00")
3429 self.assertEqual(str(t4), "00:00:00.000040")
3430 self.assertEqual(str(t5), "00:00:00.000040+00:00")
3431
3432 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
3433 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
3434 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
3435 self.assertEqual(t4.isoformat(), "00:00:00.000040")
3436 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
3437
3438 d = 'datetime.time'
3439 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
3440 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
3441 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
3442 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
3443 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
3444
3445 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
3446 "07:47:00 %Z=EST %z=-0500")
3447 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
3448 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
3449
3450 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
3451 t1 = time(23, 59, tzinfo=yuck)
3452 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
3453 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
3454
3455 # Check that an invalid tzname result raises an exception.
3456 class Badtzname(tzinfo):
Alexander Belopolskye239d232010-12-08 23:31:48 +00003457 tz = 42
3458 def tzname(self, dt): return self.tz
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003459 t = time(2, 3, 4, tzinfo=Badtzname())
3460 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
3461 self.assertRaises(TypeError, t.strftime, "%Z")
3462
Alexander Belopolskye239d232010-12-08 23:31:48 +00003463 # Issue #6697:
Utkarsh Upadhyay287c5592017-07-21 02:14:54 +02003464 if '_Fast' in self.__class__.__name__:
Alexander Belopolskye239d232010-12-08 23:31:48 +00003465 Badtzname.tz = '\ud800'
3466 self.assertRaises(ValueError, t.strftime, "%Z")
3467
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003468 def test_hash_edge_cases(self):
3469 # Offsets that overflow a basic time.
3470 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
3471 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
3472 self.assertEqual(hash(t1), hash(t2))
3473
3474 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
3475 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
3476 self.assertEqual(hash(t1), hash(t2))
3477
3478 def test_pickling(self):
3479 # Try one without a tzinfo.
3480 args = 20, 59, 16, 64**2
3481 orig = self.theclass(*args)
3482 for pickler, unpickler, proto in pickle_choices:
3483 green = pickler.dumps(orig, proto)
3484 derived = unpickler.loads(green)
3485 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003486 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003487
3488 # Try one with a tzinfo.
3489 tinfo = PicklableFixedOffset(-300, 'cookie')
3490 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
3491 for pickler, unpickler, proto in pickle_choices:
3492 green = pickler.dumps(orig, proto)
3493 derived = unpickler.loads(green)
3494 self.assertEqual(orig, derived)
3495 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3496 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3497 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003498 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003499
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02003500 def test_compat_unpickle(self):
3501 tests = [
3502 b"cdatetime\ntime\n(S'\\x05\\x06\\x07\\x01\\xe2@'\n"
3503 b"ctest.datetimetester\nPicklableFixedOffset\n(tR"
3504 b"(dS'_FixedOffset__offset'\ncdatetime\ntimedelta\n"
3505 b"(I-1\nI68400\nI0\ntRs"
3506 b"S'_FixedOffset__dstoffset'\nNs"
3507 b"S'_FixedOffset__name'\nS'cookie'\nsbtR.",
3508
3509 b'cdatetime\ntime\n(U\x06\x05\x06\x07\x01\xe2@'
3510 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
3511 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
3512 b'(J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00tR'
3513 b'U\x17_FixedOffset__dstoffsetN'
3514 b'U\x12_FixedOffset__nameU\x06cookieubtR.',
3515
3516 b'\x80\x02cdatetime\ntime\nU\x06\x05\x06\x07\x01\xe2@'
3517 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
3518 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
3519 b'J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00\x87R'
3520 b'U\x17_FixedOffset__dstoffsetN'
3521 b'U\x12_FixedOffset__nameU\x06cookieub\x86R.',
3522 ]
3523
3524 tinfo = PicklableFixedOffset(-300, 'cookie')
3525 expected = self.theclass(5, 6, 7, 123456, tzinfo=tinfo)
3526 for data in tests:
3527 for loads in pickle_loads:
3528 derived = loads(data, encoding='latin1')
3529 self.assertEqual(derived, expected, repr(data))
3530 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3531 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3532 self.assertEqual(derived.tzname(), 'cookie')
3533
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003534 def test_more_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003535 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003536 cls = self.theclass
3537
3538 t = cls(0, tzinfo=FixedOffset(-300, ""))
3539 self.assertTrue(t)
3540
3541 t = cls(5, tzinfo=FixedOffset(-300, ""))
3542 self.assertTrue(t)
3543
3544 t = cls(5, tzinfo=FixedOffset(300, ""))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003545 self.assertTrue(t)
3546
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05003547 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
3548 self.assertTrue(t)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003549
3550 def test_replace(self):
3551 cls = self.theclass
3552 z100 = FixedOffset(100, "+100")
3553 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3554 args = [1, 2, 3, 4, z100]
3555 base = cls(*args)
3556 self.assertEqual(base, base.replace())
3557
3558 i = 0
3559 for name, newval in (("hour", 5),
3560 ("minute", 6),
3561 ("second", 7),
3562 ("microsecond", 8),
3563 ("tzinfo", zm200)):
3564 newargs = args[:]
3565 newargs[i] = newval
3566 expected = cls(*newargs)
3567 got = base.replace(**{name: newval})
3568 self.assertEqual(expected, got)
3569 i += 1
3570
3571 # Ensure we can get rid of a tzinfo.
3572 self.assertEqual(base.tzname(), "+100")
3573 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003574 self.assertIsNone(base2.tzinfo)
3575 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003576
3577 # Ensure we can add one.
3578 base3 = base2.replace(tzinfo=z100)
3579 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003580 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003581
3582 # Out of bounds.
3583 base = cls(1)
3584 self.assertRaises(ValueError, base.replace, hour=24)
3585 self.assertRaises(ValueError, base.replace, minute=-1)
3586 self.assertRaises(ValueError, base.replace, second=100)
3587 self.assertRaises(ValueError, base.replace, microsecond=1000000)
3588
3589 def test_mixed_compare(self):
3590 t1 = time(1, 2, 3)
3591 t2 = time(1, 2, 3)
3592 self.assertEqual(t1, t2)
3593 t2 = t2.replace(tzinfo=None)
3594 self.assertEqual(t1, t2)
3595 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3596 self.assertEqual(t1, t2)
3597 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04003598 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003599
3600 # In time w/ identical tzinfo objects, utcoffset is ignored.
3601 class Varies(tzinfo):
3602 def __init__(self):
3603 self.offset = timedelta(minutes=22)
3604 def utcoffset(self, t):
3605 self.offset += timedelta(minutes=1)
3606 return self.offset
3607
3608 v = Varies()
3609 t1 = t2.replace(tzinfo=v)
3610 t2 = t2.replace(tzinfo=v)
3611 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3612 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3613 self.assertEqual(t1, t2)
3614
3615 # But if they're not identical, it isn't ignored.
3616 t2 = t2.replace(tzinfo=Varies())
3617 self.assertTrue(t1 < t2) # t1's offset counter still going up
3618
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003619 def test_fromisoformat(self):
3620 time_examples = [
3621 (0, 0, 0, 0),
3622 (23, 59, 59, 999999),
3623 ]
3624
3625 hh = (9, 12, 20)
3626 mm = (5, 30)
3627 ss = (4, 45)
3628 usec = (0, 245000, 678901)
3629
3630 time_examples += list(itertools.product(hh, mm, ss, usec))
3631
3632 tzinfos = [None, timezone.utc,
3633 timezone(timedelta(hours=2)),
3634 timezone(timedelta(hours=6, minutes=27))]
3635
3636 for ttup in time_examples:
3637 for tzi in tzinfos:
3638 t = self.theclass(*ttup, tzinfo=tzi)
3639 tstr = t.isoformat()
3640
3641 with self.subTest(tstr=tstr):
3642 t_rt = self.theclass.fromisoformat(tstr)
3643 self.assertEqual(t, t_rt)
3644
3645 def test_fromisoformat_timezone(self):
3646 base_time = self.theclass(12, 30, 45, 217456)
3647
3648 tzoffsets = [
3649 timedelta(hours=5), timedelta(hours=2),
3650 timedelta(hours=6, minutes=27),
3651 timedelta(hours=12, minutes=32, seconds=30),
3652 timedelta(hours=2, minutes=4, seconds=9, microseconds=123456)
3653 ]
3654
3655 tzoffsets += [-1 * td for td in tzoffsets]
3656
3657 tzinfos = [None, timezone.utc,
3658 timezone(timedelta(hours=0))]
3659
3660 tzinfos += [timezone(td) for td in tzoffsets]
3661
3662 for tzi in tzinfos:
3663 t = base_time.replace(tzinfo=tzi)
3664 tstr = t.isoformat()
3665
3666 with self.subTest(tstr=tstr):
3667 t_rt = self.theclass.fromisoformat(tstr)
3668 assert t == t_rt, t_rt
3669
3670 def test_fromisoformat_timespecs(self):
3671 time_bases = [
3672 (8, 17, 45, 123456),
3673 (8, 17, 45, 0)
3674 ]
3675
3676 tzinfos = [None, timezone.utc,
3677 timezone(timedelta(hours=-5)),
3678 timezone(timedelta(hours=2)),
3679 timezone(timedelta(hours=6, minutes=27))]
3680
3681 timespecs = ['hours', 'minutes', 'seconds',
3682 'milliseconds', 'microseconds']
3683
3684 for ip, ts in enumerate(timespecs):
3685 for tzi in tzinfos:
3686 for t_tuple in time_bases:
3687 if ts == 'milliseconds':
3688 new_microseconds = 1000 * (t_tuple[-1] // 1000)
3689 t_tuple = t_tuple[0:-1] + (new_microseconds,)
3690
3691 t = self.theclass(*(t_tuple[0:(1 + ip)]), tzinfo=tzi)
3692 tstr = t.isoformat(timespec=ts)
3693 with self.subTest(tstr=tstr):
3694 t_rt = self.theclass.fromisoformat(tstr)
3695 self.assertEqual(t, t_rt)
3696
3697 def test_fromisoformat_fails(self):
3698 bad_strs = [
3699 '', # Empty string
Paul Ganssle096329f2018-08-23 11:06:20 -04003700 '12\ud80000', # Invalid separator - surrogate char
Paul Ganssle09dc2f52017-12-21 00:33:49 -05003701 '12:', # Ends on a separator
3702 '12:30:', # Ends on a separator
3703 '12:30:15.', # Ends on a separator
3704 '1', # Incomplete hours
3705 '12:3', # Incomplete minutes
3706 '12:30:1', # Incomplete seconds
3707 '1a:30:45.334034', # Invalid character in hours
3708 '12:a0:45.334034', # Invalid character in minutes
3709 '12:30:a5.334034', # Invalid character in seconds
3710 '12:30:45.1234', # Too many digits for milliseconds
3711 '12:30:45.1234567', # Too many digits for microseconds
3712 '12:30:45.123456+24:30', # Invalid time zone offset
3713 '12:30:45.123456-24:30', # Invalid negative offset
3714 '12:30:45', # Uses full-width unicode colons
3715 '12:30:45․123456', # Uses \u2024 in place of decimal point
3716 '12:30:45a', # Extra at tend of basic time
3717 '12:30:45.123a', # Extra at end of millisecond time
3718 '12:30:45.123456a', # Extra at end of microsecond time
3719 '12:30:45.123456+12:00:30a', # Extra at end of full time
3720 ]
3721
3722 for bad_str in bad_strs:
3723 with self.subTest(bad_str=bad_str):
3724 with self.assertRaises(ValueError):
3725 self.theclass.fromisoformat(bad_str)
3726
3727 def test_fromisoformat_fails_typeerror(self):
3728 # Test the fromisoformat fails when passed the wrong type
3729 import io
3730
3731 bad_types = [b'12:30:45', None, io.StringIO('12:30:45')]
3732
3733 for bad_type in bad_types:
3734 with self.assertRaises(TypeError):
3735 self.theclass.fromisoformat(bad_type)
3736
3737 def test_fromisoformat_subclass(self):
3738 class TimeSubclass(self.theclass):
3739 pass
3740
3741 tsc = TimeSubclass(12, 14, 45, 203745, tzinfo=timezone.utc)
3742 tsc_rt = TimeSubclass.fromisoformat(tsc.isoformat())
3743
3744 self.assertEqual(tsc, tsc_rt)
3745 self.assertIsInstance(tsc_rt, TimeSubclass)
3746
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003747 def test_subclass_timetz(self):
3748
3749 class C(self.theclass):
3750 theAnswer = 42
3751
3752 def __new__(cls, *args, **kws):
3753 temp = kws.copy()
3754 extra = temp.pop('extra')
3755 result = self.theclass.__new__(cls, *args, **temp)
3756 result.extra = extra
3757 return result
3758
3759 def newmeth(self, start):
3760 return start + self.hour + self.second
3761
3762 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3763
3764 dt1 = self.theclass(*args)
3765 dt2 = C(*args, **{'extra': 7})
3766
3767 self.assertEqual(dt2.__class__, C)
3768 self.assertEqual(dt2.theAnswer, 42)
3769 self.assertEqual(dt2.extra, 7)
3770 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3771 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
3772
3773
3774# Testing datetime objects with a non-None tzinfo.
3775
3776class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
3777 theclass = datetime
3778
3779 def test_trivial(self):
3780 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
3781 self.assertEqual(dt.year, 1)
3782 self.assertEqual(dt.month, 2)
3783 self.assertEqual(dt.day, 3)
3784 self.assertEqual(dt.hour, 4)
3785 self.assertEqual(dt.minute, 5)
3786 self.assertEqual(dt.second, 6)
3787 self.assertEqual(dt.microsecond, 7)
3788 self.assertEqual(dt.tzinfo, None)
3789
3790 def test_even_more_compare(self):
3791 # The test_compare() and test_more_compare() inherited from TestDate
3792 # and TestDateTime covered non-tzinfo cases.
3793
3794 # Smallest possible after UTC adjustment.
3795 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3796 # Largest possible after UTC adjustment.
3797 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3798 tzinfo=FixedOffset(-1439, ""))
3799
3800 # Make sure those compare correctly, and w/o overflow.
3801 self.assertTrue(t1 < t2)
3802 self.assertTrue(t1 != t2)
3803 self.assertTrue(t2 > t1)
3804
3805 self.assertEqual(t1, t1)
3806 self.assertEqual(t2, t2)
3807
3808 # Equal afer adjustment.
3809 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
3810 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
3811 self.assertEqual(t1, t2)
3812
3813 # Change t1 not to subtract a minute, and t1 should be larger.
3814 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
3815 self.assertTrue(t1 > t2)
3816
3817 # Change t1 to subtract 2 minutes, and t1 should be smaller.
3818 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
3819 self.assertTrue(t1 < t2)
3820
3821 # Back to the original t1, but make seconds resolve it.
3822 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3823 second=1)
3824 self.assertTrue(t1 > t2)
3825
3826 # Likewise, but make microseconds resolve it.
3827 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3828 microsecond=1)
3829 self.assertTrue(t1 > t2)
3830
Alexander Belopolsky08313822012-06-15 20:19:47 -04003831 # Make t2 naive and it should differ.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003832 t2 = self.theclass.min
Alexander Belopolsky08313822012-06-15 20:19:47 -04003833 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003834 self.assertEqual(t2, t2)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04003835 # and > comparison should fail
3836 with self.assertRaises(TypeError):
3837 t1 > t2
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003838
3839 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
3840 class Naive(tzinfo):
3841 def utcoffset(self, dt): return None
3842 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
Alexander Belopolsky08313822012-06-15 20:19:47 -04003843 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003844 self.assertEqual(t2, t2)
3845
3846 # OTOH, it's OK to compare two of these mixing the two ways of being
3847 # naive.
3848 t1 = self.theclass(5, 6, 7)
3849 self.assertEqual(t1, t2)
3850
3851 # Try a bogus uctoffset.
3852 class Bogus(tzinfo):
3853 def utcoffset(self, dt):
3854 return timedelta(minutes=1440) # out of bounds
3855 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
3856 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
3857 self.assertRaises(ValueError, lambda: t1 == t2)
3858
3859 def test_pickling(self):
3860 # Try one without a tzinfo.
3861 args = 6, 7, 23, 20, 59, 1, 64**2
3862 orig = self.theclass(*args)
3863 for pickler, unpickler, proto in pickle_choices:
3864 green = pickler.dumps(orig, proto)
3865 derived = unpickler.loads(green)
3866 self.assertEqual(orig, derived)
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003867 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003868
3869 # Try one with a tzinfo.
3870 tinfo = PicklableFixedOffset(-300, 'cookie')
3871 orig = self.theclass(*args, **{'tzinfo': tinfo})
3872 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
3873 for pickler, unpickler, proto in pickle_choices:
3874 green = pickler.dumps(orig, proto)
3875 derived = unpickler.loads(green)
3876 self.assertEqual(orig, derived)
3877 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3878 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3879 self.assertEqual(derived.tzname(), 'cookie')
Serhiy Storchaka546ce652016-11-22 00:29:42 +02003880 self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003881
Serhiy Storchaka8452ca12018-12-07 13:42:10 +02003882 def test_compat_unpickle(self):
3883 tests = [
3884 b'cdatetime\ndatetime\n'
3885 b"(S'\\x07\\xdf\\x0b\\x1b\\x14;\\x01\\x01\\xe2@'\n"
3886 b'ctest.datetimetester\nPicklableFixedOffset\n(tR'
3887 b"(dS'_FixedOffset__offset'\ncdatetime\ntimedelta\n"
3888 b'(I-1\nI68400\nI0\ntRs'
3889 b"S'_FixedOffset__dstoffset'\nNs"
3890 b"S'_FixedOffset__name'\nS'cookie'\nsbtR.",
3891
3892 b'cdatetime\ndatetime\n'
3893 b'(U\n\x07\xdf\x0b\x1b\x14;\x01\x01\xe2@'
3894 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
3895 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
3896 b'(J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00tR'
3897 b'U\x17_FixedOffset__dstoffsetN'
3898 b'U\x12_FixedOffset__nameU\x06cookieubtR.',
3899
3900 b'\x80\x02cdatetime\ndatetime\n'
3901 b'U\n\x07\xdf\x0b\x1b\x14;\x01\x01\xe2@'
3902 b'ctest.datetimetester\nPicklableFixedOffset\n)R'
3903 b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
3904 b'J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00\x87R'
3905 b'U\x17_FixedOffset__dstoffsetN'
3906 b'U\x12_FixedOffset__nameU\x06cookieub\x86R.',
3907 ]
3908 args = 2015, 11, 27, 20, 59, 1, 123456
3909 tinfo = PicklableFixedOffset(-300, 'cookie')
3910 expected = self.theclass(*args, **{'tzinfo': tinfo})
3911 for data in tests:
3912 for loads in pickle_loads:
3913 derived = loads(data, encoding='latin1')
3914 self.assertEqual(derived, expected)
3915 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3916 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3917 self.assertEqual(derived.tzname(), 'cookie')
3918
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003919 def test_extreme_hashes(self):
3920 # If an attempt is made to hash these via subtracting the offset
3921 # then hashing a datetime object, OverflowError results. The
3922 # Python implementation used to blow up here.
3923 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3924 hash(t)
3925 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3926 tzinfo=FixedOffset(-1439, ""))
3927 hash(t)
3928
3929 # OTOH, an OOB offset should blow up.
3930 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
3931 self.assertRaises(ValueError, hash, t)
3932
3933 def test_zones(self):
3934 est = FixedOffset(-300, "EST")
3935 utc = FixedOffset(0, "UTC")
3936 met = FixedOffset(60, "MET")
3937 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
3938 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
3939 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
3940 self.assertEqual(t1.tzinfo, est)
3941 self.assertEqual(t2.tzinfo, utc)
3942 self.assertEqual(t3.tzinfo, met)
3943 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
3944 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
3945 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
3946 self.assertEqual(t1.tzname(), "EST")
3947 self.assertEqual(t2.tzname(), "UTC")
3948 self.assertEqual(t3.tzname(), "MET")
3949 self.assertEqual(hash(t1), hash(t2))
3950 self.assertEqual(hash(t1), hash(t3))
3951 self.assertEqual(hash(t2), hash(t3))
3952 self.assertEqual(t1, t2)
3953 self.assertEqual(t1, t3)
3954 self.assertEqual(t2, t3)
3955 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
3956 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
3957 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
3958 d = 'datetime.datetime(2002, 3, 19, '
3959 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
3960 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
3961 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
3962
3963 def test_combine(self):
3964 met = FixedOffset(60, "MET")
3965 d = date(2002, 3, 4)
3966 tz = time(18, 45, 3, 1234, tzinfo=met)
3967 dt = datetime.combine(d, tz)
3968 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
3969 tzinfo=met))
3970
3971 def test_extract(self):
3972 met = FixedOffset(60, "MET")
3973 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
3974 self.assertEqual(dt.date(), date(2002, 3, 4))
3975 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
3976 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
3977
3978 def test_tz_aware_arithmetic(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003979 now = self.theclass.now()
3980 tz55 = FixedOffset(-330, "west 5:30")
3981 timeaware = now.time().replace(tzinfo=tz55)
3982 nowaware = self.theclass.combine(now.date(), timeaware)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003983 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003984 self.assertEqual(nowaware.timetz(), timeaware)
3985
3986 # Can't mix aware and non-aware.
3987 self.assertRaises(TypeError, lambda: now - nowaware)
3988 self.assertRaises(TypeError, lambda: nowaware - now)
3989
3990 # And adding datetime's doesn't make sense, aware or not.
3991 self.assertRaises(TypeError, lambda: now + nowaware)
3992 self.assertRaises(TypeError, lambda: nowaware + now)
3993 self.assertRaises(TypeError, lambda: nowaware + nowaware)
3994
3995 # Subtracting should yield 0.
3996 self.assertEqual(now - now, timedelta(0))
3997 self.assertEqual(nowaware - nowaware, timedelta(0))
3998
3999 # Adding a delta should preserve tzinfo.
4000 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
4001 nowawareplus = nowaware + delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004002 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004003 nowawareplus2 = delta + nowaware
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004004 self.assertIs(nowawareplus2.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004005 self.assertEqual(nowawareplus, nowawareplus2)
4006
4007 # that - delta should be what we started with, and that - what we
4008 # started with should be delta.
4009 diff = nowawareplus - delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004010 self.assertIs(diff.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004011 self.assertEqual(nowaware, diff)
4012 self.assertRaises(TypeError, lambda: delta - nowawareplus)
4013 self.assertEqual(nowawareplus - nowaware, delta)
4014
4015 # Make up a random timezone.
4016 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
4017 # Attach it to nowawareplus.
4018 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004019 self.assertIs(nowawareplus.tzinfo, tzr)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004020 # Make sure the difference takes the timezone adjustments into account.
4021 got = nowaware - nowawareplus
4022 # Expected: (nowaware base - nowaware offset) -
4023 # (nowawareplus base - nowawareplus offset) =
4024 # (nowaware base - nowawareplus base) +
4025 # (nowawareplus offset - nowaware offset) =
4026 # -delta + nowawareplus offset - nowaware offset
4027 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
4028 self.assertEqual(got, expected)
4029
4030 # Try max possible difference.
4031 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
4032 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
4033 tzinfo=FixedOffset(-1439, "max"))
4034 maxdiff = max - min
4035 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
4036 timedelta(minutes=2*1439))
4037 # Different tzinfo, but the same offset
4038 tza = timezone(HOUR, 'A')
4039 tzb = timezone(HOUR, 'B')
4040 delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
4041 self.assertEqual(delta, self.theclass.min - self.theclass.max)
4042
4043 def test_tzinfo_now(self):
4044 meth = self.theclass.now
4045 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4046 base = meth()
4047 # Try with and without naming the keyword.
4048 off42 = FixedOffset(42, "42")
4049 another = meth(off42)
4050 again = meth(tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004051 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004052 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
4053 # Bad argument with and w/o naming the keyword.
4054 self.assertRaises(TypeError, meth, 16)
4055 self.assertRaises(TypeError, meth, tzinfo=16)
4056 # Bad keyword name.
4057 self.assertRaises(TypeError, meth, tinfo=off42)
4058 # Too many args.
4059 self.assertRaises(TypeError, meth, off42, off42)
4060
4061 # We don't know which time zone we're in, and don't have a tzinfo
4062 # class to represent it, so seeing whether a tz argument actually
4063 # does a conversion is tricky.
4064 utc = FixedOffset(0, "utc", 0)
4065 for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
4066 timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
4067 for dummy in range(3):
4068 now = datetime.now(weirdtz)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004069 self.assertIs(now.tzinfo, weirdtz)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004070 utcnow = datetime.utcnow().replace(tzinfo=utc)
4071 now2 = utcnow.astimezone(weirdtz)
4072 if abs(now - now2) < timedelta(seconds=30):
4073 break
4074 # Else the code is broken, or more than 30 seconds passed between
4075 # calls; assuming the latter, just try again.
4076 else:
4077 # Three strikes and we're out.
4078 self.fail("utcnow(), now(tz), or astimezone() may be broken")
4079
4080 def test_tzinfo_fromtimestamp(self):
4081 import time
4082 meth = self.theclass.fromtimestamp
4083 ts = time.time()
4084 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4085 base = meth(ts)
4086 # Try with and without naming the keyword.
4087 off42 = FixedOffset(42, "42")
4088 another = meth(ts, off42)
4089 again = meth(ts, tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004090 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004091 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
4092 # Bad argument with and w/o naming the keyword.
4093 self.assertRaises(TypeError, meth, ts, 16)
4094 self.assertRaises(TypeError, meth, ts, tzinfo=16)
4095 # Bad keyword name.
4096 self.assertRaises(TypeError, meth, ts, tinfo=off42)
4097 # Too many args.
4098 self.assertRaises(TypeError, meth, ts, off42, off42)
4099 # Too few args.
4100 self.assertRaises(TypeError, meth)
4101
4102 # Try to make sure tz= actually does some conversion.
4103 timestamp = 1000000000
4104 utcdatetime = datetime.utcfromtimestamp(timestamp)
4105 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
4106 # But on some flavor of Mac, it's nowhere near that. So we can't have
4107 # any idea here what time that actually is, we can only test that
4108 # relative changes match.
4109 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
4110 tz = FixedOffset(utcoffset, "tz", 0)
4111 expected = utcdatetime + utcoffset
4112 got = datetime.fromtimestamp(timestamp, tz)
4113 self.assertEqual(expected, got.replace(tzinfo=None))
4114
4115 def test_tzinfo_utcnow(self):
4116 meth = self.theclass.utcnow
4117 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4118 base = meth()
4119 # Try with and without naming the keyword; for whatever reason,
4120 # utcnow() doesn't accept a tzinfo argument.
4121 off42 = FixedOffset(42, "42")
4122 self.assertRaises(TypeError, meth, off42)
4123 self.assertRaises(TypeError, meth, tzinfo=off42)
4124
4125 def test_tzinfo_utcfromtimestamp(self):
4126 import time
4127 meth = self.theclass.utcfromtimestamp
4128 ts = time.time()
4129 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4130 base = meth(ts)
4131 # Try with and without naming the keyword; for whatever reason,
4132 # utcfromtimestamp() doesn't accept a tzinfo argument.
4133 off42 = FixedOffset(42, "42")
4134 self.assertRaises(TypeError, meth, ts, off42)
4135 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
4136
4137 def test_tzinfo_timetuple(self):
4138 # TestDateTime tested most of this. datetime adds a twist to the
4139 # DST flag.
4140 class DST(tzinfo):
4141 def __init__(self, dstvalue):
4142 if isinstance(dstvalue, int):
4143 dstvalue = timedelta(minutes=dstvalue)
4144 self.dstvalue = dstvalue
4145 def dst(self, dt):
4146 return self.dstvalue
4147
4148 cls = self.theclass
4149 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
4150 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
4151 t = d.timetuple()
4152 self.assertEqual(1, t.tm_year)
4153 self.assertEqual(1, t.tm_mon)
4154 self.assertEqual(1, t.tm_mday)
4155 self.assertEqual(10, t.tm_hour)
4156 self.assertEqual(20, t.tm_min)
4157 self.assertEqual(30, t.tm_sec)
4158 self.assertEqual(0, t.tm_wday)
4159 self.assertEqual(1, t.tm_yday)
4160 self.assertEqual(flag, t.tm_isdst)
4161
4162 # dst() returns wrong type.
4163 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
4164
4165 # dst() at the edge.
4166 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
4167 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
4168
4169 # dst() out of range.
4170 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
4171 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
4172
4173 def test_utctimetuple(self):
4174 class DST(tzinfo):
4175 def __init__(self, dstvalue=0):
4176 if isinstance(dstvalue, int):
4177 dstvalue = timedelta(minutes=dstvalue)
4178 self.dstvalue = dstvalue
4179 def dst(self, dt):
4180 return self.dstvalue
4181
4182 cls = self.theclass
4183 # This can't work: DST didn't implement utcoffset.
4184 self.assertRaises(NotImplementedError,
4185 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
4186
4187 class UOFS(DST):
4188 def __init__(self, uofs, dofs=None):
4189 DST.__init__(self, dofs)
4190 self.uofs = timedelta(minutes=uofs)
4191 def utcoffset(self, dt):
4192 return self.uofs
4193
4194 for dstvalue in -33, 33, 0, None:
4195 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
4196 t = d.utctimetuple()
4197 self.assertEqual(d.year, t.tm_year)
4198 self.assertEqual(d.month, t.tm_mon)
4199 self.assertEqual(d.day, t.tm_mday)
4200 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
4201 self.assertEqual(13, t.tm_min)
4202 self.assertEqual(d.second, t.tm_sec)
4203 self.assertEqual(d.weekday(), t.tm_wday)
4204 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
4205 t.tm_yday)
4206 # Ensure tm_isdst is 0 regardless of what dst() says: DST
4207 # is never in effect for a UTC time.
4208 self.assertEqual(0, t.tm_isdst)
4209
4210 # For naive datetime, utctimetuple == timetuple except for isdst
4211 d = cls(1, 2, 3, 10, 20, 30, 40)
4212 t = d.utctimetuple()
4213 self.assertEqual(t[:-1], d.timetuple()[:-1])
4214 self.assertEqual(0, t.tm_isdst)
4215 # Same if utcoffset is None
4216 class NOFS(DST):
4217 def utcoffset(self, dt):
4218 return None
4219 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
4220 t = d.utctimetuple()
4221 self.assertEqual(t[:-1], d.timetuple()[:-1])
4222 self.assertEqual(0, t.tm_isdst)
4223 # Check that bad tzinfo is detected
4224 class BOFS(DST):
4225 def utcoffset(self, dt):
4226 return "EST"
4227 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
4228 self.assertRaises(TypeError, d.utctimetuple)
4229
4230 # Check that utctimetuple() is the same as
4231 # astimezone(utc).timetuple()
4232 d = cls(2010, 11, 13, 14, 15, 16, 171819)
4233 for tz in [timezone.min, timezone.utc, timezone.max]:
4234 dtz = d.replace(tzinfo=tz)
4235 self.assertEqual(dtz.utctimetuple()[:-1],
4236 dtz.astimezone(timezone.utc).timetuple()[:-1])
4237 # At the edges, UTC adjustment can produce years out-of-range
4238 # for a datetime object. Ensure that an OverflowError is
4239 # raised.
4240 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
4241 # That goes back 1 minute less than a full day.
4242 self.assertRaises(OverflowError, tiny.utctimetuple)
4243
4244 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
4245 # That goes forward 1 minute less than a full day.
4246 self.assertRaises(OverflowError, huge.utctimetuple)
4247 # More overflow cases
4248 tiny = cls.min.replace(tzinfo=timezone(MINUTE))
4249 self.assertRaises(OverflowError, tiny.utctimetuple)
4250 huge = cls.max.replace(tzinfo=timezone(-MINUTE))
4251 self.assertRaises(OverflowError, huge.utctimetuple)
4252
4253 def test_tzinfo_isoformat(self):
4254 zero = FixedOffset(0, "+00:00")
4255 plus = FixedOffset(220, "+03:40")
4256 minus = FixedOffset(-231, "-03:51")
4257 unknown = FixedOffset(None, "")
4258
4259 cls = self.theclass
4260 datestr = '0001-02-03'
4261 for ofs in None, zero, plus, minus, unknown:
4262 for us in 0, 987001:
4263 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
4264 timestr = '04:05:59' + (us and '.987001' or '')
4265 ofsstr = ofs is not None and d.tzname() or ''
4266 tailstr = timestr + ofsstr
4267 iso = d.isoformat()
4268 self.assertEqual(iso, datestr + 'T' + tailstr)
4269 self.assertEqual(iso, d.isoformat('T'))
4270 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
4271 self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
4272 self.assertEqual(str(d), datestr + ' ' + tailstr)
4273
4274 def test_replace(self):
4275 cls = self.theclass
4276 z100 = FixedOffset(100, "+100")
4277 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
4278 args = [1, 2, 3, 4, 5, 6, 7, z100]
4279 base = cls(*args)
4280 self.assertEqual(base, base.replace())
4281
4282 i = 0
4283 for name, newval in (("year", 2),
4284 ("month", 3),
4285 ("day", 4),
4286 ("hour", 5),
4287 ("minute", 6),
4288 ("second", 7),
4289 ("microsecond", 8),
4290 ("tzinfo", zm200)):
4291 newargs = args[:]
4292 newargs[i] = newval
4293 expected = cls(*newargs)
4294 got = base.replace(**{name: newval})
4295 self.assertEqual(expected, got)
4296 i += 1
4297
4298 # Ensure we can get rid of a tzinfo.
4299 self.assertEqual(base.tzname(), "+100")
4300 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004301 self.assertIsNone(base2.tzinfo)
4302 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004303
4304 # Ensure we can add one.
4305 base3 = base2.replace(tzinfo=z100)
4306 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004307 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004308
4309 # Out of bounds.
4310 base = cls(2000, 2, 29)
4311 self.assertRaises(ValueError, base.replace, year=2001)
4312
4313 def test_more_astimezone(self):
4314 # The inherited test_astimezone covered some trivial and error cases.
4315 fnone = FixedOffset(None, "None")
4316 f44m = FixedOffset(44, "44")
4317 fm5h = FixedOffset(-timedelta(hours=5), "m300")
4318
4319 dt = self.theclass.now(tz=f44m)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004320 self.assertIs(dt.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004321 # Replacing with degenerate tzinfo raises an exception.
4322 self.assertRaises(ValueError, dt.astimezone, fnone)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004323 # Replacing with same tzinfo makes no change.
4324 x = dt.astimezone(dt.tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004325 self.assertIs(x.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004326 self.assertEqual(x.date(), dt.date())
4327 self.assertEqual(x.time(), dt.time())
4328
4329 # Replacing with different tzinfo does adjust.
4330 got = dt.astimezone(fm5h)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004331 self.assertIs(got.tzinfo, fm5h)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004332 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
4333 expected = dt - dt.utcoffset() # in effect, convert to UTC
4334 expected += fm5h.utcoffset(dt) # and from there to local time
4335 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
4336 self.assertEqual(got.date(), expected.date())
4337 self.assertEqual(got.time(), expected.time())
4338 self.assertEqual(got.timetz(), expected.timetz())
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004339 self.assertIs(got.tzinfo, expected.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004340 self.assertEqual(got, expected)
4341
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004342 @support.run_with_tz('UTC')
4343 def test_astimezone_default_utc(self):
4344 dt = self.theclass.now(timezone.utc)
4345 self.assertEqual(dt.astimezone(None), dt)
4346 self.assertEqual(dt.astimezone(), dt)
4347
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004348 # Note that offset in TZ variable has the opposite sign to that
4349 # produced by %z directive.
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004350 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4351 def test_astimezone_default_eastern(self):
4352 dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
4353 local = dt.astimezone()
4354 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004355 self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004356 dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
4357 local = dt.astimezone()
4358 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04004359 self.assertEqual(local.strftime("%z %Z"), "-0400 EDT")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04004360
Alexander Belopolsky1dcf4f92016-03-25 15:42:59 -04004361 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4362 def test_astimezone_default_near_fold(self):
4363 # Issue #26616.
4364 u = datetime(2015, 11, 1, 5, tzinfo=timezone.utc)
4365 t = u.astimezone()
4366 s = t.astimezone()
4367 self.assertEqual(t.tzinfo, s.tzinfo)
4368
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004369 def test_aware_subtract(self):
4370 cls = self.theclass
4371
4372 # Ensure that utcoffset() is ignored when the operands have the
4373 # same tzinfo member.
4374 class OperandDependentOffset(tzinfo):
4375 def utcoffset(self, t):
4376 if t.minute < 10:
4377 # d0 and d1 equal after adjustment
4378 return timedelta(minutes=t.minute)
4379 else:
4380 # d2 off in the weeds
4381 return timedelta(minutes=59)
4382
4383 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
4384 d0 = base.replace(minute=3)
4385 d1 = base.replace(minute=9)
4386 d2 = base.replace(minute=11)
4387 for x in d0, d1, d2:
4388 for y in d0, d1, d2:
4389 got = x - y
4390 expected = timedelta(minutes=x.minute - y.minute)
4391 self.assertEqual(got, expected)
4392
4393 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
4394 # ignored.
4395 base = cls(8, 9, 10, 11, 12, 13, 14)
4396 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
4397 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
4398 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
4399 for x in d0, d1, d2:
4400 for y in d0, d1, d2:
4401 got = x - y
4402 if (x is d0 or x is d1) and (y is d0 or y is d1):
4403 expected = timedelta(0)
4404 elif x is y is d2:
4405 expected = timedelta(0)
4406 elif x is d2:
4407 expected = timedelta(minutes=(11-59)-0)
4408 else:
4409 assert y is d2
4410 expected = timedelta(minutes=0-(11-59))
4411 self.assertEqual(got, expected)
4412
4413 def test_mixed_compare(self):
4414 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
4415 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
4416 self.assertEqual(t1, t2)
4417 t2 = t2.replace(tzinfo=None)
4418 self.assertEqual(t1, t2)
4419 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
4420 self.assertEqual(t1, t2)
4421 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04004422 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004423
4424 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
4425 class Varies(tzinfo):
4426 def __init__(self):
4427 self.offset = timedelta(minutes=22)
4428 def utcoffset(self, t):
4429 self.offset += timedelta(minutes=1)
4430 return self.offset
4431
4432 v = Varies()
4433 t1 = t2.replace(tzinfo=v)
4434 t2 = t2.replace(tzinfo=v)
4435 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
4436 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
4437 self.assertEqual(t1, t2)
4438
4439 # But if they're not identical, it isn't ignored.
4440 t2 = t2.replace(tzinfo=Varies())
4441 self.assertTrue(t1 < t2) # t1's offset counter still going up
4442
4443 def test_subclass_datetimetz(self):
4444
4445 class C(self.theclass):
4446 theAnswer = 42
4447
4448 def __new__(cls, *args, **kws):
4449 temp = kws.copy()
4450 extra = temp.pop('extra')
4451 result = self.theclass.__new__(cls, *args, **temp)
4452 result.extra = extra
4453 return result
4454
4455 def newmeth(self, start):
4456 return start + self.hour + self.year
4457
4458 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
4459
4460 dt1 = self.theclass(*args)
4461 dt2 = C(*args, **{'extra': 7})
4462
4463 self.assertEqual(dt2.__class__, C)
4464 self.assertEqual(dt2.theAnswer, 42)
4465 self.assertEqual(dt2.extra, 7)
4466 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
4467 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
4468
4469# Pain to set up DST-aware tzinfo classes.
4470
4471def first_sunday_on_or_after(dt):
4472 days_to_go = 6 - dt.weekday()
4473 if days_to_go:
4474 dt += timedelta(days_to_go)
4475 return dt
4476
4477ZERO = timedelta(0)
4478MINUTE = timedelta(minutes=1)
4479HOUR = timedelta(hours=1)
4480DAY = timedelta(days=1)
4481# In the US, DST starts at 2am (standard time) on the first Sunday in April.
4482DSTSTART = datetime(1, 4, 1, 2)
4483# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
4484# which is the first Sunday on or after Oct 25. Because we view 1:MM as
4485# being standard time on that day, there is no spelling in local time of
4486# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
4487DSTEND = datetime(1, 10, 25, 1)
4488
4489class USTimeZone(tzinfo):
4490
4491 def __init__(self, hours, reprname, stdname, dstname):
4492 self.stdoffset = timedelta(hours=hours)
4493 self.reprname = reprname
4494 self.stdname = stdname
4495 self.dstname = dstname
4496
4497 def __repr__(self):
4498 return self.reprname
4499
4500 def tzname(self, dt):
4501 if self.dst(dt):
4502 return self.dstname
4503 else:
4504 return self.stdname
4505
4506 def utcoffset(self, dt):
4507 return self.stdoffset + self.dst(dt)
4508
4509 def dst(self, dt):
4510 if dt is None or dt.tzinfo is None:
4511 # An exception instead may be sensible here, in one or more of
4512 # the cases.
4513 return ZERO
4514 assert dt.tzinfo is self
4515
4516 # Find first Sunday in April.
4517 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
4518 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
4519
4520 # Find last Sunday in October.
4521 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
4522 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
4523
4524 # Can't compare naive to aware objects, so strip the timezone from
4525 # dt first.
4526 if start <= dt.replace(tzinfo=None) < end:
4527 return HOUR
4528 else:
4529 return ZERO
4530
4531Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
4532Central = USTimeZone(-6, "Central", "CST", "CDT")
4533Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
4534Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
4535utc_real = FixedOffset(0, "UTC", 0)
4536# For better test coverage, we want another flavor of UTC that's west of
4537# the Eastern and Pacific timezones.
4538utc_fake = FixedOffset(-12*60, "UTCfake", 0)
4539
4540class TestTimezoneConversions(unittest.TestCase):
4541 # The DST switch times for 2002, in std time.
4542 dston = datetime(2002, 4, 7, 2)
4543 dstoff = datetime(2002, 10, 27, 1)
4544
4545 theclass = datetime
4546
4547 # Check a time that's inside DST.
4548 def checkinside(self, dt, tz, utc, dston, dstoff):
4549 self.assertEqual(dt.dst(), HOUR)
4550
4551 # Conversion to our own timezone is always an identity.
4552 self.assertEqual(dt.astimezone(tz), dt)
4553
4554 asutc = dt.astimezone(utc)
4555 there_and_back = asutc.astimezone(tz)
4556
4557 # Conversion to UTC and back isn't always an identity here,
4558 # because there are redundant spellings (in local time) of
4559 # UTC time when DST begins: the clock jumps from 1:59:59
4560 # to 3:00:00, and a local time of 2:MM:SS doesn't really
4561 # make sense then. The classes above treat 2:MM:SS as
4562 # daylight time then (it's "after 2am"), really an alias
4563 # for 1:MM:SS standard time. The latter form is what
4564 # conversion back from UTC produces.
4565 if dt.date() == dston.date() and dt.hour == 2:
4566 # We're in the redundant hour, and coming back from
4567 # UTC gives the 1:MM:SS standard-time spelling.
4568 self.assertEqual(there_and_back + HOUR, dt)
4569 # Although during was considered to be in daylight
4570 # time, there_and_back is not.
4571 self.assertEqual(there_and_back.dst(), ZERO)
4572 # They're the same times in UTC.
4573 self.assertEqual(there_and_back.astimezone(utc),
4574 dt.astimezone(utc))
4575 else:
4576 # We're not in the redundant hour.
4577 self.assertEqual(dt, there_and_back)
4578
4579 # Because we have a redundant spelling when DST begins, there is
Ezio Melotti3b3499b2011-03-16 11:35:38 +02004580 # (unfortunately) an hour when DST ends that can't be spelled at all in
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004581 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
4582 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
4583 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
4584 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
4585 # expressed in local time. Nevertheless, we want conversion back
4586 # from UTC to mimic the local clock's "repeat an hour" behavior.
4587 nexthour_utc = asutc + HOUR
4588 nexthour_tz = nexthour_utc.astimezone(tz)
4589 if dt.date() == dstoff.date() and dt.hour == 0:
4590 # We're in the hour before the last DST hour. The last DST hour
4591 # is ineffable. We want the conversion back to repeat 1:MM.
4592 self.assertEqual(nexthour_tz, dt.replace(hour=1))
4593 nexthour_utc += HOUR
4594 nexthour_tz = nexthour_utc.astimezone(tz)
4595 self.assertEqual(nexthour_tz, dt.replace(hour=1))
4596 else:
4597 self.assertEqual(nexthour_tz - dt, HOUR)
4598
4599 # Check a time that's outside DST.
4600 def checkoutside(self, dt, tz, utc):
4601 self.assertEqual(dt.dst(), ZERO)
4602
4603 # Conversion to our own timezone is always an identity.
4604 self.assertEqual(dt.astimezone(tz), dt)
4605
4606 # Converting to UTC and back is an identity too.
4607 asutc = dt.astimezone(utc)
4608 there_and_back = asutc.astimezone(tz)
4609 self.assertEqual(dt, there_and_back)
4610
4611 def convert_between_tz_and_utc(self, tz, utc):
4612 dston = self.dston.replace(tzinfo=tz)
4613 # Because 1:MM on the day DST ends is taken as being standard time,
4614 # there is no spelling in tz for the last hour of daylight time.
4615 # For purposes of the test, the last hour of DST is 0:MM, which is
4616 # taken as being daylight time (and 1:MM is taken as being standard
4617 # time).
4618 dstoff = self.dstoff.replace(tzinfo=tz)
4619 for delta in (timedelta(weeks=13),
4620 DAY,
4621 HOUR,
4622 timedelta(minutes=1),
4623 timedelta(microseconds=1)):
4624
4625 self.checkinside(dston, tz, utc, dston, dstoff)
4626 for during in dston + delta, dstoff - delta:
4627 self.checkinside(during, tz, utc, dston, dstoff)
4628
4629 self.checkoutside(dstoff, tz, utc)
4630 for outside in dston - delta, dstoff + delta:
4631 self.checkoutside(outside, tz, utc)
4632
4633 def test_easy(self):
4634 # Despite the name of this test, the endcases are excruciating.
4635 self.convert_between_tz_and_utc(Eastern, utc_real)
4636 self.convert_between_tz_and_utc(Pacific, utc_real)
4637 self.convert_between_tz_and_utc(Eastern, utc_fake)
4638 self.convert_between_tz_and_utc(Pacific, utc_fake)
4639 # The next is really dancing near the edge. It works because
4640 # Pacific and Eastern are far enough apart that their "problem
4641 # hours" don't overlap.
4642 self.convert_between_tz_and_utc(Eastern, Pacific)
4643 self.convert_between_tz_and_utc(Pacific, Eastern)
4644 # OTOH, these fail! Don't enable them. The difficulty is that
4645 # the edge case tests assume that every hour is representable in
4646 # the "utc" class. This is always true for a fixed-offset tzinfo
4647 # class (lke utc_real and utc_fake), but not for Eastern or Central.
4648 # For these adjacent DST-aware time zones, the range of time offsets
4649 # tested ends up creating hours in the one that aren't representable
4650 # in the other. For the same reason, we would see failures in the
4651 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
4652 # offset deltas in convert_between_tz_and_utc().
4653 #
4654 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
4655 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
4656
4657 def test_tricky(self):
4658 # 22:00 on day before daylight starts.
4659 fourback = self.dston - timedelta(hours=4)
4660 ninewest = FixedOffset(-9*60, "-0900", 0)
4661 fourback = fourback.replace(tzinfo=ninewest)
4662 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
4663 # 2", we should get the 3 spelling.
4664 # If we plug 22:00 the day before into Eastern, it "looks like std
4665 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
4666 # to 22:00 lands on 2:00, which makes no sense in local time (the
4667 # local clock jumps from 1 to 3). The point here is to make sure we
4668 # get the 3 spelling.
4669 expected = self.dston.replace(hour=3)
4670 got = fourback.astimezone(Eastern).replace(tzinfo=None)
4671 self.assertEqual(expected, got)
4672
4673 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
4674 # case we want the 1:00 spelling.
4675 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
4676 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
4677 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
4678 # spelling.
4679 expected = self.dston.replace(hour=1)
4680 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
4681 self.assertEqual(expected, got)
4682
4683 # Now on the day DST ends, we want "repeat an hour" behavior.
4684 # UTC 4:MM 5:MM 6:MM 7:MM checking these
4685 # EST 23:MM 0:MM 1:MM 2:MM
4686 # EDT 0:MM 1:MM 2:MM 3:MM
4687 # wall 0:MM 1:MM 1:MM 2:MM against these
4688 for utc in utc_real, utc_fake:
4689 for tz in Eastern, Pacific:
4690 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
4691 # Convert that to UTC.
4692 first_std_hour -= tz.utcoffset(None)
4693 # Adjust for possibly fake UTC.
4694 asutc = first_std_hour + utc.utcoffset(None)
4695 # First UTC hour to convert; this is 4:00 when utc=utc_real &
4696 # tz=Eastern.
4697 asutcbase = asutc.replace(tzinfo=utc)
4698 for tzhour in (0, 1, 1, 2):
4699 expectedbase = self.dstoff.replace(hour=tzhour)
4700 for minute in 0, 30, 59:
4701 expected = expectedbase.replace(minute=minute)
4702 asutc = asutcbase.replace(minute=minute)
4703 astz = asutc.astimezone(tz)
4704 self.assertEqual(astz.replace(tzinfo=None), expected)
4705 asutcbase += HOUR
4706
4707
4708 def test_bogus_dst(self):
4709 class ok(tzinfo):
4710 def utcoffset(self, dt): return HOUR
4711 def dst(self, dt): return HOUR
4712
4713 now = self.theclass.now().replace(tzinfo=utc_real)
4714 # Doesn't blow up.
4715 now.astimezone(ok())
4716
4717 # Does blow up.
4718 class notok(ok):
4719 def dst(self, dt): return None
4720 self.assertRaises(ValueError, now.astimezone, notok())
4721
4722 # Sometimes blow up. In the following, tzinfo.dst()
4723 # implementation may return None or not None depending on
4724 # whether DST is assumed to be in effect. In this situation,
4725 # a ValueError should be raised by astimezone().
4726 class tricky_notok(ok):
4727 def dst(self, dt):
4728 if dt.year == 2000:
4729 return None
4730 else:
4731 return 10*HOUR
4732 dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
4733 self.assertRaises(ValueError, dt.astimezone, tricky_notok())
4734
4735 def test_fromutc(self):
4736 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
4737 now = datetime.utcnow().replace(tzinfo=utc_real)
4738 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
4739 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
4740 enow = Eastern.fromutc(now) # doesn't blow up
4741 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
4742 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
4743 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
4744
4745 # Always converts UTC to standard time.
4746 class FauxUSTimeZone(USTimeZone):
4747 def fromutc(self, dt):
4748 return dt + self.stdoffset
4749 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
4750
4751 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
4752 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
4753 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
4754
4755 # Check around DST start.
4756 start = self.dston.replace(hour=4, tzinfo=Eastern)
4757 fstart = start.replace(tzinfo=FEastern)
4758 for wall in 23, 0, 1, 3, 4, 5:
4759 expected = start.replace(hour=wall)
4760 if wall == 23:
4761 expected -= timedelta(days=1)
4762 got = Eastern.fromutc(start)
4763 self.assertEqual(expected, got)
4764
4765 expected = fstart + FEastern.stdoffset
4766 got = FEastern.fromutc(fstart)
4767 self.assertEqual(expected, got)
4768
4769 # Ensure astimezone() calls fromutc() too.
4770 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
4771 self.assertEqual(expected, got)
4772
4773 start += HOUR
4774 fstart += HOUR
4775
4776 # Check around DST end.
4777 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
4778 fstart = start.replace(tzinfo=FEastern)
4779 for wall in 0, 1, 1, 2, 3, 4:
4780 expected = start.replace(hour=wall)
4781 got = Eastern.fromutc(start)
4782 self.assertEqual(expected, got)
4783
4784 expected = fstart + FEastern.stdoffset
4785 got = FEastern.fromutc(fstart)
4786 self.assertEqual(expected, got)
4787
4788 # Ensure astimezone() calls fromutc() too.
4789 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
4790 self.assertEqual(expected, got)
4791
4792 start += HOUR
4793 fstart += HOUR
4794
4795
4796#############################################################################
4797# oddballs
4798
4799class Oddballs(unittest.TestCase):
4800
4801 def test_bug_1028306(self):
4802 # Trying to compare a date to a datetime should act like a mixed-
4803 # type comparison, despite that datetime is a subclass of date.
4804 as_date = date.today()
4805 as_datetime = datetime.combine(as_date, time())
4806 self.assertTrue(as_date != as_datetime)
4807 self.assertTrue(as_datetime != as_date)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02004808 self.assertFalse(as_date == as_datetime)
4809 self.assertFalse(as_datetime == as_date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004810 self.assertRaises(TypeError, lambda: as_date < as_datetime)
4811 self.assertRaises(TypeError, lambda: as_datetime < as_date)
4812 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
4813 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
4814 self.assertRaises(TypeError, lambda: as_date > as_datetime)
4815 self.assertRaises(TypeError, lambda: as_datetime > as_date)
4816 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
4817 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
4818
Raymond Hettinger15f44ab2016-08-30 10:47:49 -07004819 # Nevertheless, comparison should work with the base-class (date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004820 # projection if use of a date method is forced.
4821 self.assertEqual(as_date.__eq__(as_datetime), True)
4822 different_day = (as_date.day + 1) % 20 + 1
4823 as_different = as_datetime.replace(day= different_day)
4824 self.assertEqual(as_date.__eq__(as_different), False)
4825
4826 # And date should compare with other subclasses of date. If a
4827 # subclass wants to stop this, it's up to the subclass to do so.
4828 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
4829 self.assertEqual(as_date, date_sc)
4830 self.assertEqual(date_sc, as_date)
4831
4832 # Ditto for datetimes.
4833 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
4834 as_date.day, 0, 0, 0)
4835 self.assertEqual(as_datetime, datetime_sc)
4836 self.assertEqual(datetime_sc, as_datetime)
4837
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004838 def test_extra_attributes(self):
4839 for x in [date.today(),
4840 time(),
4841 datetime.utcnow(),
4842 timedelta(),
4843 tzinfo(),
4844 timezone(timedelta())]:
4845 with self.assertRaises(AttributeError):
4846 x.abc = 1
4847
4848 def test_check_arg_types(self):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004849 class Number:
4850 def __init__(self, value):
4851 self.value = value
4852 def __int__(self):
4853 return self.value
4854
4855 for xx in [decimal.Decimal(10),
4856 decimal.Decimal('10.9'),
4857 Number(10)]:
4858 self.assertEqual(datetime(10, 10, 10, 10, 10, 10, 10),
4859 datetime(xx, xx, xx, xx, xx, xx, xx))
4860
4861 with self.assertRaisesRegex(TypeError, '^an integer is required '
R David Murray44b548d2016-09-08 13:59:53 -04004862 r'\(got type str\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004863 datetime(10, 10, '10')
4864
4865 f10 = Number(10.9)
4866 with self.assertRaisesRegex(TypeError, '^__int__ returned non-int '
R David Murray44b548d2016-09-08 13:59:53 -04004867 r'\(type float\)$'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04004868 datetime(10, 10, f10)
4869
4870 class Float(float):
4871 pass
4872 s10 = Float(10.9)
4873 with self.assertRaisesRegex(TypeError, '^integer argument expected, '
4874 'got float$'):
4875 datetime(10, 10, s10)
4876
4877 with self.assertRaises(TypeError):
4878 datetime(10., 10, 10)
4879 with self.assertRaises(TypeError):
4880 datetime(10, 10., 10)
4881 with self.assertRaises(TypeError):
4882 datetime(10, 10, 10.)
4883 with self.assertRaises(TypeError):
4884 datetime(10, 10, 10, 10.)
4885 with self.assertRaises(TypeError):
4886 datetime(10, 10, 10, 10, 10.)
4887 with self.assertRaises(TypeError):
4888 datetime(10, 10, 10, 10, 10, 10.)
4889 with self.assertRaises(TypeError):
4890 datetime(10, 10, 10, 10, 10, 10, 10.)
4891
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004892#############################################################################
4893# Local Time Disambiguation
4894
4895# An experimental reimplementation of fromutc that respects the "fold" flag.
4896
4897class tzinfo2(tzinfo):
4898
4899 def fromutc(self, dt):
4900 "datetime in UTC -> datetime in local time."
4901
4902 if not isinstance(dt, datetime):
4903 raise TypeError("fromutc() requires a datetime argument")
4904 if dt.tzinfo is not self:
4905 raise ValueError("dt.tzinfo is not self")
4906 # Returned value satisfies
4907 # dt + ldt.utcoffset() = ldt
4908 off0 = dt.replace(fold=0).utcoffset()
4909 off1 = dt.replace(fold=1).utcoffset()
4910 if off0 is None or off1 is None or dt.dst() is None:
4911 raise ValueError
4912 if off0 == off1:
4913 ldt = dt + off0
4914 off1 = ldt.utcoffset()
4915 if off0 == off1:
4916 return ldt
4917 # Now, we discovered both possible offsets, so
4918 # we can just try four possible solutions:
4919 for off in [off0, off1]:
4920 ldt = dt + off
4921 if ldt.utcoffset() == off:
4922 return ldt
4923 ldt = ldt.replace(fold=1)
4924 if ldt.utcoffset() == off:
4925 return ldt
4926
4927 raise ValueError("No suitable local time found")
4928
4929# Reimplementing simplified US timezones to respect the "fold" flag:
4930
4931class USTimeZone2(tzinfo2):
4932
4933 def __init__(self, hours, reprname, stdname, dstname):
4934 self.stdoffset = timedelta(hours=hours)
4935 self.reprname = reprname
4936 self.stdname = stdname
4937 self.dstname = dstname
4938
4939 def __repr__(self):
4940 return self.reprname
4941
4942 def tzname(self, dt):
4943 if self.dst(dt):
4944 return self.dstname
4945 else:
4946 return self.stdname
4947
4948 def utcoffset(self, dt):
4949 return self.stdoffset + self.dst(dt)
4950
4951 def dst(self, dt):
4952 if dt is None or dt.tzinfo is None:
4953 # An exception instead may be sensible here, in one or more of
4954 # the cases.
4955 return ZERO
4956 assert dt.tzinfo is self
4957
4958 # Find first Sunday in April.
4959 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
4960 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
4961
4962 # Find last Sunday in October.
4963 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
4964 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
4965
4966 # Can't compare naive to aware objects, so strip the timezone from
4967 # dt first.
4968 dt = dt.replace(tzinfo=None)
4969 if start + HOUR <= dt < end:
4970 # DST is in effect.
4971 return HOUR
4972 elif end <= dt < end + HOUR:
4973 # Fold (an ambiguous hour): use dt.fold to disambiguate.
4974 return ZERO if dt.fold else HOUR
4975 elif start <= dt < start + HOUR:
4976 # Gap (a non-existent hour): reverse the fold rule.
4977 return HOUR if dt.fold else ZERO
4978 else:
4979 # DST is off.
4980 return ZERO
4981
4982Eastern2 = USTimeZone2(-5, "Eastern2", "EST", "EDT")
4983Central2 = USTimeZone2(-6, "Central2", "CST", "CDT")
4984Mountain2 = USTimeZone2(-7, "Mountain2", "MST", "MDT")
4985Pacific2 = USTimeZone2(-8, "Pacific2", "PST", "PDT")
4986
4987# Europe_Vilnius_1941 tzinfo implementation reproduces the following
4988# 1941 transition from Olson's tzdist:
4989#
4990# Zone NAME GMTOFF RULES FORMAT [UNTIL]
4991# ZoneEurope/Vilnius 1:00 - CET 1940 Aug 3
4992# 3:00 - MSK 1941 Jun 24
4993# 1:00 C-Eur CE%sT 1944 Aug
4994#
4995# $ zdump -v Europe/Vilnius | grep 1941
4996# Europe/Vilnius Mon Jun 23 20:59:59 1941 UTC = Mon Jun 23 23:59:59 1941 MSK isdst=0 gmtoff=10800
4997# Europe/Vilnius Mon Jun 23 21:00:00 1941 UTC = Mon Jun 23 23:00:00 1941 CEST isdst=1 gmtoff=7200
4998
4999class Europe_Vilnius_1941(tzinfo):
5000 def _utc_fold(self):
5001 return [datetime(1941, 6, 23, 21, tzinfo=self), # Mon Jun 23 21:00:00 1941 UTC
5002 datetime(1941, 6, 23, 22, tzinfo=self)] # Mon Jun 23 22:00:00 1941 UTC
5003
5004 def _loc_fold(self):
5005 return [datetime(1941, 6, 23, 23, tzinfo=self), # Mon Jun 23 23:00:00 1941 MSK / CEST
5006 datetime(1941, 6, 24, 0, tzinfo=self)] # Mon Jun 24 00:00:00 1941 CEST
5007
5008 def utcoffset(self, dt):
5009 fold_start, fold_stop = self._loc_fold()
5010 if dt < fold_start:
5011 return 3 * HOUR
5012 if dt < fold_stop:
5013 return (2 if dt.fold else 3) * HOUR
5014 # if dt >= fold_stop
5015 return 2 * HOUR
5016
5017 def dst(self, dt):
5018 fold_start, fold_stop = self._loc_fold()
5019 if dt < fold_start:
5020 return 0 * HOUR
5021 if dt < fold_stop:
5022 return (1 if dt.fold else 0) * HOUR
5023 # if dt >= fold_stop
5024 return 1 * HOUR
5025
5026 def tzname(self, dt):
5027 fold_start, fold_stop = self._loc_fold()
5028 if dt < fold_start:
5029 return 'MSK'
5030 if dt < fold_stop:
5031 return ('MSK', 'CEST')[dt.fold]
5032 # if dt >= fold_stop
5033 return 'CEST'
5034
5035 def fromutc(self, dt):
5036 assert dt.fold == 0
5037 assert dt.tzinfo is self
5038 if dt.year != 1941:
5039 raise NotImplementedError
5040 fold_start, fold_stop = self._utc_fold()
5041 if dt < fold_start:
5042 return dt + 3 * HOUR
5043 if dt < fold_stop:
5044 return (dt + 2 * HOUR).replace(fold=1)
5045 # if dt >= fold_stop
5046 return dt + 2 * HOUR
5047
5048
5049class TestLocalTimeDisambiguation(unittest.TestCase):
5050
5051 def test_vilnius_1941_fromutc(self):
5052 Vilnius = Europe_Vilnius_1941()
5053
5054 gdt = datetime(1941, 6, 23, 20, 59, 59, tzinfo=timezone.utc)
5055 ldt = gdt.astimezone(Vilnius)
5056 self.assertEqual(ldt.strftime("%c %Z%z"),
5057 'Mon Jun 23 23:59:59 1941 MSK+0300')
5058 self.assertEqual(ldt.fold, 0)
5059 self.assertFalse(ldt.dst())
5060
5061 gdt = datetime(1941, 6, 23, 21, tzinfo=timezone.utc)
5062 ldt = gdt.astimezone(Vilnius)
5063 self.assertEqual(ldt.strftime("%c %Z%z"),
5064 'Mon Jun 23 23:00:00 1941 CEST+0200')
5065 self.assertEqual(ldt.fold, 1)
5066 self.assertTrue(ldt.dst())
5067
5068 gdt = datetime(1941, 6, 23, 22, tzinfo=timezone.utc)
5069 ldt = gdt.astimezone(Vilnius)
5070 self.assertEqual(ldt.strftime("%c %Z%z"),
5071 'Tue Jun 24 00:00:00 1941 CEST+0200')
5072 self.assertEqual(ldt.fold, 0)
5073 self.assertTrue(ldt.dst())
5074
5075 def test_vilnius_1941_toutc(self):
5076 Vilnius = Europe_Vilnius_1941()
5077
5078 ldt = datetime(1941, 6, 23, 22, 59, 59, tzinfo=Vilnius)
5079 gdt = ldt.astimezone(timezone.utc)
5080 self.assertEqual(gdt.strftime("%c %Z"),
5081 'Mon Jun 23 19:59:59 1941 UTC')
5082
5083 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius)
5084 gdt = ldt.astimezone(timezone.utc)
5085 self.assertEqual(gdt.strftime("%c %Z"),
5086 'Mon Jun 23 20:59:59 1941 UTC')
5087
5088 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius, fold=1)
5089 gdt = ldt.astimezone(timezone.utc)
5090 self.assertEqual(gdt.strftime("%c %Z"),
5091 'Mon Jun 23 21:59:59 1941 UTC')
5092
5093 ldt = datetime(1941, 6, 24, 0, tzinfo=Vilnius)
5094 gdt = ldt.astimezone(timezone.utc)
5095 self.assertEqual(gdt.strftime("%c %Z"),
5096 'Mon Jun 23 22:00:00 1941 UTC')
5097
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005098 def test_constructors(self):
5099 t = time(0, fold=1)
5100 dt = datetime(1, 1, 1, fold=1)
5101 self.assertEqual(t.fold, 1)
5102 self.assertEqual(dt.fold, 1)
5103 with self.assertRaises(TypeError):
5104 time(0, 0, 0, 0, None, 0)
5105
5106 def test_member(self):
5107 dt = datetime(1, 1, 1, fold=1)
5108 t = dt.time()
5109 self.assertEqual(t.fold, 1)
5110 t = dt.timetz()
5111 self.assertEqual(t.fold, 1)
5112
5113 def test_replace(self):
5114 t = time(0)
5115 dt = datetime(1, 1, 1)
5116 self.assertEqual(t.replace(fold=1).fold, 1)
5117 self.assertEqual(dt.replace(fold=1).fold, 1)
5118 self.assertEqual(t.replace(fold=0).fold, 0)
5119 self.assertEqual(dt.replace(fold=0).fold, 0)
5120 # Check that replacement of other fields does not change "fold".
5121 t = t.replace(fold=1, tzinfo=Eastern)
5122 dt = dt.replace(fold=1, tzinfo=Eastern)
5123 self.assertEqual(t.replace(tzinfo=None).fold, 1)
5124 self.assertEqual(dt.replace(tzinfo=None).fold, 1)
Serhiy Storchaka314d6fc2017-03-31 22:48:16 +03005125 # Out of bounds.
5126 with self.assertRaises(ValueError):
5127 t.replace(fold=2)
5128 with self.assertRaises(ValueError):
5129 dt.replace(fold=2)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005130 # Check that fold is a keyword-only argument
5131 with self.assertRaises(TypeError):
5132 t.replace(1, 1, 1, None, 1)
5133 with self.assertRaises(TypeError):
5134 dt.replace(1, 1, 1, 1, 1, 1, 1, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04005135
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005136 def test_comparison(self):
5137 t = time(0)
5138 dt = datetime(1, 1, 1)
5139 self.assertEqual(t, t.replace(fold=1))
5140 self.assertEqual(dt, dt.replace(fold=1))
5141
5142 def test_hash(self):
5143 t = time(0)
5144 dt = datetime(1, 1, 1)
5145 self.assertEqual(hash(t), hash(t.replace(fold=1)))
5146 self.assertEqual(hash(dt), hash(dt.replace(fold=1)))
5147
5148 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5149 def test_fromtimestamp(self):
5150 s = 1414906200
5151 dt0 = datetime.fromtimestamp(s)
5152 dt1 = datetime.fromtimestamp(s + 3600)
5153 self.assertEqual(dt0.fold, 0)
5154 self.assertEqual(dt1.fold, 1)
5155
5156 @support.run_with_tz('Australia/Lord_Howe')
5157 def test_fromtimestamp_lord_howe(self):
5158 tm = _time.localtime(1.4e9)
5159 if _time.strftime('%Z%z', tm) != 'LHST+1030':
5160 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
5161 # $ TZ=Australia/Lord_Howe date -r 1428158700
5162 # Sun Apr 5 01:45:00 LHDT 2015
5163 # $ TZ=Australia/Lord_Howe date -r 1428160500
5164 # Sun Apr 5 01:45:00 LHST 2015
5165 s = 1428158700
5166 t0 = datetime.fromtimestamp(s)
5167 t1 = datetime.fromtimestamp(s + 1800)
5168 self.assertEqual(t0, t1)
5169 self.assertEqual(t0.fold, 0)
5170 self.assertEqual(t1.fold, 1)
5171
Ammar Askar96d1e692018-07-25 09:54:58 -07005172 def test_fromtimestamp_low_fold_detection(self):
5173 # Ensure that fold detection doesn't cause an
5174 # OSError for really low values, see bpo-29097
5175 self.assertEqual(datetime.fromtimestamp(0).fold, 0)
5176
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005177 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5178 def test_timestamp(self):
5179 dt0 = datetime(2014, 11, 2, 1, 30)
5180 dt1 = dt0.replace(fold=1)
5181 self.assertEqual(dt0.timestamp() + 3600,
5182 dt1.timestamp())
5183
5184 @support.run_with_tz('Australia/Lord_Howe')
5185 def test_timestamp_lord_howe(self):
5186 tm = _time.localtime(1.4e9)
5187 if _time.strftime('%Z%z', tm) != 'LHST+1030':
5188 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
5189 t = datetime(2015, 4, 5, 1, 45)
5190 s0 = t.replace(fold=0).timestamp()
5191 s1 = t.replace(fold=1).timestamp()
5192 self.assertEqual(s0 + 1800, s1)
5193
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005194 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5195 def test_astimezone(self):
5196 dt0 = datetime(2014, 11, 2, 1, 30)
5197 dt1 = dt0.replace(fold=1)
5198 # Convert both naive instances to aware.
5199 adt0 = dt0.astimezone()
5200 adt1 = dt1.astimezone()
5201 # Check that the first instance in DST zone and the second in STD
5202 self.assertEqual(adt0.tzname(), 'EDT')
5203 self.assertEqual(adt1.tzname(), 'EST')
5204 self.assertEqual(adt0 + HOUR, adt1)
5205 # Aware instances with fixed offset tzinfo's always have fold=0
5206 self.assertEqual(adt0.fold, 0)
5207 self.assertEqual(adt1.fold, 0)
5208
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005209 def test_pickle_fold(self):
5210 t = time(fold=1)
5211 dt = datetime(1, 1, 1, fold=1)
5212 for pickler, unpickler, proto in pickle_choices:
5213 for x in [t, dt]:
5214 s = pickler.dumps(x, proto)
5215 y = unpickler.loads(s)
5216 self.assertEqual(x, y)
5217 self.assertEqual((0 if proto < 4 else x.fold), y.fold)
5218
5219 def test_repr(self):
5220 t = time(fold=1)
5221 dt = datetime(1, 1, 1, fold=1)
5222 self.assertEqual(repr(t), 'datetime.time(0, 0, fold=1)')
5223 self.assertEqual(repr(dt),
5224 'datetime.datetime(1, 1, 1, 0, 0, fold=1)')
5225
5226 def test_dst(self):
5227 # Let's first establish that things work in regular times.
5228 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
5229 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
5230 self.assertEqual(dt_summer.dst(), HOUR)
5231 self.assertEqual(dt_winter.dst(), ZERO)
5232 # The disambiguation flag is ignored
5233 self.assertEqual(dt_summer.replace(fold=1).dst(), HOUR)
5234 self.assertEqual(dt_winter.replace(fold=1).dst(), ZERO)
5235
5236 # Pick local time in the fold.
5237 for minute in [0, 30, 59]:
5238 dt = datetime(2002, 10, 27, 1, minute, tzinfo=Eastern2)
5239 # With fold=0 (the default) it is in DST.
5240 self.assertEqual(dt.dst(), HOUR)
5241 # With fold=1 it is in STD.
5242 self.assertEqual(dt.replace(fold=1).dst(), ZERO)
5243
5244 # Pick local time in the gap.
5245 for minute in [0, 30, 59]:
5246 dt = datetime(2002, 4, 7, 2, minute, tzinfo=Eastern2)
5247 # With fold=0 (the default) it is in STD.
5248 self.assertEqual(dt.dst(), ZERO)
5249 # With fold=1 it is in DST.
5250 self.assertEqual(dt.replace(fold=1).dst(), HOUR)
5251
5252
5253 def test_utcoffset(self):
5254 # Let's first establish that things work in regular times.
5255 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
5256 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
5257 self.assertEqual(dt_summer.utcoffset(), -4 * HOUR)
5258 self.assertEqual(dt_winter.utcoffset(), -5 * HOUR)
5259 # The disambiguation flag is ignored
5260 self.assertEqual(dt_summer.replace(fold=1).utcoffset(), -4 * HOUR)
5261 self.assertEqual(dt_winter.replace(fold=1).utcoffset(), -5 * HOUR)
5262
5263 def test_fromutc(self):
5264 # Let's first establish that things work in regular times.
5265 u_summer = datetime(2002, 10, 27, 6, tzinfo=Eastern2) - timedelta.resolution
5266 u_winter = datetime(2002, 10, 27, 7, tzinfo=Eastern2)
5267 t_summer = Eastern2.fromutc(u_summer)
5268 t_winter = Eastern2.fromutc(u_winter)
5269 self.assertEqual(t_summer, u_summer - 4 * HOUR)
5270 self.assertEqual(t_winter, u_winter - 5 * HOUR)
5271 self.assertEqual(t_summer.fold, 0)
5272 self.assertEqual(t_winter.fold, 0)
5273
5274 # What happens in the fall-back fold?
5275 u = datetime(2002, 10, 27, 5, 30, tzinfo=Eastern2)
5276 t0 = Eastern2.fromutc(u)
5277 u += HOUR
5278 t1 = Eastern2.fromutc(u)
5279 self.assertEqual(t0, t1)
5280 self.assertEqual(t0.fold, 0)
5281 self.assertEqual(t1.fold, 1)
5282 # The tricky part is when u is in the local fold:
5283 u = datetime(2002, 10, 27, 1, 30, tzinfo=Eastern2)
5284 t = Eastern2.fromutc(u)
5285 self.assertEqual((t.day, t.hour), (26, 21))
5286 # .. or gets into the local fold after a standard time adjustment
5287 u = datetime(2002, 10, 27, 6, 30, tzinfo=Eastern2)
5288 t = Eastern2.fromutc(u)
5289 self.assertEqual((t.day, t.hour), (27, 1))
5290
5291 # What happens in the spring-forward gap?
5292 u = datetime(2002, 4, 7, 2, 0, tzinfo=Eastern2)
5293 t = Eastern2.fromutc(u)
5294 self.assertEqual((t.day, t.hour), (6, 21))
5295
5296 def test_mixed_compare_regular(self):
5297 t = datetime(2000, 1, 1, tzinfo=Eastern2)
5298 self.assertEqual(t, t.astimezone(timezone.utc))
5299 t = datetime(2000, 6, 1, tzinfo=Eastern2)
5300 self.assertEqual(t, t.astimezone(timezone.utc))
5301
5302 def test_mixed_compare_fold(self):
5303 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
5304 t_fold_utc = t_fold.astimezone(timezone.utc)
5305 self.assertNotEqual(t_fold, t_fold_utc)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04005306 self.assertNotEqual(t_fold_utc, t_fold)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005307
5308 def test_mixed_compare_gap(self):
5309 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
5310 t_gap_utc = t_gap.astimezone(timezone.utc)
5311 self.assertNotEqual(t_gap, t_gap_utc)
Alexander Belopolsky4c3e39f2018-06-08 18:58:38 -04005312 self.assertNotEqual(t_gap_utc, t_gap)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005313
5314 def test_hash_aware(self):
5315 t = datetime(2000, 1, 1, tzinfo=Eastern2)
5316 self.assertEqual(hash(t), hash(t.replace(fold=1)))
5317 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
5318 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
5319 self.assertEqual(hash(t_fold), hash(t_fold.replace(fold=1)))
5320 self.assertEqual(hash(t_gap), hash(t_gap.replace(fold=1)))
5321
5322SEC = timedelta(0, 1)
5323
5324def pairs(iterable):
5325 a, b = itertools.tee(iterable)
5326 next(b, None)
5327 return zip(a, b)
5328
5329class ZoneInfo(tzinfo):
5330 zoneroot = '/usr/share/zoneinfo'
5331 def __init__(self, ut, ti):
5332 """
5333
5334 :param ut: array
5335 Array of transition point timestamps
5336 :param ti: list
5337 A list of (offset, isdst, abbr) tuples
5338 :return: None
5339 """
5340 self.ut = ut
5341 self.ti = ti
5342 self.lt = self.invert(ut, ti)
5343
5344 @staticmethod
5345 def invert(ut, ti):
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04005346 lt = (array('q', ut), array('q', ut))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005347 if ut:
5348 offset = ti[0][0] // SEC
Alexander Belopolsky7c7c1462016-08-23 14:44:51 -04005349 lt[0][0] += offset
5350 lt[1][0] += offset
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005351 for i in range(1, len(ut)):
5352 lt[0][i] += ti[i-1][0] // SEC
5353 lt[1][i] += ti[i][0] // SEC
5354 return lt
5355
5356 @classmethod
5357 def fromfile(cls, fileobj):
5358 if fileobj.read(4).decode() != "TZif":
5359 raise ValueError("not a zoneinfo file")
5360 fileobj.seek(32)
5361 counts = array('i')
5362 counts.fromfile(fileobj, 3)
5363 if sys.byteorder != 'big':
5364 counts.byteswap()
5365
5366 ut = array('i')
5367 ut.fromfile(fileobj, counts[0])
5368 if sys.byteorder != 'big':
5369 ut.byteswap()
5370
5371 type_indices = array('B')
5372 type_indices.fromfile(fileobj, counts[0])
5373
5374 ttis = []
5375 for i in range(counts[1]):
5376 ttis.append(struct.unpack(">lbb", fileobj.read(6)))
5377
5378 abbrs = fileobj.read(counts[2])
5379
5380 # Convert ttis
5381 for i, (gmtoff, isdst, abbrind) in enumerate(ttis):
5382 abbr = abbrs[abbrind:abbrs.find(0, abbrind)].decode()
5383 ttis[i] = (timedelta(0, gmtoff), isdst, abbr)
5384
5385 ti = [None] * len(ut)
5386 for i, idx in enumerate(type_indices):
5387 ti[i] = ttis[idx]
5388
5389 self = cls(ut, ti)
5390
5391 return self
5392
5393 @classmethod
5394 def fromname(cls, name):
5395 path = os.path.join(cls.zoneroot, name)
5396 with open(path, 'rb') as f:
5397 return cls.fromfile(f)
5398
5399 EPOCHORDINAL = date(1970, 1, 1).toordinal()
5400
5401 def fromutc(self, dt):
5402 """datetime in UTC -> datetime in local time."""
5403
5404 if not isinstance(dt, datetime):
5405 raise TypeError("fromutc() requires a datetime argument")
5406 if dt.tzinfo is not self:
5407 raise ValueError("dt.tzinfo is not self")
5408
5409 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
5410 + dt.hour * 3600
5411 + dt.minute * 60
5412 + dt.second)
5413
5414 if timestamp < self.ut[1]:
5415 tti = self.ti[0]
5416 fold = 0
5417 else:
5418 idx = bisect.bisect_right(self.ut, timestamp)
5419 assert self.ut[idx-1] <= timestamp
5420 assert idx == len(self.ut) or timestamp < self.ut[idx]
5421 tti_prev, tti = self.ti[idx-2:idx]
5422 # Detect fold
5423 shift = tti_prev[0] - tti[0]
5424 fold = (shift > timedelta(0, timestamp - self.ut[idx-1]))
5425 dt += tti[0]
5426 if fold:
5427 return dt.replace(fold=1)
5428 else:
5429 return dt
5430
5431 def _find_ti(self, dt, i):
5432 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
5433 + dt.hour * 3600
5434 + dt.minute * 60
5435 + dt.second)
5436 lt = self.lt[dt.fold]
5437 idx = bisect.bisect_right(lt, timestamp)
5438
5439 return self.ti[max(0, idx - 1)][i]
5440
5441 def utcoffset(self, dt):
5442 return self._find_ti(dt, 0)
5443
5444 def dst(self, dt):
5445 isdst = self._find_ti(dt, 1)
5446 # XXX: We cannot accurately determine the "save" value,
5447 # so let's return 1h whenever DST is in effect. Since
5448 # we don't use dst() in fromutc(), it is unlikely that
5449 # it will be needed for anything more than bool(dst()).
5450 return ZERO if isdst else HOUR
5451
5452 def tzname(self, dt):
5453 return self._find_ti(dt, 2)
5454
5455 @classmethod
5456 def zonenames(cls, zonedir=None):
5457 if zonedir is None:
5458 zonedir = cls.zoneroot
Alexander Belopolsky1b8f26c2016-08-11 11:01:45 -04005459 zone_tab = os.path.join(zonedir, 'zone.tab')
5460 try:
5461 f = open(zone_tab)
5462 except OSError:
5463 return
5464 with f:
5465 for line in f:
5466 line = line.strip()
5467 if line and not line.startswith('#'):
5468 yield line.split()[2]
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005469
5470 @classmethod
5471 def stats(cls, start_year=1):
5472 count = gap_count = fold_count = zeros_count = 0
5473 min_gap = min_fold = timedelta.max
5474 max_gap = max_fold = ZERO
5475 min_gap_datetime = max_gap_datetime = datetime.min
5476 min_gap_zone = max_gap_zone = None
5477 min_fold_datetime = max_fold_datetime = datetime.min
5478 min_fold_zone = max_fold_zone = None
5479 stats_since = datetime(start_year, 1, 1) # Starting from 1970 eliminates a lot of noise
5480 for zonename in cls.zonenames():
5481 count += 1
5482 tz = cls.fromname(zonename)
5483 for dt, shift in tz.transitions():
5484 if dt < stats_since:
5485 continue
5486 if shift > ZERO:
5487 gap_count += 1
5488 if (shift, dt) > (max_gap, max_gap_datetime):
5489 max_gap = shift
5490 max_gap_zone = zonename
5491 max_gap_datetime = dt
5492 if (shift, datetime.max - dt) < (min_gap, datetime.max - min_gap_datetime):
5493 min_gap = shift
5494 min_gap_zone = zonename
5495 min_gap_datetime = dt
5496 elif shift < ZERO:
5497 fold_count += 1
5498 shift = -shift
5499 if (shift, dt) > (max_fold, max_fold_datetime):
5500 max_fold = shift
5501 max_fold_zone = zonename
5502 max_fold_datetime = dt
5503 if (shift, datetime.max - dt) < (min_fold, datetime.max - min_fold_datetime):
5504 min_fold = shift
5505 min_fold_zone = zonename
5506 min_fold_datetime = dt
5507 else:
5508 zeros_count += 1
5509 trans_counts = (gap_count, fold_count, zeros_count)
5510 print("Number of zones: %5d" % count)
5511 print("Number of transitions: %5d = %d (gaps) + %d (folds) + %d (zeros)" %
5512 ((sum(trans_counts),) + trans_counts))
5513 print("Min gap: %16s at %s in %s" % (min_gap, min_gap_datetime, min_gap_zone))
5514 print("Max gap: %16s at %s in %s" % (max_gap, max_gap_datetime, max_gap_zone))
5515 print("Min fold: %16s at %s in %s" % (min_fold, min_fold_datetime, min_fold_zone))
5516 print("Max fold: %16s at %s in %s" % (max_fold, max_fold_datetime, max_fold_zone))
5517
5518
5519 def transitions(self):
5520 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
5521 shift = ti[0] - prev_ti[0]
5522 yield datetime.utcfromtimestamp(t), shift
5523
5524 def nondst_folds(self):
5525 """Find all folds with the same value of isdst on both sides of the transition."""
5526 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
5527 shift = ti[0] - prev_ti[0]
5528 if shift < ZERO and ti[1] == prev_ti[1]:
5529 yield datetime.utcfromtimestamp(t), -shift, prev_ti[2], ti[2]
5530
5531 @classmethod
5532 def print_all_nondst_folds(cls, same_abbr=False, start_year=1):
5533 count = 0
5534 for zonename in cls.zonenames():
5535 tz = cls.fromname(zonename)
5536 for dt, shift, prev_abbr, abbr in tz.nondst_folds():
5537 if dt.year < start_year or same_abbr and prev_abbr != abbr:
5538 continue
5539 count += 1
5540 print("%3d) %-30s %s %10s %5s -> %s" %
5541 (count, zonename, dt, shift, prev_abbr, abbr))
5542
5543 def folds(self):
5544 for t, shift in self.transitions():
5545 if shift < ZERO:
5546 yield t, -shift
5547
5548 def gaps(self):
5549 for t, shift in self.transitions():
5550 if shift > ZERO:
5551 yield t, shift
5552
5553 def zeros(self):
5554 for t, shift in self.transitions():
5555 if not shift:
5556 yield t
5557
5558
5559class ZoneInfoTest(unittest.TestCase):
5560 zonename = 'America/New_York'
5561
5562 def setUp(self):
5563 if sys.platform == "win32":
5564 self.skipTest("Skipping zoneinfo tests on Windows")
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04005565 try:
5566 self.tz = ZoneInfo.fromname(self.zonename)
5567 except FileNotFoundError as err:
5568 self.skipTest("Skipping %s: %s" % (self.zonename, err))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005569
5570 def assertEquivDatetimes(self, a, b):
5571 self.assertEqual((a.replace(tzinfo=None), a.fold, id(a.tzinfo)),
5572 (b.replace(tzinfo=None), b.fold, id(b.tzinfo)))
5573
5574 def test_folds(self):
5575 tz = self.tz
5576 for dt, shift in tz.folds():
5577 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
5578 udt = dt + x
5579 ldt = tz.fromutc(udt.replace(tzinfo=tz))
5580 self.assertEqual(ldt.fold, 1)
5581 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
5582 self.assertEquivDatetimes(adt, ldt)
5583 utcoffset = ldt.utcoffset()
5584 self.assertEqual(ldt.replace(tzinfo=None), udt + utcoffset)
5585 # Round trip
5586 self.assertEquivDatetimes(ldt.astimezone(timezone.utc),
5587 udt.replace(tzinfo=timezone.utc))
5588
5589
5590 for x in [-timedelta.resolution, shift]:
5591 udt = dt + x
5592 udt = udt.replace(tzinfo=tz)
5593 ldt = tz.fromutc(udt)
5594 self.assertEqual(ldt.fold, 0)
5595
5596 def test_gaps(self):
5597 tz = self.tz
5598 for dt, shift in tz.gaps():
5599 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
5600 udt = dt + x
5601 udt = udt.replace(tzinfo=tz)
5602 ldt = tz.fromutc(udt)
5603 self.assertEqual(ldt.fold, 0)
5604 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
5605 self.assertEquivDatetimes(adt, ldt)
5606 utcoffset = ldt.utcoffset()
5607 self.assertEqual(ldt.replace(tzinfo=None), udt.replace(tzinfo=None) + utcoffset)
5608 # Create a local time inside the gap
5609 ldt = tz.fromutc(dt.replace(tzinfo=tz)) - shift + x
5610 self.assertLess(ldt.replace(fold=1).utcoffset(),
5611 ldt.replace(fold=0).utcoffset(),
5612 "At %s." % ldt)
5613
5614 for x in [-timedelta.resolution, shift]:
5615 udt = dt + x
5616 ldt = tz.fromutc(udt.replace(tzinfo=tz))
5617 self.assertEqual(ldt.fold, 0)
5618
5619 def test_system_transitions(self):
5620 if ('Riyadh8' in self.zonename or
5621 # From tzdata NEWS file:
5622 # The files solar87, solar88, and solar89 are no longer distributed.
5623 # They were a negative experiment - that is, a demonstration that
5624 # tz data can represent solar time only with some difficulty and error.
5625 # Their presence in the distribution caused confusion, as Riyadh
5626 # civil time was generally not solar time in those years.
5627 self.zonename.startswith('right/')):
5628 self.skipTest("Skipping %s" % self.zonename)
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04005629 tz = self.tz
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005630 TZ = os.environ.get('TZ')
5631 os.environ['TZ'] = self.zonename
5632 try:
5633 _time.tzset()
5634 for udt, shift in tz.transitions():
Alexander Belopolsky10c2dd22016-08-12 19:08:15 -04005635 if udt.year >= 2037:
5636 # System support for times around the end of 32-bit time_t
5637 # and later is flaky on many systems.
5638 break
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005639 s0 = (udt - datetime(1970, 1, 1)) // SEC
5640 ss = shift // SEC # shift seconds
5641 for x in [-40 * 3600, -20*3600, -1, 0,
5642 ss - 1, ss + 20 * 3600, ss + 40 * 3600]:
5643 s = s0 + x
5644 sdt = datetime.fromtimestamp(s)
5645 tzdt = datetime.fromtimestamp(s, tz).replace(tzinfo=None)
5646 self.assertEquivDatetimes(sdt, tzdt)
5647 s1 = sdt.timestamp()
5648 self.assertEqual(s, s1)
5649 if ss > 0: # gap
5650 # Create local time inside the gap
5651 dt = datetime.fromtimestamp(s0) - shift / 2
5652 ts0 = dt.timestamp()
5653 ts1 = dt.replace(fold=1).timestamp()
5654 self.assertEqual(ts0, s0 + ss / 2)
5655 self.assertEqual(ts1, s0 - ss / 2)
5656 finally:
5657 if TZ is None:
5658 del os.environ['TZ']
5659 else:
5660 os.environ['TZ'] = TZ
5661 _time.tzset()
5662
5663
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04005664class ZoneInfoCompleteTest(unittest.TestSuite):
5665 def __init__(self):
5666 tests = []
5667 if is_resource_enabled('tzdata'):
5668 for name in ZoneInfo.zonenames():
5669 Test = type('ZoneInfoTest[%s]' % name, (ZoneInfoTest,), {})
5670 Test.zonename = name
5671 for method in dir(Test):
5672 if method.startswith('test_'):
5673 tests.append(Test(method))
5674 super().__init__(tests)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005675
5676# Iran had a sub-minute UTC offset before 1946.
5677class IranTest(ZoneInfoTest):
Alexander Belopolsky07e2a0a2016-07-30 11:41:02 -04005678 zonename = 'Asia/Tehran'
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04005679
Paul Ganssle04af5b12018-01-24 17:29:30 -05005680
5681class CapiTest(unittest.TestCase):
5682 def setUp(self):
5683 # Since the C API is not present in the _Pure tests, skip all tests
5684 if self.__class__.__name__.endswith('Pure'):
5685 self.skipTest('Not relevant in pure Python')
5686
5687 # This *must* be called, and it must be called first, so until either
5688 # restriction is loosened, we'll call it as part of test setup
5689 _testcapi.test_datetime_capi()
5690
5691 def test_utc_capi(self):
5692 for use_macro in (True, False):
5693 capi_utc = _testcapi.get_timezone_utc_capi(use_macro)
5694
5695 with self.subTest(use_macro=use_macro):
5696 self.assertIs(capi_utc, timezone.utc)
5697
5698 def test_timezones_capi(self):
5699 est_capi, est_macro, est_macro_nn = _testcapi.make_timezones_capi()
5700
5701 exp_named = timezone(timedelta(hours=-5), "EST")
5702 exp_unnamed = timezone(timedelta(hours=-5))
5703
5704 cases = [
5705 ('est_capi', est_capi, exp_named),
5706 ('est_macro', est_macro, exp_named),
5707 ('est_macro_nn', est_macro_nn, exp_unnamed)
5708 ]
5709
5710 for name, tz_act, tz_exp in cases:
5711 with self.subTest(name=name):
5712 self.assertEqual(tz_act, tz_exp)
5713
5714 dt1 = datetime(2000, 2, 4, tzinfo=tz_act)
5715 dt2 = datetime(2000, 2, 4, tzinfo=tz_exp)
5716
5717 self.assertEqual(dt1, dt2)
5718 self.assertEqual(dt1.tzname(), dt2.tzname())
5719
5720 dt_utc = datetime(2000, 2, 4, 5, tzinfo=timezone.utc)
5721
5722 self.assertEqual(dt1.astimezone(timezone.utc), dt_utc)
5723
Paul Gansslea049f572018-02-22 15:15:32 -05005724 def test_timezones_offset_zero(self):
5725 utc0, utc1, non_utc = _testcapi.get_timezones_offset_zero()
5726
5727 with self.subTest(testname="utc0"):
5728 self.assertIs(utc0, timezone.utc)
5729
5730 with self.subTest(testname="utc1"):
5731 self.assertIs(utc1, timezone.utc)
5732
5733 with self.subTest(testname="non_utc"):
5734 self.assertIsNot(non_utc, timezone.utc)
5735
5736 non_utc_exp = timezone(timedelta(hours=0), "")
5737
5738 self.assertEqual(non_utc, non_utc_exp)
5739
5740 dt1 = datetime(2000, 2, 4, tzinfo=non_utc)
5741 dt2 = datetime(2000, 2, 4, tzinfo=non_utc_exp)
5742
5743 self.assertEqual(dt1, dt2)
5744 self.assertEqual(dt1.tzname(), dt2.tzname())
5745
Paul Ganssle04af5b12018-01-24 17:29:30 -05005746 def test_check_date(self):
5747 class DateSubclass(date):
5748 pass
5749
5750 d = date(2011, 1, 1)
5751 ds = DateSubclass(2011, 1, 1)
5752 dt = datetime(2011, 1, 1)
5753
5754 is_date = _testcapi.datetime_check_date
5755
5756 # Check the ones that should be valid
5757 self.assertTrue(is_date(d))
5758 self.assertTrue(is_date(dt))
5759 self.assertTrue(is_date(ds))
5760 self.assertTrue(is_date(d, True))
5761
5762 # Check that the subclasses do not match exactly
5763 self.assertFalse(is_date(dt, True))
5764 self.assertFalse(is_date(ds, True))
5765
5766 # Check that various other things are not dates at all
5767 args = [tuple(), list(), 1, '2011-01-01',
5768 timedelta(1), timezone.utc, time(12, 00)]
5769 for arg in args:
5770 for exact in (True, False):
5771 with self.subTest(arg=arg, exact=exact):
5772 self.assertFalse(is_date(arg, exact))
5773
5774 def test_check_time(self):
5775 class TimeSubclass(time):
5776 pass
5777
5778 t = time(12, 30)
5779 ts = TimeSubclass(12, 30)
5780
5781 is_time = _testcapi.datetime_check_time
5782
5783 # Check the ones that should be valid
5784 self.assertTrue(is_time(t))
5785 self.assertTrue(is_time(ts))
5786 self.assertTrue(is_time(t, True))
5787
5788 # Check that the subclass does not match exactly
5789 self.assertFalse(is_time(ts, True))
5790
5791 # Check that various other things are not times
5792 args = [tuple(), list(), 1, '2011-01-01',
5793 timedelta(1), timezone.utc, date(2011, 1, 1)]
5794
5795 for arg in args:
5796 for exact in (True, False):
5797 with self.subTest(arg=arg, exact=exact):
5798 self.assertFalse(is_time(arg, exact))
5799
5800 def test_check_datetime(self):
5801 class DateTimeSubclass(datetime):
5802 pass
5803
5804 dt = datetime(2011, 1, 1, 12, 30)
5805 dts = DateTimeSubclass(2011, 1, 1, 12, 30)
5806
5807 is_datetime = _testcapi.datetime_check_datetime
5808
5809 # Check the ones that should be valid
5810 self.assertTrue(is_datetime(dt))
5811 self.assertTrue(is_datetime(dts))
5812 self.assertTrue(is_datetime(dt, True))
5813
5814 # Check that the subclass does not match exactly
5815 self.assertFalse(is_datetime(dts, True))
5816
5817 # Check that various other things are not datetimes
5818 args = [tuple(), list(), 1, '2011-01-01',
5819 timedelta(1), timezone.utc, date(2011, 1, 1)]
5820
5821 for arg in args:
5822 for exact in (True, False):
5823 with self.subTest(arg=arg, exact=exact):
5824 self.assertFalse(is_datetime(arg, exact))
5825
5826 def test_check_delta(self):
5827 class TimeDeltaSubclass(timedelta):
5828 pass
5829
5830 td = timedelta(1)
5831 tds = TimeDeltaSubclass(1)
5832
5833 is_timedelta = _testcapi.datetime_check_delta
5834
5835 # Check the ones that should be valid
5836 self.assertTrue(is_timedelta(td))
5837 self.assertTrue(is_timedelta(tds))
5838 self.assertTrue(is_timedelta(td, True))
5839
5840 # Check that the subclass does not match exactly
5841 self.assertFalse(is_timedelta(tds, True))
5842
5843 # Check that various other things are not timedeltas
5844 args = [tuple(), list(), 1, '2011-01-01',
5845 timezone.utc, date(2011, 1, 1), datetime(2011, 1, 1)]
5846
5847 for arg in args:
5848 for exact in (True, False):
5849 with self.subTest(arg=arg, exact=exact):
5850 self.assertFalse(is_timedelta(arg, exact))
5851
5852 def test_check_tzinfo(self):
5853 class TZInfoSubclass(tzinfo):
5854 pass
5855
5856 tzi = tzinfo()
5857 tzis = TZInfoSubclass()
5858 tz = timezone(timedelta(hours=-5))
5859
5860 is_tzinfo = _testcapi.datetime_check_tzinfo
5861
5862 # Check the ones that should be valid
5863 self.assertTrue(is_tzinfo(tzi))
5864 self.assertTrue(is_tzinfo(tz))
5865 self.assertTrue(is_tzinfo(tzis))
5866 self.assertTrue(is_tzinfo(tzi, True))
5867
5868 # Check that the subclasses do not match exactly
5869 self.assertFalse(is_tzinfo(tz, True))
5870 self.assertFalse(is_tzinfo(tzis, True))
5871
5872 # Check that various other things are not tzinfos
5873 args = [tuple(), list(), 1, '2011-01-01',
5874 date(2011, 1, 1), datetime(2011, 1, 1)]
5875
5876 for arg in args:
5877 for exact in (True, False):
5878 with self.subTest(arg=arg, exact=exact):
5879 self.assertFalse(is_tzinfo(arg, exact))
5880
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04005881def load_tests(loader, standard_tests, pattern):
5882 standard_tests.addTest(ZoneInfoCompleteTest())
5883 return standard_tests
5884
5885
Alexander Belopolskycf86e362010-07-23 19:25:47 +00005886if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -05005887 unittest.main()