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