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