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