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