1"""Test date/time type. 2 3See http://www.zope.org/Members/fdrake/DateTimeWiki/TestCases 4""" 5from __future__ import division 6import sys 7import pickle 8import cPickle 9import unittest 10 11from test import test_support 12 13from datetime import MINYEAR, MAXYEAR 14from datetime import timedelta 15from datetime import tzinfo 16from datetime import time 17from datetime import date, datetime 18 19pickle_choices = [(pickler, unpickler, proto) 20 for pickler in pickle, cPickle 21 for unpickler in pickle, cPickle 22 for proto in range(3)] 23assert len(pickle_choices) == 2*2*3 24 25# An arbitrary collection of objects of non-datetime types, for testing 26# mixed-type comparisons. 27OTHERSTUFF = (10, 10L, 34.5, "abc", {}, [], ()) 28 29 30############################################################################# 31# module tests 32 33class TestModule(unittest.TestCase): 34 35 def test_constants(self): 36 import datetime 37 self.assertEqual(datetime.MINYEAR, 1) 38 self.assertEqual(datetime.MAXYEAR, 9999) 39 40############################################################################# 41# tzinfo tests 42 43class FixedOffset(tzinfo): 44 def __init__(self, offset, name, dstoffset=42): 45 if isinstance(offset, int): 46 offset = timedelta(minutes=offset) 47 if isinstance(dstoffset, int): 48 dstoffset = timedelta(minutes=dstoffset) 49 self.__offset = offset 50 self.__name = name 51 self.__dstoffset = dstoffset 52 def __repr__(self): 53 return self.__name.lower() 54 def utcoffset(self, dt): 55 return self.__offset 56 def tzname(self, dt): 57 return self.__name 58 def dst(self, dt): 59 return self.__dstoffset 60 61class PicklableFixedOffset(FixedOffset): 62 def __init__(self, offset=None, name=None, dstoffset=None): 63 FixedOffset.__init__(self, offset, name, dstoffset) 64 65class TestTZInfo(unittest.TestCase): 66 67 def test_non_abstractness(self): 68 # In order to allow subclasses to get pickled, the C implementation 69 # wasn't able to get away with having __init__ raise 70 # NotImplementedError. 71 useless = tzinfo() 72 dt = datetime.max 73 self.assertRaises(NotImplementedError, useless.tzname, dt) 74 self.assertRaises(NotImplementedError, useless.utcoffset, dt) 75 self.assertRaises(NotImplementedError, useless.dst, dt) 76 77 def test_subclass_must_override(self): 78 class NotEnough(tzinfo): 79 def __init__(self, offset, name): 80 self.__offset = offset 81 self.__name = name 82 self.assertTrue(issubclass(NotEnough, tzinfo)) 83 ne = NotEnough(3, "NotByALongShot") 84 self.assertIsInstance(ne, tzinfo) 85 86 dt = datetime.now() 87 self.assertRaises(NotImplementedError, ne.tzname, dt) 88 self.assertRaises(NotImplementedError, ne.utcoffset, dt) 89 self.assertRaises(NotImplementedError, ne.dst, dt) 90 91 def test_normal(self): 92 fo = FixedOffset(3, "Three") 93 self.assertIsInstance(fo, tzinfo) 94 for dt in datetime.now(), None: 95 self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3)) 96 self.assertEqual(fo.tzname(dt), "Three") 97 self.assertEqual(fo.dst(dt), timedelta(minutes=42)) 98 99 def test_pickling_base(self): 100 # There's no point to pickling tzinfo objects on their own (they 101 # carry no data), but they need to be picklable anyway else 102 # concrete subclasses can't be pickled. 103 orig = tzinfo.__new__(tzinfo) 104 self.assertIs(type(orig), tzinfo) 105 for pickler, unpickler, proto in pickle_choices: 106 green = pickler.dumps(orig, proto) 107 derived = unpickler.loads(green) 108 self.assertIs(type(derived), tzinfo) 109 110 def test_pickling_subclass(self): 111 # Make sure we can pickle/unpickle an instance of a subclass. 112 offset = timedelta(minutes=-300) 113 orig = PicklableFixedOffset(offset, 'cookie') 114 self.assertIsInstance(orig, tzinfo) 115 self.assertTrue(type(orig) is PicklableFixedOffset) 116 self.assertEqual(orig.utcoffset(None), offset) 117 self.assertEqual(orig.tzname(None), 'cookie') 118 for pickler, unpickler, proto in pickle_choices: 119 green = pickler.dumps(orig, proto) 120 derived = unpickler.loads(green) 121 self.assertIsInstance(derived, tzinfo) 122 self.assertTrue(type(derived) is PicklableFixedOffset) 123 self.assertEqual(derived.utcoffset(None), offset) 124 self.assertEqual(derived.tzname(None), 'cookie') 125 126############################################################################# 127# Base class for testing a particular aspect of timedelta, time, date and 128# datetime comparisons. 129 130class HarmlessMixedComparison: 131 # Test that __eq__ and __ne__ don't complain for mixed-type comparisons. 132 133 # Subclasses must define 'theclass', and theclass(1, 1, 1) must be a 134 # legit constructor. 135 136 def test_harmless_mixed_comparison(self): 137 me = self.theclass(1, 1, 1) 138 139 self.assertFalse(me == ()) 140 self.assertTrue(me != ()) 141 self.assertFalse(() == me) 142 self.assertTrue(() != me) 143 144 self.assertIn(me, [1, 20L, [], me]) 145 self.assertIn([], [me, 1, 20L, []]) 146 147 def test_harmful_mixed_comparison(self): 148 me = self.theclass(1, 1, 1) 149 150 self.assertRaises(TypeError, lambda: me < ()) 151 self.assertRaises(TypeError, lambda: me <= ()) 152 self.assertRaises(TypeError, lambda: me > ()) 153 self.assertRaises(TypeError, lambda: me >= ()) 154 155 self.assertRaises(TypeError, lambda: () < me) 156 self.assertRaises(TypeError, lambda: () <= me) 157 self.assertRaises(TypeError, lambda: () > me) 158 self.assertRaises(TypeError, lambda: () >= me) 159 160 self.assertRaises(TypeError, cmp, (), me) 161 self.assertRaises(TypeError, cmp, me, ()) 162 163############################################################################# 164# timedelta tests 165 166class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase): 167 168 theclass = timedelta 169 170 def test_constructor(self): 171 eq = self.assertEqual 172 td = timedelta 173 174 # Check keyword args to constructor 175 eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0, 176 milliseconds=0, microseconds=0)) 177 eq(td(1), td(days=1)) 178 eq(td(0, 1), td(seconds=1)) 179 eq(td(0, 0, 1), td(microseconds=1)) 180 eq(td(weeks=1), td(days=7)) 181 eq(td(days=1), td(hours=24)) 182 eq(td(hours=1), td(minutes=60)) 183 eq(td(minutes=1), td(seconds=60)) 184 eq(td(seconds=1), td(milliseconds=1000)) 185 eq(td(milliseconds=1), td(microseconds=1000)) 186 187 # Check float args to constructor 188 eq(td(weeks=1.0/7), td(days=1)) 189 eq(td(days=1.0/24), td(hours=1)) 190 eq(td(hours=1.0/60), td(minutes=1)) 191 eq(td(minutes=1.0/60), td(seconds=1)) 192 eq(td(seconds=0.001), td(milliseconds=1)) 193 eq(td(milliseconds=0.001), td(microseconds=1)) 194 195 def test_computations(self): 196 eq = self.assertEqual 197 td = timedelta 198 199 a = td(7) # One week 200 b = td(0, 60) # One minute 201 c = td(0, 0, 1000) # One millisecond 202 eq(a+b+c, td(7, 60, 1000)) 203 eq(a-b, td(6, 24*3600 - 60)) 204 eq(-a, td(-7)) 205 eq(+a, td(7)) 206 eq(-b, td(-1, 24*3600 - 60)) 207 eq(-c, td(-1, 24*3600 - 1, 999000)) 208 eq(abs(a), a) 209 eq(abs(-a), a) 210 eq(td(6, 24*3600), a) 211 eq(td(0, 0, 60*1000000), b) 212 eq(a*10, td(70)) 213 eq(a*10, 10*a) 214 eq(a*10L, 10*a) 215 eq(b*10, td(0, 600)) 216 eq(10*b, td(0, 600)) 217 eq(b*10L, td(0, 600)) 218 eq(c*10, td(0, 0, 10000)) 219 eq(10*c, td(0, 0, 10000)) 220 eq(c*10L, td(0, 0, 10000)) 221 eq(a*-1, -a) 222 eq(b*-2, -b-b) 223 eq(c*-2, -c+-c) 224 eq(b*(60*24), (b*60)*24) 225 eq(b*(60*24), (60*b)*24) 226 eq(c*1000, td(0, 1)) 227 eq(1000*c, td(0, 1)) 228 eq(a//7, td(1)) 229 eq(b//10, td(0, 6)) 230 eq(c//1000, td(0, 0, 1)) 231 eq(a//10, td(0, 7*24*360)) 232 eq(a//3600000, td(0, 0, 7*24*1000)) 233 234 # Issue #11576 235 eq(td(999999999, 86399, 999999) - td(999999999, 86399, 999998), 236 td(0, 0, 1)) 237 eq(td(999999999, 1, 1) - td(999999999, 1, 0), 238 td(0, 0, 1)) 239 240 241 def test_disallowed_computations(self): 242 a = timedelta(42) 243 244 # Add/sub ints, longs, floats should be illegal 245 for i in 1, 1L, 1.0: 246 self.assertRaises(TypeError, lambda: a+i) 247 self.assertRaises(TypeError, lambda: a-i) 248 self.assertRaises(TypeError, lambda: i+a) 249 self.assertRaises(TypeError, lambda: i-a) 250 251 # Mul/div by float isn't supported. 252 x = 2.3 253 self.assertRaises(TypeError, lambda: a*x) 254 self.assertRaises(TypeError, lambda: x*a) 255 self.assertRaises(TypeError, lambda: a/x) 256 self.assertRaises(TypeError, lambda: x/a) 257 self.assertRaises(TypeError, lambda: a // x) 258 self.assertRaises(TypeError, lambda: x // a) 259 260 # Division of int by timedelta doesn't make sense. 261 # Division by zero doesn't make sense. 262 for zero in 0, 0L: 263 self.assertRaises(TypeError, lambda: zero // a) 264 self.assertRaises(ZeroDivisionError, lambda: a // zero) 265 266 def test_basic_attributes(self): 267 days, seconds, us = 1, 7, 31 268 td = timedelta(days, seconds, us) 269 self.assertEqual(td.days, days) 270 self.assertEqual(td.seconds, seconds) 271 self.assertEqual(td.microseconds, us) 272 273 def test_total_seconds(self): 274 td = timedelta(days=365) 275 self.assertEqual(td.total_seconds(), 31536000.0) 276 for total_seconds in [123456.789012, -123456.789012, 0.123456, 0, 1e6]: 277 td = timedelta(seconds=total_seconds) 278 self.assertEqual(td.total_seconds(), total_seconds) 279 # Issue8644: Test that td.total_seconds() has the same 280 # accuracy as td / timedelta(seconds=1). 281 for ms in [-1, -2, -123]: 282 td = timedelta(microseconds=ms) 283 self.assertEqual(td.total_seconds(), 284 ((24*3600*td.days + td.seconds)*10**6 285 + td.microseconds)/10**6) 286 287 def test_carries(self): 288 t1 = timedelta(days=100, 289 weeks=-7, 290 hours=-24*(100-49), 291 minutes=-3, 292 seconds=12, 293 microseconds=(3*60 - 12) * 1e6 + 1) 294 t2 = timedelta(microseconds=1) 295 self.assertEqual(t1, t2) 296 297 def test_hash_equality(self): 298 t1 = timedelta(days=100, 299 weeks=-7, 300 hours=-24*(100-49), 301 minutes=-3, 302 seconds=12, 303 microseconds=(3*60 - 12) * 1000000) 304 t2 = timedelta() 305 self.assertEqual(hash(t1), hash(t2)) 306 307 t1 += timedelta(weeks=7) 308 t2 += timedelta(days=7*7) 309 self.assertEqual(t1, t2) 310 self.assertEqual(hash(t1), hash(t2)) 311 312 d = {t1: 1} 313 d[t2] = 2 314 self.assertEqual(len(d), 1) 315 self.assertEqual(d[t1], 2) 316 317 def test_pickling(self): 318 args = 12, 34, 56 319 orig = timedelta(*args) 320 for pickler, unpickler, proto in pickle_choices: 321 green = pickler.dumps(orig, proto) 322 derived = unpickler.loads(green) 323 self.assertEqual(orig, derived) 324 325 def test_compare(self): 326 t1 = timedelta(2, 3, 4) 327 t2 = timedelta(2, 3, 4) 328 self.assertTrue(t1 == t2) 329 self.assertTrue(t1 <= t2) 330 self.assertTrue(t1 >= t2) 331 self.assertFalse(t1 != t2) 332 self.assertFalse(t1 < t2) 333 self.assertFalse(t1 > t2) 334 self.assertEqual(cmp(t1, t2), 0) 335 self.assertEqual(cmp(t2, t1), 0) 336 337 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5): 338 t2 = timedelta(*args) # this is larger than t1 339 self.assertTrue(t1 < t2) 340 self.assertTrue(t2 > t1) 341 self.assertTrue(t1 <= t2) 342 self.assertTrue(t2 >= t1) 343 self.assertTrue(t1 != t2) 344 self.assertTrue(t2 != t1) 345 self.assertFalse(t1 == t2) 346 self.assertFalse(t2 == t1) 347 self.assertFalse(t1 > t2) 348 self.assertFalse(t2 < t1) 349 self.assertFalse(t1 >= t2) 350 self.assertFalse(t2 <= t1) 351 self.assertEqual(cmp(t1, t2), -1) 352 self.assertEqual(cmp(t2, t1), 1) 353 354 for badarg in OTHERSTUFF: 355 self.assertEqual(t1 == badarg, False) 356 self.assertEqual(t1 != badarg, True) 357 self.assertEqual(badarg == t1, False) 358 self.assertEqual(badarg != t1, True) 359 360 self.assertRaises(TypeError, lambda: t1 <= badarg) 361 self.assertRaises(TypeError, lambda: t1 < badarg) 362 self.assertRaises(TypeError, lambda: t1 > badarg) 363 self.assertRaises(TypeError, lambda: t1 >= badarg) 364 self.assertRaises(TypeError, lambda: badarg <= t1) 365 self.assertRaises(TypeError, lambda: badarg < t1) 366 self.assertRaises(TypeError, lambda: badarg > t1) 367 self.assertRaises(TypeError, lambda: badarg >= t1) 368 369 def test_str(self): 370 td = timedelta 371 eq = self.assertEqual 372 373 eq(str(td(1)), "1 day, 0:00:00") 374 eq(str(td(-1)), "-1 day, 0:00:00") 375 eq(str(td(2)), "2 days, 0:00:00") 376 eq(str(td(-2)), "-2 days, 0:00:00") 377 378 eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59") 379 eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04") 380 eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)), 381 "-210 days, 23:12:34") 382 383 eq(str(td(milliseconds=1)), "0:00:00.001000") 384 eq(str(td(microseconds=3)), "0:00:00.000003") 385 386 eq(str(td(days=999999999, hours=23, minutes=59, seconds=59, 387 microseconds=999999)), 388 "999999999 days, 23:59:59.999999") 389 390 def test_roundtrip(self): 391 for td in (timedelta(days=999999999, hours=23, minutes=59, 392 seconds=59, microseconds=999999), 393 timedelta(days=-999999999), 394 timedelta(days=1, seconds=2, microseconds=3)): 395 396 # Verify td -> string -> td identity. 397 s = repr(td) 398 self.assertTrue(s.startswith('datetime.')) 399 s = s[9:] 400 td2 = eval(s) 401 self.assertEqual(td, td2) 402 403 # Verify identity via reconstructing from pieces. 404 td2 = timedelta(td.days, td.seconds, td.microseconds) 405 self.assertEqual(td, td2) 406 407 def test_resolution_info(self): 408 self.assertIsInstance(timedelta.min, timedelta) 409 self.assertIsInstance(timedelta.max, timedelta) 410 self.assertIsInstance(timedelta.resolution, timedelta) 411 self.assertTrue(timedelta.max > timedelta.min) 412 self.assertEqual(timedelta.min, timedelta(-999999999)) 413 self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1)) 414 self.assertEqual(timedelta.resolution, timedelta(0, 0, 1)) 415 416 def test_overflow(self): 417 tiny = timedelta.resolution 418 419 td = timedelta.min + tiny 420 td -= tiny # no problem 421 self.assertRaises(OverflowError, td.__sub__, tiny) 422 self.assertRaises(OverflowError, td.__add__, -tiny) 423 424 td = timedelta.max - tiny 425 td += tiny # no problem 426 self.assertRaises(OverflowError, td.__add__, tiny) 427 self.assertRaises(OverflowError, td.__sub__, -tiny) 428 429 self.assertRaises(OverflowError, lambda: -timedelta.max) 430 431 def test_microsecond_rounding(self): 432 td = timedelta 433 eq = self.assertEqual 434 435 # Single-field rounding. 436 eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0 437 eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0 438 eq(td(milliseconds=0.6/1000), td(microseconds=1)) 439 eq(td(milliseconds=-0.6/1000), td(microseconds=-1)) 440 441 # Rounding due to contributions from more than one field. 442 us_per_hour = 3600e6 443 us_per_day = us_per_hour * 24 444 eq(td(days=.4/us_per_day), td(0)) 445 eq(td(hours=.2/us_per_hour), td(0)) 446 eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1)) 447 448 eq(td(days=-.4/us_per_day), td(0)) 449 eq(td(hours=-.2/us_per_hour), td(0)) 450 eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1)) 451 452 def test_massive_normalization(self): 453 td = timedelta(microseconds=-1) 454 self.assertEqual((td.days, td.seconds, td.microseconds), 455 (-1, 24*3600-1, 999999)) 456 457 def test_bool(self): 458 self.assertTrue(timedelta(1)) 459 self.assertTrue(timedelta(0, 1)) 460 self.assertTrue(timedelta(0, 0, 1)) 461 self.assertTrue(timedelta(microseconds=1)) 462 self.assertFalse(timedelta(0)) 463 464 def test_subclass_timedelta(self): 465 466 class T(timedelta): 467 @staticmethod 468 def from_td(td): 469 return T(td.days, td.seconds, td.microseconds) 470 471 def as_hours(self): 472 sum = (self.days * 24 + 473 self.seconds / 3600.0 + 474 self.microseconds / 3600e6) 475 return round(sum) 476 477 t1 = T(days=1) 478 self.assertIs(type(t1), T) 479 self.assertEqual(t1.as_hours(), 24) 480 481 t2 = T(days=-1, seconds=-3600) 482 self.assertIs(type(t2), T) 483 self.assertEqual(t2.as_hours(), -25) 484 485 t3 = t1 + t2 486 self.assertIs(type(t3), timedelta) 487 t4 = T.from_td(t3) 488 self.assertIs(type(t4), T) 489 self.assertEqual(t3.days, t4.days) 490 self.assertEqual(t3.seconds, t4.seconds) 491 self.assertEqual(t3.microseconds, t4.microseconds) 492 self.assertEqual(str(t3), str(t4)) 493 self.assertEqual(t4.as_hours(), -1) 494 495 def test_issue31752(self): 496 # The interpreter shouldn't crash because divmod() returns negative 497 # remainder. 498 class BadInt(int): 499 def __mul__(self, other): 500 return Prod() 501 502 class BadLong(long): 503 def __mul__(self, other): 504 return Prod() 505 506 class Prod: 507 def __radd__(self, other): 508 return Sum() 509 510 class Sum(int): 511 def __divmod__(self, other): 512 # negative remainder 513 return (0, -1) 514 515 timedelta(microseconds=BadInt(1)) 516 timedelta(hours=BadInt(1)) 517 timedelta(weeks=BadInt(1)) 518 timedelta(microseconds=BadLong(1)) 519 timedelta(hours=BadLong(1)) 520 timedelta(weeks=BadLong(1)) 521 522 class Sum(long): 523 def __divmod__(self, other): 524 # negative remainder 525 return (0, -1) 526 527 timedelta(microseconds=BadInt(1)) 528 timedelta(hours=BadInt(1)) 529 timedelta(weeks=BadInt(1)) 530 timedelta(microseconds=BadLong(1)) 531 timedelta(hours=BadLong(1)) 532 timedelta(weeks=BadLong(1)) 533 534 535############################################################################# 536# date tests 537 538class TestDateOnly(unittest.TestCase): 539 # Tests here won't pass if also run on datetime objects, so don't 540 # subclass this to test datetimes too. 541 542 def test_delta_non_days_ignored(self): 543 dt = date(2000, 1, 2) 544 delta = timedelta(days=1, hours=2, minutes=3, seconds=4, 545 microseconds=5) 546 days = timedelta(delta.days) 547 self.assertEqual(days, timedelta(1)) 548 549 dt2 = dt + delta 550 self.assertEqual(dt2, dt + days) 551 552 dt2 = delta + dt 553 self.assertEqual(dt2, dt + days) 554 555 dt2 = dt - delta 556 self.assertEqual(dt2, dt - days) 557 558 delta = -delta 559 days = timedelta(delta.days) 560 self.assertEqual(days, timedelta(-2)) 561 562 dt2 = dt + delta 563 self.assertEqual(dt2, dt + days) 564 565 dt2 = delta + dt 566 self.assertEqual(dt2, dt + days) 567 568 dt2 = dt - delta 569 self.assertEqual(dt2, dt - days) 570 571class SubclassDate(date): 572 sub_var = 1 573 574class TestDate(HarmlessMixedComparison, unittest.TestCase): 575 # Tests here should pass for both dates and datetimes, except for a 576 # few tests that TestDateTime overrides. 577 578 theclass = date 579 580 def test_basic_attributes(self): 581 dt = self.theclass(2002, 3, 1) 582 self.assertEqual(dt.year, 2002) 583 self.assertEqual(dt.month, 3) 584 self.assertEqual(dt.day, 1) 585 586 def test_roundtrip(self): 587 for dt in (self.theclass(1, 2, 3), 588 self.theclass.today()): 589 # Verify dt -> string -> date identity. 590 s = repr(dt) 591 self.assertTrue(s.startswith('datetime.')) 592 s = s[9:] 593 dt2 = eval(s) 594 self.assertEqual(dt, dt2) 595 596 # Verify identity via reconstructing from pieces. 597 dt2 = self.theclass(dt.year, dt.month, dt.day) 598 self.assertEqual(dt, dt2) 599 600 def test_ordinal_conversions(self): 601 # Check some fixed values. 602 for y, m, d, n in [(1, 1, 1, 1), # calendar origin 603 (1, 12, 31, 365), 604 (2, 1, 1, 366), 605 # first example from "Calendrical Calculations" 606 (1945, 11, 12, 710347)]: 607 d = self.theclass(y, m, d) 608 self.assertEqual(n, d.toordinal()) 609 fromord = self.theclass.fromordinal(n) 610 self.assertEqual(d, fromord) 611 if hasattr(fromord, "hour"): 612 # if we're checking something fancier than a date, verify 613 # the extra fields have been zeroed out 614 self.assertEqual(fromord.hour, 0) 615 self.assertEqual(fromord.minute, 0) 616 self.assertEqual(fromord.second, 0) 617 self.assertEqual(fromord.microsecond, 0) 618 619 # Check first and last days of year spottily across the whole 620 # range of years supported. 621 for year in xrange(MINYEAR, MAXYEAR+1, 7): 622 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity. 623 d = self.theclass(year, 1, 1) 624 n = d.toordinal() 625 d2 = self.theclass.fromordinal(n) 626 self.assertEqual(d, d2) 627 # Verify that moving back a day gets to the end of year-1. 628 if year > 1: 629 d = self.theclass.fromordinal(n-1) 630 d2 = self.theclass(year-1, 12, 31) 631 self.assertEqual(d, d2) 632 self.assertEqual(d2.toordinal(), n-1) 633 634 # Test every day in a leap-year and a non-leap year. 635 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] 636 for year, isleap in (2000, True), (2002, False): 637 n = self.theclass(year, 1, 1).toordinal() 638 for month, maxday in zip(range(1, 13), dim): 639 if month == 2 and isleap: 640 maxday += 1 641 for day in range(1, maxday+1): 642 d = self.theclass(year, month, day) 643 self.assertEqual(d.toordinal(), n) 644 self.assertEqual(d, self.theclass.fromordinal(n)) 645 n += 1 646 647 def test_extreme_ordinals(self): 648 a = self.theclass.min 649 a = self.theclass(a.year, a.month, a.day) # get rid of time parts 650 aord = a.toordinal() 651 b = a.fromordinal(aord) 652 self.assertEqual(a, b) 653 654 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1)) 655 656 b = a + timedelta(days=1) 657 self.assertEqual(b.toordinal(), aord + 1) 658 self.assertEqual(b, self.theclass.fromordinal(aord + 1)) 659 660 a = self.theclass.max 661 a = self.theclass(a.year, a.month, a.day) # get rid of time parts 662 aord = a.toordinal() 663 b = a.fromordinal(aord) 664 self.assertEqual(a, b) 665 666 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1)) 667 668 b = a - timedelta(days=1) 669 self.assertEqual(b.toordinal(), aord - 1) 670 self.assertEqual(b, self.theclass.fromordinal(aord - 1)) 671 672 def test_bad_constructor_arguments(self): 673 # bad years 674 self.theclass(MINYEAR, 1, 1) # no exception 675 self.theclass(MAXYEAR, 1, 1) # no exception 676 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1) 677 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1) 678 # bad months 679 self.theclass(2000, 1, 1) # no exception 680 self.theclass(2000, 12, 1) # no exception 681 self.assertRaises(ValueError, self.theclass, 2000, 0, 1) 682 self.assertRaises(ValueError, self.theclass, 2000, 13, 1) 683 # bad days 684 self.theclass(2000, 2, 29) # no exception 685 self.theclass(2004, 2, 29) # no exception 686 self.theclass(2400, 2, 29) # no exception 687 self.assertRaises(ValueError, self.theclass, 2000, 2, 30) 688 self.assertRaises(ValueError, self.theclass, 2001, 2, 29) 689 self.assertRaises(ValueError, self.theclass, 2100, 2, 29) 690 self.assertRaises(ValueError, self.theclass, 1900, 2, 29) 691 self.assertRaises(ValueError, self.theclass, 2000, 1, 0) 692 self.assertRaises(ValueError, self.theclass, 2000, 1, 32) 693 694 def test_hash_equality(self): 695 d = self.theclass(2000, 12, 31) 696 # same thing 697 e = self.theclass(2000, 12, 31) 698 self.assertEqual(d, e) 699 self.assertEqual(hash(d), hash(e)) 700 701 dic = {d: 1} 702 dic[e] = 2 703 self.assertEqual(len(dic), 1) 704 self.assertEqual(dic[d], 2) 705 self.assertEqual(dic[e], 2) 706 707 d = self.theclass(2001, 1, 1) 708 # same thing 709 e = self.theclass(2001, 1, 1) 710 self.assertEqual(d, e) 711 self.assertEqual(hash(d), hash(e)) 712 713 dic = {d: 1} 714 dic[e] = 2 715 self.assertEqual(len(dic), 1) 716 self.assertEqual(dic[d], 2) 717 self.assertEqual(dic[e], 2) 718 719 def test_computations(self): 720 a = self.theclass(2002, 1, 31) 721 b = self.theclass(1956, 1, 31) 722 723 diff = a-b 724 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4))) 725 self.assertEqual(diff.seconds, 0) 726 self.assertEqual(diff.microseconds, 0) 727 728 day = timedelta(1) 729 week = timedelta(7) 730 a = self.theclass(2002, 3, 2) 731 self.assertEqual(a + day, self.theclass(2002, 3, 3)) 732 self.assertEqual(day + a, self.theclass(2002, 3, 3)) 733 self.assertEqual(a - day, self.theclass(2002, 3, 1)) 734 self.assertEqual(-day + a, self.theclass(2002, 3, 1)) 735 self.assertEqual(a + week, self.theclass(2002, 3, 9)) 736 self.assertEqual(a - week, self.theclass(2002, 2, 23)) 737 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1)) 738 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3)) 739 self.assertEqual((a + week) - a, week) 740 self.assertEqual((a + day) - a, day) 741 self.assertEqual((a - week) - a, -week) 742 self.assertEqual((a - day) - a, -day) 743 self.assertEqual(a - (a + week), -week) 744 self.assertEqual(a - (a + day), -day) 745 self.assertEqual(a - (a - week), week) 746 self.assertEqual(a - (a - day), day) 747 748 # Add/sub ints, longs, floats should be illegal 749 for i in 1, 1L, 1.0: 750 self.assertRaises(TypeError, lambda: a+i) 751 self.assertRaises(TypeError, lambda: a-i) 752 self.assertRaises(TypeError, lambda: i+a) 753 self.assertRaises(TypeError, lambda: i-a) 754 755 # delta - date is senseless. 756 self.assertRaises(TypeError, lambda: day - a) 757 # mixing date and (delta or date) via * or // is senseless 758 self.assertRaises(TypeError, lambda: day * a) 759 self.assertRaises(TypeError, lambda: a * day) 760 self.assertRaises(TypeError, lambda: day // a) 761 self.assertRaises(TypeError, lambda: a // day) 762 self.assertRaises(TypeError, lambda: a * a) 763 self.assertRaises(TypeError, lambda: a // a) 764 # date + date is senseless 765 self.assertRaises(TypeError, lambda: a + a) 766 767 def test_overflow(self): 768 tiny = self.theclass.resolution 769 770 for delta in [tiny, timedelta(1), timedelta(2)]: 771 dt = self.theclass.min + delta 772 dt -= delta # no problem 773 self.assertRaises(OverflowError, dt.__sub__, delta) 774 self.assertRaises(OverflowError, dt.__add__, -delta) 775 776 dt = self.theclass.max - delta 777 dt += delta # no problem 778 self.assertRaises(OverflowError, dt.__add__, delta) 779 self.assertRaises(OverflowError, dt.__sub__, -delta) 780 781 def test_fromtimestamp(self): 782 import time 783 784 # Try an arbitrary fixed value. 785 year, month, day = 1999, 9, 19 786 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1)) 787 d = self.theclass.fromtimestamp(ts) 788 self.assertEqual(d.year, year) 789 self.assertEqual(d.month, month) 790 self.assertEqual(d.day, day) 791 792 def test_insane_fromtimestamp(self): 793 # It's possible that some platform maps time_t to double, 794 # and that this test will fail there. This test should 795 # exempt such platforms (provided they return reasonable 796 # results!). 797 for insane in -1e200, 1e200: 798 self.assertRaises(ValueError, self.theclass.fromtimestamp, 799 insane) 800 801 def test_today(self): 802 import time 803 804 # We claim that today() is like fromtimestamp(time.time()), so 805 # prove it. 806 for dummy in range(3): 807 today = self.theclass.today() 808 ts = time.time() 809 todayagain = self.theclass.fromtimestamp(ts) 810 if today == todayagain: 811 break 812 # There are several legit reasons that could fail: 813 # 1. It recently became midnight, between the today() and the 814 # time() calls. 815 # 2. The platform time() has such fine resolution that we'll 816 # never get the same value twice. 817 # 3. The platform time() has poor resolution, and we just 818 # happened to call today() right before a resolution quantum 819 # boundary. 820 # 4. The system clock got fiddled between calls. 821 # In any case, wait a little while and try again. 822 time.sleep(0.1) 823 824 # It worked or it didn't. If it didn't, assume it's reason #2, and 825 # let the test pass if they're within half a second of each other. 826 if today != todayagain: 827 self.assertAlmostEqual(todayagain, today, 828 delta=timedelta(seconds=0.5)) 829 830 def test_weekday(self): 831 for i in range(7): 832 # March 4, 2002 is a Monday 833 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i) 834 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1) 835 # January 2, 1956 is a Monday 836 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i) 837 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1) 838 839 def test_isocalendar(self): 840 # Check examples from 841 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm 842 for i in range(7): 843 d = self.theclass(2003, 12, 22+i) 844 self.assertEqual(d.isocalendar(), (2003, 52, i+1)) 845 d = self.theclass(2003, 12, 29) + timedelta(i) 846 self.assertEqual(d.isocalendar(), (2004, 1, i+1)) 847 d = self.theclass(2004, 1, 5+i) 848 self.assertEqual(d.isocalendar(), (2004, 2, i+1)) 849 d = self.theclass(2009, 12, 21+i) 850 self.assertEqual(d.isocalendar(), (2009, 52, i+1)) 851 d = self.theclass(2009, 12, 28) + timedelta(i) 852 self.assertEqual(d.isocalendar(), (2009, 53, i+1)) 853 d = self.theclass(2010, 1, 4+i) 854 self.assertEqual(d.isocalendar(), (2010, 1, i+1)) 855 856 def test_iso_long_years(self): 857 # Calculate long ISO years and compare to table from 858 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm 859 ISO_LONG_YEARS_TABLE = """ 860 4 32 60 88 861 9 37 65 93 862 15 43 71 99 863 20 48 76 864 26 54 82 865 866 105 133 161 189 867 111 139 167 195 868 116 144 172 869 122 150 178 870 128 156 184 871 872 201 229 257 285 873 207 235 263 291 874 212 240 268 296 875 218 246 274 876 224 252 280 877 878 303 331 359 387 879 308 336 364 392 880 314 342 370 398 881 320 348 376 882 325 353 381 883 """ 884 iso_long_years = map(int, ISO_LONG_YEARS_TABLE.split()) 885 iso_long_years.sort() 886 L = [] 887 for i in range(400): 888 d = self.theclass(2000+i, 12, 31) 889 d1 = self.theclass(1600+i, 12, 31) 890 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:]) 891 if d.isocalendar()[1] == 53: 892 L.append(i) 893 self.assertEqual(L, iso_long_years) 894 895 def test_isoformat(self): 896 t = self.theclass(2, 3, 2) 897 self.assertEqual(t.isoformat(), "0002-03-02") 898 899 def test_ctime(self): 900 t = self.theclass(2002, 3, 2) 901 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002") 902 903 def test_strftime(self): 904 t = self.theclass(2005, 3, 2) 905 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05") 906 self.assertEqual(t.strftime(""), "") # SF bug #761337 907 self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784 908 909 self.assertRaises(TypeError, t.strftime) # needs an arg 910 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args 911 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type 912 913 # test that unicode input is allowed (issue 2782) 914 self.assertEqual(t.strftime(u"%m"), "03") 915 916 # A naive object replaces %z and %Z w/ empty strings. 917 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''") 918 919 #make sure that invalid format specifiers are handled correctly 920 #self.assertRaises(ValueError, t.strftime, "%e") 921 #self.assertRaises(ValueError, t.strftime, "%") 922 #self.assertRaises(ValueError, t.strftime, "%#") 923 924 #oh well, some systems just ignore those invalid ones. 925 #at least, exercise them to make sure that no crashes 926 #are generated 927 for f in ["%e", "%", "%#"]: 928 try: 929 t.strftime(f) 930 except ValueError: 931 pass 932 933 #check that this standard extension works 934 t.strftime("%f") 935 936 937 def test_format(self): 938 dt = self.theclass(2007, 9, 10) 939 self.assertEqual(dt.__format__(''), str(dt)) 940 941 # check that a derived class's __str__() gets called 942 class A(self.theclass): 943 def __str__(self): 944 return 'A' 945 a = A(2007, 9, 10) 946 self.assertEqual(a.__format__(''), 'A') 947 948 # check that a derived class's strftime gets called 949 class B(self.theclass): 950 def strftime(self, format_spec): 951 return 'B' 952 b = B(2007, 9, 10) 953 self.assertEqual(b.__format__(''), str(dt)) 954 955 for fmt in ["m:%m d:%d y:%y", 956 "m:%m d:%d y:%y H:%H M:%M S:%S", 957 "%z %Z", 958 ]: 959 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt)) 960 self.assertEqual(a.__format__(fmt), dt.strftime(fmt)) 961 self.assertEqual(b.__format__(fmt), 'B') 962 963 def test_resolution_info(self): 964 self.assertIsInstance(self.theclass.min, self.theclass) 965 self.assertIsInstance(self.theclass.max, self.theclass) 966 self.assertIsInstance(self.theclass.resolution, timedelta) 967 self.assertTrue(self.theclass.max > self.theclass.min) 968 969 def test_extreme_timedelta(self): 970 big = self.theclass.max - self.theclass.min 971 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds 972 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds 973 # n == 315537897599999999 ~= 2**58.13 974 justasbig = timedelta(0, 0, n) 975 self.assertEqual(big, justasbig) 976 self.assertEqual(self.theclass.min + big, self.theclass.max) 977 self.assertEqual(self.theclass.max - big, self.theclass.min) 978 979 def test_timetuple(self): 980 for i in range(7): 981 # January 2, 1956 is a Monday (0) 982 d = self.theclass(1956, 1, 2+i) 983 t = d.timetuple() 984 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1)) 985 # February 1, 1956 is a Wednesday (2) 986 d = self.theclass(1956, 2, 1+i) 987 t = d.timetuple() 988 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1)) 989 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day 990 # of the year. 991 d = self.theclass(1956, 3, 1+i) 992 t = d.timetuple() 993 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1)) 994 self.assertEqual(t.tm_year, 1956) 995 self.assertEqual(t.tm_mon, 3) 996 self.assertEqual(t.tm_mday, 1+i) 997 self.assertEqual(t.tm_hour, 0) 998 self.assertEqual(t.tm_min, 0) 999 self.assertEqual(t.tm_sec, 0) 1000 self.assertEqual(t.tm_wday, (3+i)%7) 1001 self.assertEqual(t.tm_yday, 61+i) 1002 self.assertEqual(t.tm_isdst, -1) 1003 1004 def test_pickling(self): 1005 args = 6, 7, 23 1006 orig = self.theclass(*args) 1007 for pickler, unpickler, proto in pickle_choices: 1008 green = pickler.dumps(orig, proto) 1009 derived = unpickler.loads(green) 1010 self.assertEqual(orig, derived) 1011 1012 def test_compare(self): 1013 t1 = self.theclass(2, 3, 4) 1014 t2 = self.theclass(2, 3, 4) 1015 self.assertTrue(t1 == t2) 1016 self.assertTrue(t1 <= t2) 1017 self.assertTrue(t1 >= t2) 1018 self.assertFalse(t1 != t2) 1019 self.assertFalse(t1 < t2) 1020 self.assertFalse(t1 > t2) 1021 self.assertEqual(cmp(t1, t2), 0) 1022 self.assertEqual(cmp(t2, t1), 0) 1023 1024 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5): 1025 t2 = self.theclass(*args) # this is larger than t1 1026 self.assertTrue(t1 < t2) 1027 self.assertTrue(t2 > t1) 1028 self.assertTrue(t1 <= t2) 1029 self.assertTrue(t2 >= t1) 1030 self.assertTrue(t1 != t2) 1031 self.assertTrue(t2 != t1) 1032 self.assertFalse(t1 == t2) 1033 self.assertFalse(t2 == t1) 1034 self.assertFalse(t1 > t2) 1035 self.assertFalse(t2 < t1) 1036 self.assertFalse(t1 >= t2) 1037 self.assertFalse(t2 <= t1) 1038 self.assertEqual(cmp(t1, t2), -1) 1039 self.assertEqual(cmp(t2, t1), 1) 1040 1041 for badarg in OTHERSTUFF: 1042 self.assertEqual(t1 == badarg, False) 1043 self.assertEqual(t1 != badarg, True) 1044 self.assertEqual(badarg == t1, False) 1045 self.assertEqual(badarg != t1, True) 1046 1047 self.assertRaises(TypeError, lambda: t1 < badarg) 1048 self.assertRaises(TypeError, lambda: t1 > badarg) 1049 self.assertRaises(TypeError, lambda: t1 >= badarg) 1050 self.assertRaises(TypeError, lambda: badarg <= t1) 1051 self.assertRaises(TypeError, lambda: badarg < t1) 1052 self.assertRaises(TypeError, lambda: badarg > t1) 1053 self.assertRaises(TypeError, lambda: badarg >= t1) 1054 1055 def test_mixed_compare(self): 1056 our = self.theclass(2000, 4, 5) 1057 self.assertRaises(TypeError, cmp, our, 1) 1058 self.assertRaises(TypeError, cmp, 1, our) 1059 1060 class AnotherDateTimeClass(object): 1061 def __cmp__(self, other): 1062 # Return "equal" so calling this can't be confused with 1063 # compare-by-address (which never says "equal" for distinct 1064 # objects). 1065 return 0 1066 __hash__ = None # Silence Py3k warning 1067 1068 # This still errors, because date and datetime comparison raise 1069 # TypeError instead of NotImplemented when they don't know what to 1070 # do, in order to stop comparison from falling back to the default 1071 # compare-by-address. 1072 their = AnotherDateTimeClass() 1073 self.assertRaises(TypeError, cmp, our, their) 1074 # Oops: The next stab raises TypeError in the C implementation, 1075 # but not in the Python implementation of datetime. The difference 1076 # is due to that the Python implementation defines __cmp__ but 1077 # the C implementation defines tp_richcompare. This is more pain 1078 # to fix than it's worth, so commenting out the test. 1079 # self.assertEqual(cmp(their, our), 0) 1080 1081 # But date and datetime comparison return NotImplemented instead if the 1082 # other object has a timetuple attr. This gives the other object a 1083 # chance to do the comparison. 1084 class Comparable(AnotherDateTimeClass): 1085 def timetuple(self): 1086 return () 1087 1088 their = Comparable() 1089 self.assertEqual(cmp(our, their), 0) 1090 self.assertEqual(cmp(their, our), 0) 1091 self.assertTrue(our == their) 1092 self.assertTrue(their == our) 1093 1094 def test_bool(self): 1095 # All dates are considered true. 1096 self.assertTrue(self.theclass.min) 1097 self.assertTrue(self.theclass.max) 1098 1099 def test_strftime_out_of_range(self): 1100 # For nasty technical reasons, we can't handle years before 1900. 1101 cls = self.theclass 1102 self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900") 1103 for y in 1, 49, 51, 99, 100, 1000, 1899: 1104 self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y") 1105 1106 def test_replace(self): 1107 cls = self.theclass 1108 args = [1, 2, 3] 1109 base = cls(*args) 1110 self.assertEqual(base, base.replace()) 1111 1112 i = 0 1113 for name, newval in (("year", 2), 1114 ("month", 3), 1115 ("day", 4)): 1116 newargs = args[:] 1117 newargs[i] = newval 1118 expected = cls(*newargs) 1119 got = base.replace(**{name: newval}) 1120 self.assertEqual(expected, got) 1121 i += 1 1122 1123 # Out of bounds. 1124 base = cls(2000, 2, 29) 1125 self.assertRaises(ValueError, base.replace, year=2001) 1126 1127 def test_subclass_date(self): 1128 1129 class C(self.theclass): 1130 theAnswer = 42 1131 1132 def __new__(cls, *args, **kws): 1133 temp = kws.copy() 1134 extra = temp.pop('extra') 1135 result = self.theclass.__new__(cls, *args, **temp) 1136 result.extra = extra 1137 return result 1138 1139 def newmeth(self, start): 1140 return start + self.year + self.month 1141 1142 args = 2003, 4, 14 1143 1144 dt1 = self.theclass(*args) 1145 dt2 = C(*args, **{'extra': 7}) 1146 1147 self.assertEqual(dt2.__class__, C) 1148 self.assertEqual(dt2.theAnswer, 42) 1149 self.assertEqual(dt2.extra, 7) 1150 self.assertEqual(dt1.toordinal(), dt2.toordinal()) 1151 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7) 1152 1153 def test_pickling_subclass_date(self): 1154 1155 args = 6, 7, 23 1156 orig = SubclassDate(*args) 1157 for pickler, unpickler, proto in pickle_choices: 1158 green = pickler.dumps(orig, proto) 1159 derived = unpickler.loads(green) 1160 self.assertEqual(orig, derived) 1161 1162 def test_backdoor_resistance(self): 1163 # For fast unpickling, the constructor accepts a pickle string. 1164 # This is a low-overhead backdoor. A user can (by intent or 1165 # mistake) pass a string directly, which (if it's the right length) 1166 # will get treated like a pickle, and bypass the normal sanity 1167 # checks in the constructor. This can create insane objects. 1168 # The constructor doesn't want to burn the time to validate all 1169 # fields, but does check the month field. This stops, e.g., 1170 # datetime.datetime('1995-03-25') from yielding an insane object. 1171 base = '1995-03-25' 1172 if not issubclass(self.theclass, datetime): 1173 base = base[:4] 1174 for month_byte in '9', chr(0), chr(13), '\xff': 1175 self.assertRaises(TypeError, self.theclass, 1176 base[:2] + month_byte + base[3:]) 1177 for ord_byte in range(1, 13): 1178 # This shouldn't blow up because of the month byte alone. If 1179 # the implementation changes to do more-careful checking, it may 1180 # blow up because other fields are insane. 1181 self.theclass(base[:2] + chr(ord_byte) + base[3:]) 1182 1183############################################################################# 1184# datetime tests 1185 1186class SubclassDatetime(datetime): 1187 sub_var = 1 1188 1189class TestDateTime(TestDate): 1190 1191 theclass = datetime 1192 1193 def test_basic_attributes(self): 1194 dt = self.theclass(2002, 3, 1, 12, 0) 1195 self.assertEqual(dt.year, 2002) 1196 self.assertEqual(dt.month, 3) 1197 self.assertEqual(dt.day, 1) 1198 self.assertEqual(dt.hour, 12) 1199 self.assertEqual(dt.minute, 0) 1200 self.assertEqual(dt.second, 0) 1201 self.assertEqual(dt.microsecond, 0) 1202 1203 def test_basic_attributes_nonzero(self): 1204 # Make sure all attributes are non-zero so bugs in 1205 # bit-shifting access show up. 1206 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000) 1207 self.assertEqual(dt.year, 2002) 1208 self.assertEqual(dt.month, 3) 1209 self.assertEqual(dt.day, 1) 1210 self.assertEqual(dt.hour, 12) 1211 self.assertEqual(dt.minute, 59) 1212 self.assertEqual(dt.second, 59) 1213 self.assertEqual(dt.microsecond, 8000) 1214 1215 def test_roundtrip(self): 1216 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7), 1217 self.theclass.now()): 1218 # Verify dt -> string -> datetime identity. 1219 s = repr(dt) 1220 self.assertTrue(s.startswith('datetime.')) 1221 s = s[9:] 1222 dt2 = eval(s) 1223 self.assertEqual(dt, dt2) 1224 1225 # Verify identity via reconstructing from pieces. 1226 dt2 = self.theclass(dt.year, dt.month, dt.day, 1227 dt.hour, dt.minute, dt.second, 1228 dt.microsecond) 1229 self.assertEqual(dt, dt2) 1230 1231 def test_isoformat(self): 1232 t = self.theclass(2, 3, 2, 4, 5, 1, 123) 1233 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123") 1234 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123") 1235 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123") 1236 self.assertEqual(t.isoformat('\x00'), "0002-03-02\x0004:05:01.000123") 1237 # str is ISO format with the separator forced to a blank. 1238 self.assertEqual(str(t), "0002-03-02 04:05:01.000123") 1239 1240 t = self.theclass(2, 3, 2) 1241 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00") 1242 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00") 1243 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00") 1244 # str is ISO format with the separator forced to a blank. 1245 self.assertEqual(str(t), "0002-03-02 00:00:00") 1246 1247 def test_format(self): 1248 dt = self.theclass(2007, 9, 10, 4, 5, 1, 123) 1249 self.assertEqual(dt.__format__(''), str(dt)) 1250 1251 # check that a derived class's __str__() gets called 1252 class A(self.theclass): 1253 def __str__(self): 1254 return 'A' 1255 a = A(2007, 9, 10, 4, 5, 1, 123) 1256 self.assertEqual(a.__format__(''), 'A') 1257 1258 # check that a derived class's strftime gets called 1259 class B(self.theclass): 1260 def strftime(self, format_spec): 1261 return 'B' 1262 b = B(2007, 9, 10, 4, 5, 1, 123) 1263 self.assertEqual(b.__format__(''), str(dt)) 1264 1265 for fmt in ["m:%m d:%d y:%y", 1266 "m:%m d:%d y:%y H:%H M:%M S:%S", 1267 "%z %Z", 1268 ]: 1269 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt)) 1270 self.assertEqual(a.__format__(fmt), dt.strftime(fmt)) 1271 self.assertEqual(b.__format__(fmt), 'B') 1272 1273 def test_more_ctime(self): 1274 # Test fields that TestDate doesn't touch. 1275 import time 1276 1277 t = self.theclass(2002, 3, 2, 18, 3, 5, 123) 1278 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002") 1279 # Oops! The next line fails on Win2K under MSVC 6, so it's commented 1280 # out. The difference is that t.ctime() produces " 2" for the day, 1281 # but platform ctime() produces "02" for the day. According to 1282 # C99, t.ctime() is correct here. 1283 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple()))) 1284 1285 # So test a case where that difference doesn't matter. 1286 t = self.theclass(2002, 3, 22, 18, 3, 5, 123) 1287 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple()))) 1288 1289 def test_tz_independent_comparing(self): 1290 dt1 = self.theclass(2002, 3, 1, 9, 0, 0) 1291 dt2 = self.theclass(2002, 3, 1, 10, 0, 0) 1292 dt3 = self.theclass(2002, 3, 1, 9, 0, 0) 1293 self.assertEqual(dt1, dt3) 1294 self.assertTrue(dt2 > dt3) 1295 1296 # Make sure comparison doesn't forget microseconds, and isn't done 1297 # via comparing a float timestamp (an IEEE double doesn't have enough 1298 # precision to span microsecond resolution across years 1 thru 9999, 1299 # so comparing via timestamp necessarily calls some distinct values 1300 # equal). 1301 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998) 1302 us = timedelta(microseconds=1) 1303 dt2 = dt1 + us 1304 self.assertEqual(dt2 - dt1, us) 1305 self.assertTrue(dt1 < dt2) 1306 1307 def test_strftime_with_bad_tzname_replace(self): 1308 # verify ok if tzinfo.tzname().replace() returns a non-string 1309 class MyTzInfo(FixedOffset): 1310 def tzname(self, dt): 1311 class MyStr(str): 1312 def replace(self, *args): 1313 return None 1314 return MyStr('name') 1315 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name')) 1316 self.assertRaises(TypeError, t.strftime, '%Z') 1317 1318 def test_bad_constructor_arguments(self): 1319 # bad years 1320 self.theclass(MINYEAR, 1, 1) # no exception 1321 self.theclass(MAXYEAR, 1, 1) # no exception 1322 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1) 1323 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1) 1324 # bad months 1325 self.theclass(2000, 1, 1) # no exception 1326 self.theclass(2000, 12, 1) # no exception 1327 self.assertRaises(ValueError, self.theclass, 2000, 0, 1) 1328 self.assertRaises(ValueError, self.theclass, 2000, 13, 1) 1329 # bad days 1330 self.theclass(2000, 2, 29) # no exception 1331 self.theclass(2004, 2, 29) # no exception 1332 self.theclass(2400, 2, 29) # no exception 1333 self.assertRaises(ValueError, self.theclass, 2000, 2, 30) 1334 self.assertRaises(ValueError, self.theclass, 2001, 2, 29) 1335 self.assertRaises(ValueError, self.theclass, 2100, 2, 29) 1336 self.assertRaises(ValueError, self.theclass, 1900, 2, 29) 1337 self.assertRaises(ValueError, self.theclass, 2000, 1, 0) 1338 self.assertRaises(ValueError, self.theclass, 2000, 1, 32) 1339 # bad hours 1340 self.theclass(2000, 1, 31, 0) # no exception 1341 self.theclass(2000, 1, 31, 23) # no exception 1342 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1) 1343 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24) 1344 # bad minutes 1345 self.theclass(2000, 1, 31, 23, 0) # no exception 1346 self.theclass(2000, 1, 31, 23, 59) # no exception 1347 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1) 1348 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60) 1349 # bad seconds 1350 self.theclass(2000, 1, 31, 23, 59, 0) # no exception 1351 self.theclass(2000, 1, 31, 23, 59, 59) # no exception 1352 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1) 1353 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60) 1354 # bad microseconds 1355 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception 1356 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception 1357 self.assertRaises(ValueError, self.theclass, 1358 2000, 1, 31, 23, 59, 59, -1) 1359 self.assertRaises(ValueError, self.theclass, 1360 2000, 1, 31, 23, 59, 59, 1361 1000000) 1362 1363 def test_hash_equality(self): 1364 d = self.theclass(2000, 12, 31, 23, 30, 17) 1365 e = self.theclass(2000, 12, 31, 23, 30, 17) 1366 self.assertEqual(d, e) 1367 self.assertEqual(hash(d), hash(e)) 1368 1369 dic = {d: 1} 1370 dic[e] = 2 1371 self.assertEqual(len(dic), 1) 1372 self.assertEqual(dic[d], 2) 1373 self.assertEqual(dic[e], 2) 1374 1375 d = self.theclass(2001, 1, 1, 0, 5, 17) 1376 e = self.theclass(2001, 1, 1, 0, 5, 17) 1377 self.assertEqual(d, e) 1378 self.assertEqual(hash(d), hash(e)) 1379 1380 dic = {d: 1} 1381 dic[e] = 2 1382 self.assertEqual(len(dic), 1) 1383 self.assertEqual(dic[d], 2) 1384 self.assertEqual(dic[e], 2) 1385 1386 def test_computations(self): 1387 a = self.theclass(2002, 1, 31) 1388 b = self.theclass(1956, 1, 31) 1389 diff = a-b 1390 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4))) 1391 self.assertEqual(diff.seconds, 0) 1392 self.assertEqual(diff.microseconds, 0) 1393 a = self.theclass(2002, 3, 2, 17, 6) 1394 millisec = timedelta(0, 0, 1000) 1395 hour = timedelta(0, 3600) 1396 day = timedelta(1) 1397 week = timedelta(7) 1398 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6)) 1399 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6)) 1400 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6)) 1401 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6)) 1402 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6)) 1403 self.assertEqual(a - hour, a + -hour) 1404 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6)) 1405 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6)) 1406 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6)) 1407 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6)) 1408 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6)) 1409 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6)) 1410 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6)) 1411 self.assertEqual((a + week) - a, week) 1412 self.assertEqual((a + day) - a, day) 1413 self.assertEqual((a + hour) - a, hour) 1414 self.assertEqual((a + millisec) - a, millisec) 1415 self.assertEqual((a - week) - a, -week) 1416 self.assertEqual((a - day) - a, -day) 1417 self.assertEqual((a - hour) - a, -hour) 1418 self.assertEqual((a - millisec) - a, -millisec) 1419 self.assertEqual(a - (a + week), -week) 1420 self.assertEqual(a - (a + day), -day) 1421 self.assertEqual(a - (a + hour), -hour) 1422 self.assertEqual(a - (a + millisec), -millisec) 1423 self.assertEqual(a - (a - week), week) 1424 self.assertEqual(a - (a - day), day) 1425 self.assertEqual(a - (a - hour), hour) 1426 self.assertEqual(a - (a - millisec), millisec) 1427 self.assertEqual(a + (week + day + hour + millisec), 1428 self.theclass(2002, 3, 10, 18, 6, 0, 1000)) 1429 self.assertEqual(a + (week + day + hour + millisec), 1430 (((a + week) + day) + hour) + millisec) 1431 self.assertEqual(a - (week + day + hour + millisec), 1432 self.theclass(2002, 2, 22, 16, 5, 59, 999000)) 1433 self.assertEqual(a - (week + day + hour + millisec), 1434 (((a - week) - day) - hour) - millisec) 1435 # Add/sub ints, longs, floats should be illegal 1436 for i in 1, 1L, 1.0: 1437 self.assertRaises(TypeError, lambda: a+i) 1438 self.assertRaises(TypeError, lambda: a-i) 1439 self.assertRaises(TypeError, lambda: i+a) 1440 self.assertRaises(TypeError, lambda: i-a) 1441 1442 # delta - datetime is senseless. 1443 self.assertRaises(TypeError, lambda: day - a) 1444 # mixing datetime and (delta or datetime) via * or // is senseless 1445 self.assertRaises(TypeError, lambda: day * a) 1446 self.assertRaises(TypeError, lambda: a * day) 1447 self.assertRaises(TypeError, lambda: day // a) 1448 self.assertRaises(TypeError, lambda: a // day) 1449 self.assertRaises(TypeError, lambda: a * a) 1450 self.assertRaises(TypeError, lambda: a // a) 1451 # datetime + datetime is senseless 1452 self.assertRaises(TypeError, lambda: a + a) 1453 1454 def test_pickling(self): 1455 args = 6, 7, 23, 20, 59, 1, 64**2 1456 orig = self.theclass(*args) 1457 for pickler, unpickler, proto in pickle_choices: 1458 green = pickler.dumps(orig, proto) 1459 derived = unpickler.loads(green) 1460 self.assertEqual(orig, derived) 1461 1462 def test_more_pickling(self): 1463 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116) 1464 for proto in range(pickle.HIGHEST_PROTOCOL + 1): 1465 s = pickle.dumps(a, proto) 1466 b = pickle.loads(s) 1467 self.assertEqual(b.year, 2003) 1468 self.assertEqual(b.month, 2) 1469 self.assertEqual(b.day, 7) 1470 1471 def test_pickling_subclass_datetime(self): 1472 args = 6, 7, 23, 20, 59, 1, 64**2 1473 orig = SubclassDatetime(*args) 1474 for pickler, unpickler, proto in pickle_choices: 1475 green = pickler.dumps(orig, proto) 1476 derived = unpickler.loads(green) 1477 self.assertEqual(orig, derived) 1478 1479 def test_more_compare(self): 1480 # The test_compare() inherited from TestDate covers the error cases. 1481 # We just want to test lexicographic ordering on the members datetime 1482 # has that date lacks. 1483 args = [2000, 11, 29, 20, 58, 16, 999998] 1484 t1 = self.theclass(*args) 1485 t2 = self.theclass(*args) 1486 self.assertTrue(t1 == t2) 1487 self.assertTrue(t1 <= t2) 1488 self.assertTrue(t1 >= t2) 1489 self.assertFalse(t1 != t2) 1490 self.assertFalse(t1 < t2) 1491 self.assertFalse(t1 > t2) 1492 self.assertEqual(cmp(t1, t2), 0) 1493 self.assertEqual(cmp(t2, t1), 0) 1494 1495 for i in range(len(args)): 1496 newargs = args[:] 1497 newargs[i] = args[i] + 1 1498 t2 = self.theclass(*newargs) # this is larger than t1 1499 self.assertTrue(t1 < t2) 1500 self.assertTrue(t2 > t1) 1501 self.assertTrue(t1 <= t2) 1502 self.assertTrue(t2 >= t1) 1503 self.assertTrue(t1 != t2) 1504 self.assertTrue(t2 != t1) 1505 self.assertFalse(t1 == t2) 1506 self.assertFalse(t2 == t1) 1507 self.assertFalse(t1 > t2) 1508 self.assertFalse(t2 < t1) 1509 self.assertFalse(t1 >= t2) 1510 self.assertFalse(t2 <= t1) 1511 self.assertEqual(cmp(t1, t2), -1) 1512 self.assertEqual(cmp(t2, t1), 1) 1513 1514 1515 # A helper for timestamp constructor tests. 1516 def verify_field_equality(self, expected, got): 1517 self.assertEqual(expected.tm_year, got.year) 1518 self.assertEqual(expected.tm_mon, got.month) 1519 self.assertEqual(expected.tm_mday, got.day) 1520 self.assertEqual(expected.tm_hour, got.hour) 1521 self.assertEqual(expected.tm_min, got.minute) 1522 self.assertEqual(expected.tm_sec, got.second) 1523 1524 def test_fromtimestamp(self): 1525 import time 1526 1527 ts = time.time() 1528 expected = time.localtime(ts) 1529 got = self.theclass.fromtimestamp(ts) 1530 self.verify_field_equality(expected, got) 1531 1532 def test_utcfromtimestamp(self): 1533 import time 1534 1535 ts = time.time() 1536 expected = time.gmtime(ts) 1537 got = self.theclass.utcfromtimestamp(ts) 1538 self.verify_field_equality(expected, got) 1539 1540 def test_microsecond_rounding(self): 1541 # Test whether fromtimestamp "rounds up" floats that are less 1542 # than one microsecond smaller than an integer. 1543 self.assertEqual(self.theclass.fromtimestamp(0.9999999), 1544 self.theclass.fromtimestamp(1)) 1545 1546 def test_insane_fromtimestamp(self): 1547 # It's possible that some platform maps time_t to double, 1548 # and that this test will fail there. This test should 1549 # exempt such platforms (provided they return reasonable 1550 # results!). 1551 for insane in -1e200, 1e200: 1552 self.assertRaises(ValueError, self.theclass.fromtimestamp, 1553 insane) 1554 1555 def test_insane_utcfromtimestamp(self): 1556 # It's possible that some platform maps time_t to double, 1557 # and that this test will fail there. This test should 1558 # exempt such platforms (provided they return reasonable 1559 # results!). 1560 for insane in -1e200, 1e200: 1561 self.assertRaises(ValueError, self.theclass.utcfromtimestamp, 1562 insane) 1563 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps") 1564 def test_negative_float_fromtimestamp(self): 1565 # The result is tz-dependent; at least test that this doesn't 1566 # fail (like it did before bug 1646728 was fixed). 1567 self.theclass.fromtimestamp(-1.05) 1568 1569 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps") 1570 def test_negative_float_utcfromtimestamp(self): 1571 d = self.theclass.utcfromtimestamp(-1.05) 1572 self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000)) 1573 1574 def test_utcnow(self): 1575 import time 1576 1577 # Call it a success if utcnow() and utcfromtimestamp() are within 1578 # a second of each other. 1579 tolerance = timedelta(seconds=1) 1580 for dummy in range(3): 1581 from_now = self.theclass.utcnow() 1582 from_timestamp = self.theclass.utcfromtimestamp(time.time()) 1583 if abs(from_timestamp - from_now) <= tolerance: 1584 break 1585 # Else try again a few times. 1586 self.assertLessEqual(abs(from_timestamp - from_now), tolerance) 1587 1588 def test_strptime(self): 1589 import _strptime 1590 1591 string = '2004-12-01 13:02:47.197' 1592 format = '%Y-%m-%d %H:%M:%S.%f' 1593 result, frac = _strptime._strptime(string, format) 1594 expected = self.theclass(*(result[0:6]+(frac,))) 1595 got = self.theclass.strptime(string, format) 1596 self.assertEqual(expected, got) 1597 1598 def test_more_timetuple(self): 1599 # This tests fields beyond those tested by the TestDate.test_timetuple. 1600 t = self.theclass(2004, 12, 31, 6, 22, 33) 1601 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1)) 1602 self.assertEqual(t.timetuple(), 1603 (t.year, t.month, t.day, 1604 t.hour, t.minute, t.second, 1605 t.weekday(), 1606 t.toordinal() - date(t.year, 1, 1).toordinal() + 1, 1607 -1)) 1608 tt = t.timetuple() 1609 self.assertEqual(tt.tm_year, t.year) 1610 self.assertEqual(tt.tm_mon, t.month) 1611 self.assertEqual(tt.tm_mday, t.day) 1612 self.assertEqual(tt.tm_hour, t.hour) 1613 self.assertEqual(tt.tm_min, t.minute) 1614 self.assertEqual(tt.tm_sec, t.second) 1615 self.assertEqual(tt.tm_wday, t.weekday()) 1616 self.assertEqual(tt.tm_yday, t.toordinal() - 1617 date(t.year, 1, 1).toordinal() + 1) 1618 self.assertEqual(tt.tm_isdst, -1) 1619 1620 def test_more_strftime(self): 1621 # This tests fields beyond those tested by the TestDate.test_strftime. 1622 t = self.theclass(2004, 12, 31, 6, 22, 33, 47) 1623 self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"), 1624 "12 31 04 000047 33 22 06 366") 1625 1626 def test_extract(self): 1627 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234) 1628 self.assertEqual(dt.date(), date(2002, 3, 4)) 1629 self.assertEqual(dt.time(), time(18, 45, 3, 1234)) 1630 1631 def test_combine(self): 1632 d = date(2002, 3, 4) 1633 t = time(18, 45, 3, 1234) 1634 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234) 1635 combine = self.theclass.combine 1636 dt = combine(d, t) 1637 self.assertEqual(dt, expected) 1638 1639 dt = combine(time=t, date=d) 1640 self.assertEqual(dt, expected) 1641 1642 self.assertEqual(d, dt.date()) 1643 self.assertEqual(t, dt.time()) 1644 self.assertEqual(dt, combine(dt.date(), dt.time())) 1645 1646 self.assertRaises(TypeError, combine) # need an arg 1647 self.assertRaises(TypeError, combine, d) # need two args 1648 self.assertRaises(TypeError, combine, t, d) # args reversed 1649 self.assertRaises(TypeError, combine, d, t, 1) # too many args 1650 self.assertRaises(TypeError, combine, "date", "time") # wrong types 1651 1652 def test_replace(self): 1653 cls = self.theclass 1654 args = [1, 2, 3, 4, 5, 6, 7] 1655 base = cls(*args) 1656 self.assertEqual(base, base.replace()) 1657 1658 i = 0 1659 for name, newval in (("year", 2), 1660 ("month", 3), 1661 ("day", 4), 1662 ("hour", 5), 1663 ("minute", 6), 1664 ("second", 7), 1665 ("microsecond", 8)): 1666 newargs = args[:] 1667 newargs[i] = newval 1668 expected = cls(*newargs) 1669 got = base.replace(**{name: newval}) 1670 self.assertEqual(expected, got) 1671 i += 1 1672 1673 # Out of bounds. 1674 base = cls(2000, 2, 29) 1675 self.assertRaises(ValueError, base.replace, year=2001) 1676 1677 def test_astimezone(self): 1678 # Pretty boring! The TZ test is more interesting here. astimezone() 1679 # simply can't be applied to a naive object. 1680 dt = self.theclass.now() 1681 f = FixedOffset(44, "") 1682 self.assertRaises(TypeError, dt.astimezone) # not enough args 1683 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args 1684 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type 1685 self.assertRaises(ValueError, dt.astimezone, f) # naive 1686 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive 1687 1688 class Bogus(tzinfo): 1689 def utcoffset(self, dt): return None 1690 def dst(self, dt): return timedelta(0) 1691 bog = Bogus() 1692 self.assertRaises(ValueError, dt.astimezone, bog) # naive 1693 1694 class AlsoBogus(tzinfo): 1695 def utcoffset(self, dt): return timedelta(0) 1696 def dst(self, dt): return None 1697 alsobog = AlsoBogus() 1698 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive 1699 1700 def test_subclass_datetime(self): 1701 1702 class C(self.theclass): 1703 theAnswer = 42 1704 1705 def __new__(cls, *args, **kws): 1706 temp = kws.copy() 1707 extra = temp.pop('extra') 1708 result = self.theclass.__new__(cls, *args, **temp) 1709 result.extra = extra 1710 return result 1711 1712 def newmeth(self, start): 1713 return start + self.year + self.month + self.second 1714 1715 args = 2003, 4, 14, 12, 13, 41 1716 1717 dt1 = self.theclass(*args) 1718 dt2 = C(*args, **{'extra': 7}) 1719 1720 self.assertEqual(dt2.__class__, C) 1721 self.assertEqual(dt2.theAnswer, 42) 1722 self.assertEqual(dt2.extra, 7) 1723 self.assertEqual(dt1.toordinal(), dt2.toordinal()) 1724 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month + 1725 dt1.second - 7) 1726 1727class SubclassTime(time): 1728 sub_var = 1 1729 1730class TestTime(HarmlessMixedComparison, unittest.TestCase): 1731 1732 theclass = time 1733 1734 def test_basic_attributes(self): 1735 t = self.theclass(12, 0) 1736 self.assertEqual(t.hour, 12) 1737 self.assertEqual(t.minute, 0) 1738 self.assertEqual(t.second, 0) 1739 self.assertEqual(t.microsecond, 0) 1740 1741 def test_basic_attributes_nonzero(self): 1742 # Make sure all attributes are non-zero so bugs in 1743 # bit-shifting access show up. 1744 t = self.theclass(12, 59, 59, 8000) 1745 self.assertEqual(t.hour, 12) 1746 self.assertEqual(t.minute, 59) 1747 self.assertEqual(t.second, 59) 1748 self.assertEqual(t.microsecond, 8000) 1749 1750 def test_roundtrip(self): 1751 t = self.theclass(1, 2, 3, 4) 1752 1753 # Verify t -> string -> time identity. 1754 s = repr(t) 1755 self.assertTrue(s.startswith('datetime.')) 1756 s = s[9:] 1757 t2 = eval(s) 1758 self.assertEqual(t, t2) 1759 1760 # Verify identity via reconstructing from pieces. 1761 t2 = self.theclass(t.hour, t.minute, t.second, 1762 t.microsecond) 1763 self.assertEqual(t, t2) 1764 1765 def test_comparing(self): 1766 args = [1, 2, 3, 4] 1767 t1 = self.theclass(*args) 1768 t2 = self.theclass(*args) 1769 self.assertTrue(t1 == t2) 1770 self.assertTrue(t1 <= t2) 1771 self.assertTrue(t1 >= t2) 1772 self.assertFalse(t1 != t2) 1773 self.assertFalse(t1 < t2) 1774 self.assertFalse(t1 > t2) 1775 self.assertEqual(cmp(t1, t2), 0) 1776 self.assertEqual(cmp(t2, t1), 0) 1777 1778 for i in range(len(args)): 1779 newargs = args[:] 1780 newargs[i] = args[i] + 1 1781 t2 = self.theclass(*newargs) # this is larger than t1 1782 self.assertTrue(t1 < t2) 1783 self.assertTrue(t2 > t1) 1784 self.assertTrue(t1 <= t2) 1785 self.assertTrue(t2 >= t1) 1786 self.assertTrue(t1 != t2) 1787 self.assertTrue(t2 != t1) 1788 self.assertFalse(t1 == t2) 1789 self.assertFalse(t2 == t1) 1790 self.assertFalse(t1 > t2) 1791 self.assertFalse(t2 < t1) 1792 self.assertFalse(t1 >= t2) 1793 self.assertFalse(t2 <= t1) 1794 self.assertEqual(cmp(t1, t2), -1) 1795 self.assertEqual(cmp(t2, t1), 1) 1796 1797 for badarg in OTHERSTUFF: 1798 self.assertEqual(t1 == badarg, False) 1799 self.assertEqual(t1 != badarg, True) 1800 self.assertEqual(badarg == t1, False) 1801 self.assertEqual(badarg != t1, True) 1802 1803 self.assertRaises(TypeError, lambda: t1 <= badarg) 1804 self.assertRaises(TypeError, lambda: t1 < badarg) 1805 self.assertRaises(TypeError, lambda: t1 > badarg) 1806 self.assertRaises(TypeError, lambda: t1 >= badarg) 1807 self.assertRaises(TypeError, lambda: badarg <= t1) 1808 self.assertRaises(TypeError, lambda: badarg < t1) 1809 self.assertRaises(TypeError, lambda: badarg > t1) 1810 self.assertRaises(TypeError, lambda: badarg >= t1) 1811 1812 def test_bad_constructor_arguments(self): 1813 # bad hours 1814 self.theclass(0, 0) # no exception 1815 self.theclass(23, 0) # no exception 1816 self.assertRaises(ValueError, self.theclass, -1, 0) 1817 self.assertRaises(ValueError, self.theclass, 24, 0) 1818 # bad minutes 1819 self.theclass(23, 0) # no exception 1820 self.theclass(23, 59) # no exception 1821 self.assertRaises(ValueError, self.theclass, 23, -1) 1822 self.assertRaises(ValueError, self.theclass, 23, 60) 1823 # bad seconds 1824 self.theclass(23, 59, 0) # no exception 1825 self.theclass(23, 59, 59) # no exception 1826 self.assertRaises(ValueError, self.theclass, 23, 59, -1) 1827 self.assertRaises(ValueError, self.theclass, 23, 59, 60) 1828 # bad microseconds 1829 self.theclass(23, 59, 59, 0) # no exception 1830 self.theclass(23, 59, 59, 999999) # no exception 1831 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1) 1832 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000) 1833 1834 def test_hash_equality(self): 1835 d = self.theclass(23, 30, 17) 1836 e = self.theclass(23, 30, 17) 1837 self.assertEqual(d, e) 1838 self.assertEqual(hash(d), hash(e)) 1839 1840 dic = {d: 1} 1841 dic[e] = 2 1842 self.assertEqual(len(dic), 1) 1843 self.assertEqual(dic[d], 2) 1844 self.assertEqual(dic[e], 2) 1845 1846 d = self.theclass(0, 5, 17) 1847 e = self.theclass(0, 5, 17) 1848 self.assertEqual(d, e) 1849 self.assertEqual(hash(d), hash(e)) 1850 1851 dic = {d: 1} 1852 dic[e] = 2 1853 self.assertEqual(len(dic), 1) 1854 self.assertEqual(dic[d], 2) 1855 self.assertEqual(dic[e], 2) 1856 1857 def test_isoformat(self): 1858 t = self.theclass(4, 5, 1, 123) 1859 self.assertEqual(t.isoformat(), "04:05:01.000123") 1860 self.assertEqual(t.isoformat(), str(t)) 1861 1862 t = self.theclass() 1863 self.assertEqual(t.isoformat(), "00:00:00") 1864 self.assertEqual(t.isoformat(), str(t)) 1865 1866 t = self.theclass(microsecond=1) 1867 self.assertEqual(t.isoformat(), "00:00:00.000001") 1868 self.assertEqual(t.isoformat(), str(t)) 1869 1870 t = self.theclass(microsecond=10) 1871 self.assertEqual(t.isoformat(), "00:00:00.000010") 1872 self.assertEqual(t.isoformat(), str(t)) 1873 1874 t = self.theclass(microsecond=100) 1875 self.assertEqual(t.isoformat(), "00:00:00.000100") 1876 self.assertEqual(t.isoformat(), str(t)) 1877 1878 t = self.theclass(microsecond=1000) 1879 self.assertEqual(t.isoformat(), "00:00:00.001000") 1880 self.assertEqual(t.isoformat(), str(t)) 1881 1882 t = self.theclass(microsecond=10000) 1883 self.assertEqual(t.isoformat(), "00:00:00.010000") 1884 self.assertEqual(t.isoformat(), str(t)) 1885 1886 t = self.theclass(microsecond=100000) 1887 self.assertEqual(t.isoformat(), "00:00:00.100000") 1888 self.assertEqual(t.isoformat(), str(t)) 1889 1890 def test_1653736(self): 1891 # verify it doesn't accept extra keyword arguments 1892 t = self.theclass(second=1) 1893 self.assertRaises(TypeError, t.isoformat, foo=3) 1894 1895 def test_strftime(self): 1896 t = self.theclass(1, 2, 3, 4) 1897 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004") 1898 # A naive object replaces %z and %Z with empty strings. 1899 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''") 1900 1901 def test_format(self): 1902 t = self.theclass(1, 2, 3, 4) 1903 self.assertEqual(t.__format__(''), str(t)) 1904 1905 # check that a derived class's __str__() gets called 1906 class A(self.theclass): 1907 def __str__(self): 1908 return 'A' 1909 a = A(1, 2, 3, 4) 1910 self.assertEqual(a.__format__(''), 'A') 1911 1912 # check that a derived class's strftime gets called 1913 class B(self.theclass): 1914 def strftime(self, format_spec): 1915 return 'B' 1916 b = B(1, 2, 3, 4) 1917 self.assertEqual(b.__format__(''), str(t)) 1918 1919 for fmt in ['%H %M %S', 1920 ]: 1921 self.assertEqual(t.__format__(fmt), t.strftime(fmt)) 1922 self.assertEqual(a.__format__(fmt), t.strftime(fmt)) 1923 self.assertEqual(b.__format__(fmt), 'B') 1924 1925 def test_str(self): 1926 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004") 1927 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000") 1928 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000") 1929 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03") 1930 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00") 1931 1932 def test_repr(self): 1933 name = 'datetime.' + self.theclass.__name__ 1934 self.assertEqual(repr(self.theclass(1, 2, 3, 4)), 1935 "%s(1, 2, 3, 4)" % name) 1936 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)), 1937 "%s(10, 2, 3, 4000)" % name) 1938 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)), 1939 "%s(0, 2, 3, 400000)" % name) 1940 self.assertEqual(repr(self.theclass(12, 2, 3, 0)), 1941 "%s(12, 2, 3)" % name) 1942 self.assertEqual(repr(self.theclass(23, 15, 0, 0)), 1943 "%s(23, 15)" % name) 1944 1945 def test_resolution_info(self): 1946 self.assertIsInstance(self.theclass.min, self.theclass) 1947 self.assertIsInstance(self.theclass.max, self.theclass) 1948 self.assertIsInstance(self.theclass.resolution, timedelta) 1949 self.assertTrue(self.theclass.max > self.theclass.min) 1950 1951 def test_pickling(self): 1952 args = 20, 59, 16, 64**2 1953 orig = self.theclass(*args) 1954 for pickler, unpickler, proto in pickle_choices: 1955 green = pickler.dumps(orig, proto) 1956 derived = unpickler.loads(green) 1957 self.assertEqual(orig, derived) 1958 1959 def test_pickling_subclass_time(self): 1960 args = 20, 59, 16, 64**2 1961 orig = SubclassTime(*args) 1962 for pickler, unpickler, proto in pickle_choices: 1963 green = pickler.dumps(orig, proto) 1964 derived = unpickler.loads(green) 1965 self.assertEqual(orig, derived) 1966 1967 def test_bool(self): 1968 cls = self.theclass 1969 self.assertTrue(cls(1)) 1970 self.assertTrue(cls(0, 1)) 1971 self.assertTrue(cls(0, 0, 1)) 1972 self.assertTrue(cls(0, 0, 0, 1)) 1973 self.assertFalse(cls(0)) 1974 self.assertFalse(cls()) 1975 1976 def test_replace(self): 1977 cls = self.theclass 1978 args = [1, 2, 3, 4] 1979 base = cls(*args) 1980 self.assertEqual(base, base.replace()) 1981 1982 i = 0 1983 for name, newval in (("hour", 5), 1984 ("minute", 6), 1985 ("second", 7), 1986 ("microsecond", 8)): 1987 newargs = args[:] 1988 newargs[i] = newval 1989 expected = cls(*newargs) 1990 got = base.replace(**{name: newval}) 1991 self.assertEqual(expected, got) 1992 i += 1 1993 1994 # Out of bounds. 1995 base = cls(1) 1996 self.assertRaises(ValueError, base.replace, hour=24) 1997 self.assertRaises(ValueError, base.replace, minute=-1) 1998 self.assertRaises(ValueError, base.replace, second=100) 1999 self.assertRaises(ValueError, base.replace, microsecond=1000000) 2000 2001 def test_subclass_time(self): 2002 2003 class C(self.theclass): 2004 theAnswer = 42 2005 2006 def __new__(cls, *args, **kws): 2007 temp = kws.copy() 2008 extra = temp.pop('extra') 2009 result = self.theclass.__new__(cls, *args, **temp) 2010 result.extra = extra 2011 return result 2012 2013 def newmeth(self, start): 2014 return start + self.hour + self.second 2015 2016 args = 4, 5, 6 2017 2018 dt1 = self.theclass(*args) 2019 dt2 = C(*args, **{'extra': 7}) 2020 2021 self.assertEqual(dt2.__class__, C) 2022 self.assertEqual(dt2.theAnswer, 42) 2023 self.assertEqual(dt2.extra, 7) 2024 self.assertEqual(dt1.isoformat(), dt2.isoformat()) 2025 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7) 2026 2027 def test_backdoor_resistance(self): 2028 # see TestDate.test_backdoor_resistance(). 2029 base = '2:59.0' 2030 for hour_byte in ' ', '9', chr(24), '\xff': 2031 self.assertRaises(TypeError, self.theclass, 2032 hour_byte + base[1:]) 2033 2034# A mixin for classes with a tzinfo= argument. Subclasses must define 2035# theclass as a class attribute, and theclass(1, 1, 1, tzinfo=whatever) 2036# must be legit (which is true for time and datetime). 2037class TZInfoBase: 2038 2039 def test_argument_passing(self): 2040 cls = self.theclass 2041 # A datetime passes itself on, a time passes None. 2042 class introspective(tzinfo): 2043 def tzname(self, dt): return dt and "real" or "none" 2044 def utcoffset(self, dt): 2045 return timedelta(minutes = dt and 42 or -42) 2046 dst = utcoffset 2047 2048 obj = cls(1, 2, 3, tzinfo=introspective()) 2049 2050 expected = cls is time and "none" or "real" 2051 self.assertEqual(obj.tzname(), expected) 2052 2053 expected = timedelta(minutes=(cls is time and -42 or 42)) 2054 self.assertEqual(obj.utcoffset(), expected) 2055 self.assertEqual(obj.dst(), expected) 2056 2057 def test_bad_tzinfo_classes(self): 2058 cls = self.theclass 2059 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12) 2060 2061 class NiceTry(object): 2062 def __init__(self): pass 2063 def utcoffset(self, dt): pass 2064 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry) 2065 2066 class BetterTry(tzinfo): 2067 def __init__(self): pass 2068 def utcoffset(self, dt): pass 2069 b = BetterTry() 2070 t = cls(1, 1, 1, tzinfo=b) 2071 self.assertIs(t.tzinfo, b) 2072 2073 def test_utc_offset_out_of_bounds(self): 2074 class Edgy(tzinfo): 2075 def __init__(self, offset): 2076 self.offset = timedelta(minutes=offset) 2077 def utcoffset(self, dt): 2078 return self.offset 2079 2080 cls = self.theclass 2081 for offset, legit in ((-1440, False), 2082 (-1439, True), 2083 (1439, True), 2084 (1440, False)): 2085 if cls is time: 2086 t = cls(1, 2, 3, tzinfo=Edgy(offset)) 2087 elif cls is datetime: 2088 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset)) 2089 else: 2090 assert 0, "impossible" 2091 if legit: 2092 aofs = abs(offset) 2093 h, m = divmod(aofs, 60) 2094 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m) 2095 if isinstance(t, datetime): 2096 t = t.timetz() 2097 self.assertEqual(str(t), "01:02:03" + tag) 2098 else: 2099 self.assertRaises(ValueError, str, t) 2100 2101 def test_tzinfo_classes(self): 2102 cls = self.theclass 2103 class C1(tzinfo): 2104 def utcoffset(self, dt): return None 2105 def dst(self, dt): return None 2106 def tzname(self, dt): return None 2107 for t in (cls(1, 1, 1), 2108 cls(1, 1, 1, tzinfo=None), 2109 cls(1, 1, 1, tzinfo=C1())): 2110 self.assertIsNone(t.utcoffset()) 2111 self.assertIsNone(t.dst()) 2112 self.assertIsNone(t.tzname()) 2113 2114 class C3(tzinfo): 2115 def utcoffset(self, dt): return timedelta(minutes=-1439) 2116 def dst(self, dt): return timedelta(minutes=1439) 2117 def tzname(self, dt): return "aname" 2118 t = cls(1, 1, 1, tzinfo=C3()) 2119 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439)) 2120 self.assertEqual(t.dst(), timedelta(minutes=1439)) 2121 self.assertEqual(t.tzname(), "aname") 2122 2123 # Wrong types. 2124 class C4(tzinfo): 2125 def utcoffset(self, dt): return "aname" 2126 def dst(self, dt): return 7 2127 def tzname(self, dt): return 0 2128 t = cls(1, 1, 1, tzinfo=C4()) 2129 self.assertRaises(TypeError, t.utcoffset) 2130 self.assertRaises(TypeError, t.dst) 2131 self.assertRaises(TypeError, t.tzname) 2132 2133 # Offset out of range. 2134 class C6(tzinfo): 2135 def utcoffset(self, dt): return timedelta(hours=-24) 2136 def dst(self, dt): return timedelta(hours=24) 2137 t = cls(1, 1, 1, tzinfo=C6()) 2138 self.assertRaises(ValueError, t.utcoffset) 2139 self.assertRaises(ValueError, t.dst) 2140 2141 # Not a whole number of minutes. 2142 class C7(tzinfo): 2143 def utcoffset(self, dt): return timedelta(seconds=61) 2144 def dst(self, dt): return timedelta(microseconds=-81) 2145 t = cls(1, 1, 1, tzinfo=C7()) 2146 self.assertRaises(ValueError, t.utcoffset) 2147 self.assertRaises(ValueError, t.dst) 2148 2149 def test_aware_compare(self): 2150 cls = self.theclass 2151 2152 # Ensure that utcoffset() gets ignored if the comparands have 2153 # the same tzinfo member. 2154 class OperandDependentOffset(tzinfo): 2155 def utcoffset(self, t): 2156 if t.minute < 10: 2157 # d0 and d1 equal after adjustment 2158 return timedelta(minutes=t.minute) 2159 else: 2160 # d2 off in the weeds 2161 return timedelta(minutes=59) 2162 2163 base = cls(8, 9, 10, tzinfo=OperandDependentOffset()) 2164 d0 = base.replace(minute=3) 2165 d1 = base.replace(minute=9) 2166 d2 = base.replace(minute=11) 2167 for x in d0, d1, d2: 2168 for y in d0, d1, d2: 2169 got = cmp(x, y) 2170 expected = cmp(x.minute, y.minute) 2171 self.assertEqual(got, expected) 2172 2173 # However, if they're different members, uctoffset is not ignored. 2174 # Note that a time can't actually have an operand-depedent offset, 2175 # though (and time.utcoffset() passes None to tzinfo.utcoffset()), 2176 # so skip this test for time. 2177 if cls is not time: 2178 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset()) 2179 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset()) 2180 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset()) 2181 for x in d0, d1, d2: 2182 for y in d0, d1, d2: 2183 got = cmp(x, y) 2184 if (x is d0 or x is d1) and (y is d0 or y is d1): 2185 expected = 0 2186 elif x is y is d2: 2187 expected = 0 2188 elif x is d2: 2189 expected = -1 2190 else: 2191 assert y is d2 2192 expected = 1 2193 self.assertEqual(got, expected) 2194 2195 2196# Testing time objects with a non-None tzinfo. 2197class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase): 2198 theclass = time 2199 2200 def test_empty(self): 2201 t = self.theclass() 2202 self.assertEqual(t.hour, 0) 2203 self.assertEqual(t.minute, 0) 2204 self.assertEqual(t.second, 0) 2205 self.assertEqual(t.microsecond, 0) 2206 self.assertIsNone(t.tzinfo) 2207 2208 def test_zones(self): 2209 est = FixedOffset(-300, "EST", 1) 2210 utc = FixedOffset(0, "UTC", -2) 2211 met = FixedOffset(60, "MET", 3) 2212 t1 = time( 7, 47, tzinfo=est) 2213 t2 = time(12, 47, tzinfo=utc) 2214 t3 = time(13, 47, tzinfo=met) 2215 t4 = time(microsecond=40) 2216 t5 = time(microsecond=40, tzinfo=utc) 2217 2218 self.assertEqual(t1.tzinfo, est) 2219 self.assertEqual(t2.tzinfo, utc) 2220 self.assertEqual(t3.tzinfo, met) 2221 self.assertIsNone(t4.tzinfo) 2222 self.assertEqual(t5.tzinfo, utc) 2223 2224 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300)) 2225 self.assertEqual(t2.utcoffset(), timedelta(minutes=0)) 2226 self.assertEqual(t3.utcoffset(), timedelta(minutes=60)) 2227 self.assertIsNone(t4.utcoffset()) 2228 self.assertRaises(TypeError, t1.utcoffset, "no args") 2229 2230 self.assertEqual(t1.tzname(), "EST") 2231 self.assertEqual(t2.tzname(), "UTC") 2232 self.assertEqual(t3.tzname(), "MET") 2233 self.assertIsNone(t4.tzname()) 2234 self.assertRaises(TypeError, t1.tzname, "no args") 2235 2236 self.assertEqual(t1.dst(), timedelta(minutes=1)) 2237 self.assertEqual(t2.dst(), timedelta(minutes=-2)) 2238 self.assertEqual(t3.dst(), timedelta(minutes=3)) 2239 self.assertIsNone(t4.dst()) 2240 self.assertRaises(TypeError, t1.dst, "no args") 2241 2242 self.assertEqual(hash(t1), hash(t2)) 2243 self.assertEqual(hash(t1), hash(t3)) 2244 self.assertEqual(hash(t2), hash(t3)) 2245 2246 self.assertEqual(t1, t2) 2247 self.assertEqual(t1, t3) 2248 self.assertEqual(t2, t3) 2249 self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive 2250 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive 2251 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive 2252 2253 self.assertEqual(str(t1), "07:47:00-05:00") 2254 self.assertEqual(str(t2), "12:47:00+00:00") 2255 self.assertEqual(str(t3), "13:47:00+01:00") 2256 self.assertEqual(str(t4), "00:00:00.000040") 2257 self.assertEqual(str(t5), "00:00:00.000040+00:00") 2258 2259 self.assertEqual(t1.isoformat(), "07:47:00-05:00") 2260 self.assertEqual(t2.isoformat(), "12:47:00+00:00") 2261 self.assertEqual(t3.isoformat(), "13:47:00+01:00") 2262 self.assertEqual(t4.isoformat(), "00:00:00.000040") 2263 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00") 2264 2265 d = 'datetime.time' 2266 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)") 2267 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)") 2268 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)") 2269 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)") 2270 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)") 2271 2272 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"), 2273 "07:47:00 %Z=EST %z=-0500") 2274 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000") 2275 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100") 2276 2277 yuck = FixedOffset(-1439, "%z %Z %%z%%Z") 2278 t1 = time(23, 59, tzinfo=yuck) 2279 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"), 2280 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'") 2281 2282 # Check that an invalid tzname result raises an exception. 2283 class Badtzname(tzinfo): 2284 def tzname(self, dt): return 42 2285 t = time(2, 3, 4, tzinfo=Badtzname()) 2286 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04") 2287 self.assertRaises(TypeError, t.strftime, "%Z") 2288 2289 def test_hash_edge_cases(self): 2290 # Offsets that overflow a basic time. 2291 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, "")) 2292 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, "")) 2293 self.assertEqual(hash(t1), hash(t2)) 2294 2295 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, "")) 2296 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, "")) 2297 self.assertEqual(hash(t1), hash(t2)) 2298 2299 def test_pickling(self): 2300 # Try one without a tzinfo. 2301 args = 20, 59, 16, 64**2 2302 orig = self.theclass(*args) 2303 for pickler, unpickler, proto in pickle_choices: 2304 green = pickler.dumps(orig, proto) 2305 derived = unpickler.loads(green) 2306 self.assertEqual(orig, derived) 2307 2308 # Try one with a tzinfo. 2309 tinfo = PicklableFixedOffset(-300, 'cookie') 2310 orig = self.theclass(5, 6, 7, tzinfo=tinfo) 2311 for pickler, unpickler, proto in pickle_choices: 2312 green = pickler.dumps(orig, proto) 2313 derived = unpickler.loads(green) 2314 self.assertEqual(orig, derived) 2315 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset) 2316 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300)) 2317 self.assertEqual(derived.tzname(), 'cookie') 2318 2319 def test_more_bool(self): 2320 # Test cases with non-None tzinfo. 2321 cls = self.theclass 2322 2323 t = cls(0, tzinfo=FixedOffset(-300, "")) 2324 self.assertTrue(t) 2325 2326 t = cls(5, tzinfo=FixedOffset(-300, "")) 2327 self.assertTrue(t) 2328 2329 t = cls(5, tzinfo=FixedOffset(300, "")) 2330 self.assertFalse(t) 2331 2332 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, "")) 2333 self.assertFalse(t) 2334 2335 # Mostly ensuring this doesn't overflow internally. 2336 t = cls(0, tzinfo=FixedOffset(23*60 + 59, "")) 2337 self.assertTrue(t) 2338 2339 # But this should yield a value error -- the utcoffset is bogus. 2340 t = cls(0, tzinfo=FixedOffset(24*60, "")) 2341 self.assertRaises(ValueError, lambda: bool(t)) 2342 2343 # Likewise. 2344 t = cls(0, tzinfo=FixedOffset(-24*60, "")) 2345 self.assertRaises(ValueError, lambda: bool(t)) 2346 2347 def test_replace(self): 2348 cls = self.theclass 2349 z100 = FixedOffset(100, "+100") 2350 zm200 = FixedOffset(timedelta(minutes=-200), "-200") 2351 args = [1, 2, 3, 4, z100] 2352 base = cls(*args) 2353 self.assertEqual(base, base.replace()) 2354 2355 i = 0 2356 for name, newval in (("hour", 5), 2357 ("minute", 6), 2358 ("second", 7), 2359 ("microsecond", 8), 2360 ("tzinfo", zm200)): 2361 newargs = args[:] 2362 newargs[i] = newval 2363 expected = cls(*newargs) 2364 got = base.replace(**{name: newval}) 2365 self.assertEqual(expected, got) 2366 i += 1 2367 2368 # Ensure we can get rid of a tzinfo. 2369 self.assertEqual(base.tzname(), "+100") 2370 base2 = base.replace(tzinfo=None) 2371 self.assertIsNone(base2.tzinfo) 2372 self.assertIsNone(base2.tzname()) 2373 2374 # Ensure we can add one. 2375 base3 = base2.replace(tzinfo=z100) 2376 self.assertEqual(base, base3) 2377 self.assertIs(base.tzinfo, base3.tzinfo) 2378 2379 # Out of bounds. 2380 base = cls(1) 2381 self.assertRaises(ValueError, base.replace, hour=24) 2382 self.assertRaises(ValueError, base.replace, minute=-1) 2383 self.assertRaises(ValueError, base.replace, second=100) 2384 self.assertRaises(ValueError, base.replace, microsecond=1000000) 2385 2386 def test_mixed_compare(self): 2387 t1 = time(1, 2, 3) 2388 t2 = time(1, 2, 3) 2389 self.assertEqual(t1, t2) 2390 t2 = t2.replace(tzinfo=None) 2391 self.assertEqual(t1, t2) 2392 t2 = t2.replace(tzinfo=FixedOffset(None, "")) 2393 self.assertEqual(t1, t2) 2394 t2 = t2.replace(tzinfo=FixedOffset(0, "")) 2395 self.assertRaises(TypeError, lambda: t1 == t2) 2396 2397 # In time w/ identical tzinfo objects, utcoffset is ignored. 2398 class Varies(tzinfo): 2399 def __init__(self): 2400 self.offset = timedelta(minutes=22) 2401 def utcoffset(self, t): 2402 self.offset += timedelta(minutes=1) 2403 return self.offset 2404 2405 v = Varies() 2406 t1 = t2.replace(tzinfo=v) 2407 t2 = t2.replace(tzinfo=v) 2408 self.assertEqual(t1.utcoffset(), timedelta(minutes=23)) 2409 self.assertEqual(t2.utcoffset(), timedelta(minutes=24)) 2410 self.assertEqual(t1, t2) 2411 2412 # But if they're not identical, it isn't ignored. 2413 t2 = t2.replace(tzinfo=Varies()) 2414 self.assertTrue(t1 < t2) # t1's offset counter still going up 2415 2416 def test_subclass_timetz(self): 2417 2418 class C(self.theclass): 2419 theAnswer = 42 2420 2421 def __new__(cls, *args, **kws): 2422 temp = kws.copy() 2423 extra = temp.pop('extra') 2424 result = self.theclass.__new__(cls, *args, **temp) 2425 result.extra = extra 2426 return result 2427 2428 def newmeth(self, start): 2429 return start + self.hour + self.second 2430 2431 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1) 2432 2433 dt1 = self.theclass(*args) 2434 dt2 = C(*args, **{'extra': 7}) 2435 2436 self.assertEqual(dt2.__class__, C) 2437 self.assertEqual(dt2.theAnswer, 42) 2438 self.assertEqual(dt2.extra, 7) 2439 self.assertEqual(dt1.utcoffset(), dt2.utcoffset()) 2440 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7) 2441 2442 2443# Testing datetime objects with a non-None tzinfo. 2444 2445class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase): 2446 theclass = datetime 2447 2448 def test_trivial(self): 2449 dt = self.theclass(1, 2, 3, 4, 5, 6, 7) 2450 self.assertEqual(dt.year, 1) 2451 self.assertEqual(dt.month, 2) 2452 self.assertEqual(dt.day, 3) 2453 self.assertEqual(dt.hour, 4) 2454 self.assertEqual(dt.minute, 5) 2455 self.assertEqual(dt.second, 6) 2456 self.assertEqual(dt.microsecond, 7) 2457 self.assertEqual(dt.tzinfo, None) 2458 2459 def test_even_more_compare(self): 2460 # The test_compare() and test_more_compare() inherited from TestDate 2461 # and TestDateTime covered non-tzinfo cases. 2462 2463 # Smallest possible after UTC adjustment. 2464 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "")) 2465 # Largest possible after UTC adjustment. 2466 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999, 2467 tzinfo=FixedOffset(-1439, "")) 2468 2469 # Make sure those compare correctly, and w/o overflow. 2470 self.assertTrue(t1 < t2) 2471 self.assertTrue(t1 != t2) 2472 self.assertTrue(t2 > t1) 2473 2474 self.assertTrue(t1 == t1) 2475 self.assertTrue(t2 == t2) 2476 2477 # Equal afer adjustment. 2478 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, "")) 2479 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, "")) 2480 self.assertEqual(t1, t2) 2481 2482 # Change t1 not to subtract a minute, and t1 should be larger. 2483 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, "")) 2484 self.assertTrue(t1 > t2) 2485 2486 # Change t1 to subtract 2 minutes, and t1 should be smaller. 2487 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, "")) 2488 self.assertTrue(t1 < t2) 2489 2490 # Back to the original t1, but make seconds resolve it. 2491 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""), 2492 second=1) 2493 self.assertTrue(t1 > t2) 2494 2495 # Likewise, but make microseconds resolve it. 2496 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""), 2497 microsecond=1) 2498 self.assertTrue(t1 > t2) 2499 2500 # Make t2 naive and it should fail. 2501 t2 = self.theclass.min 2502 self.assertRaises(TypeError, lambda: t1 == t2) 2503 self.assertEqual(t2, t2) 2504 2505 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None. 2506 class Naive(tzinfo): 2507 def utcoffset(self, dt): return None 2508 t2 = self.theclass(5, 6, 7, tzinfo=Naive()) 2509 self.assertRaises(TypeError, lambda: t1 == t2) 2510 self.assertEqual(t2, t2) 2511 2512 # OTOH, it's OK to compare two of these mixing the two ways of being 2513 # naive. 2514 t1 = self.theclass(5, 6, 7) 2515 self.assertEqual(t1, t2) 2516 2517 # Try a bogus uctoffset. 2518 class Bogus(tzinfo): 2519 def utcoffset(self, dt): 2520 return timedelta(minutes=1440) # out of bounds 2521 t1 = self.theclass(2, 2, 2, tzinfo=Bogus()) 2522 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, "")) 2523 self.assertRaises(ValueError, lambda: t1 == t2) 2524 2525 def test_pickling(self): 2526 # Try one without a tzinfo. 2527 args = 6, 7, 23, 20, 59, 1, 64**2 2528 orig = self.theclass(*args) 2529 for pickler, unpickler, proto in pickle_choices: 2530 green = pickler.dumps(orig, proto) 2531 derived = unpickler.loads(green) 2532 self.assertEqual(orig, derived) 2533 2534 # Try one with a tzinfo. 2535 tinfo = PicklableFixedOffset(-300, 'cookie') 2536 orig = self.theclass(*args, **{'tzinfo': tinfo}) 2537 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0)) 2538 for pickler, unpickler, proto in pickle_choices: 2539 green = pickler.dumps(orig, proto) 2540 derived = unpickler.loads(green) 2541 self.assertEqual(orig, derived) 2542 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset) 2543 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300)) 2544 self.assertEqual(derived.tzname(), 'cookie') 2545 2546 def test_extreme_hashes(self): 2547 # If an attempt is made to hash these via subtracting the offset 2548 # then hashing a datetime object, OverflowError results. The 2549 # Python implementation used to blow up here. 2550 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "")) 2551 hash(t) 2552 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999, 2553 tzinfo=FixedOffset(-1439, "")) 2554 hash(t) 2555 2556 # OTOH, an OOB offset should blow up. 2557 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, "")) 2558 self.assertRaises(ValueError, hash, t) 2559 2560 def test_zones(self): 2561 est = FixedOffset(-300, "EST") 2562 utc = FixedOffset(0, "UTC") 2563 met = FixedOffset(60, "MET") 2564 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est) 2565 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc) 2566 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met) 2567 self.assertEqual(t1.tzinfo, est) 2568 self.assertEqual(t2.tzinfo, utc) 2569 self.assertEqual(t3.tzinfo, met) 2570 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300)) 2571 self.assertEqual(t2.utcoffset(), timedelta(minutes=0)) 2572 self.assertEqual(t3.utcoffset(), timedelta(minutes=60)) 2573 self.assertEqual(t1.tzname(), "EST") 2574 self.assertEqual(t2.tzname(), "UTC") 2575 self.assertEqual(t3.tzname(), "MET") 2576 self.assertEqual(hash(t1), hash(t2)) 2577 self.assertEqual(hash(t1), hash(t3)) 2578 self.assertEqual(hash(t2), hash(t3)) 2579 self.assertEqual(t1, t2) 2580 self.assertEqual(t1, t3) 2581 self.assertEqual(t2, t3) 2582 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00") 2583 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00") 2584 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00") 2585 d = 'datetime.datetime(2002, 3, 19, ' 2586 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)") 2587 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)") 2588 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)") 2589 2590 def test_combine(self): 2591 met = FixedOffset(60, "MET") 2592 d = date(2002, 3, 4) 2593 tz = time(18, 45, 3, 1234, tzinfo=met) 2594 dt = datetime.combine(d, tz) 2595 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234, 2596 tzinfo=met)) 2597 2598 def test_extract(self): 2599 met = FixedOffset(60, "MET") 2600 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met) 2601 self.assertEqual(dt.date(), date(2002, 3, 4)) 2602 self.assertEqual(dt.time(), time(18, 45, 3, 1234)) 2603 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met)) 2604 2605 def test_tz_aware_arithmetic(self): 2606 import random 2607 2608 now = self.theclass.now() 2609 tz55 = FixedOffset(-330, "west 5:30") 2610 timeaware = now.time().replace(tzinfo=tz55) 2611 nowaware = self.theclass.combine(now.date(), timeaware) 2612 self.assertIs(nowaware.tzinfo, tz55) 2613 self.assertEqual(nowaware.timetz(), timeaware) 2614 2615 # Can't mix aware and non-aware. 2616 self.assertRaises(TypeError, lambda: now - nowaware) 2617 self.assertRaises(TypeError, lambda: nowaware - now) 2618 2619 # And adding datetime's doesn't make sense, aware or not. 2620 self.assertRaises(TypeError, lambda: now + nowaware) 2621 self.assertRaises(TypeError, lambda: nowaware + now) 2622 self.assertRaises(TypeError, lambda: nowaware + nowaware) 2623 2624 # Subtracting should yield 0. 2625 self.assertEqual(now - now, timedelta(0)) 2626 self.assertEqual(nowaware - nowaware, timedelta(0)) 2627 2628 # Adding a delta should preserve tzinfo. 2629 delta = timedelta(weeks=1, minutes=12, microseconds=5678) 2630 nowawareplus = nowaware + delta 2631 self.assertIs(nowaware.tzinfo, tz55) 2632 nowawareplus2 = delta + nowaware 2633 self.assertIs(nowawareplus2.tzinfo, tz55) 2634 self.assertEqual(nowawareplus, nowawareplus2) 2635 2636 # that - delta should be what we started with, and that - what we 2637 # started with should be delta. 2638 diff = nowawareplus - delta 2639 self.assertIs(diff.tzinfo, tz55) 2640 self.assertEqual(nowaware, diff) 2641 self.assertRaises(TypeError, lambda: delta - nowawareplus) 2642 self.assertEqual(nowawareplus - nowaware, delta) 2643 2644 # Make up a random timezone. 2645 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone") 2646 # Attach it to nowawareplus. 2647 nowawareplus = nowawareplus.replace(tzinfo=tzr) 2648 self.assertIs(nowawareplus.tzinfo, tzr) 2649 # Make sure the difference takes the timezone adjustments into account. 2650 got = nowaware - nowawareplus 2651 # Expected: (nowaware base - nowaware offset) - 2652 # (nowawareplus base - nowawareplus offset) = 2653 # (nowaware base - nowawareplus base) + 2654 # (nowawareplus offset - nowaware offset) = 2655 # -delta + nowawareplus offset - nowaware offset 2656 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta 2657 self.assertEqual(got, expected) 2658 2659 # Try max possible difference. 2660 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min")) 2661 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999, 2662 tzinfo=FixedOffset(-1439, "max")) 2663 maxdiff = max - min 2664 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min + 2665 timedelta(minutes=2*1439)) 2666 2667 def test_tzinfo_now(self): 2668 meth = self.theclass.now 2669 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up). 2670 base = meth() 2671 # Try with and without naming the keyword. 2672 off42 = FixedOffset(42, "42") 2673 another = meth(off42) 2674 again = meth(tz=off42) 2675 self.assertIs(another.tzinfo, again.tzinfo) 2676 self.assertEqual(another.utcoffset(), timedelta(minutes=42)) 2677 # Bad argument with and w/o naming the keyword. 2678 self.assertRaises(TypeError, meth, 16) 2679 self.assertRaises(TypeError, meth, tzinfo=16) 2680 # Bad keyword name. 2681 self.assertRaises(TypeError, meth, tinfo=off42) 2682 # Too many args. 2683 self.assertRaises(TypeError, meth, off42, off42) 2684 2685 # We don't know which time zone we're in, and don't have a tzinfo 2686 # class to represent it, so seeing whether a tz argument actually 2687 # does a conversion is tricky. 2688 weirdtz = FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0) 2689 utc = FixedOffset(0, "utc", 0) 2690 for dummy in range(3): 2691 now = datetime.now(weirdtz) 2692 self.assertIs(now.tzinfo, weirdtz) 2693 utcnow = datetime.utcnow().replace(tzinfo=utc) 2694 now2 = utcnow.astimezone(weirdtz) 2695 if abs(now - now2) < timedelta(seconds=30): 2696 break 2697 # Else the code is broken, or more than 30 seconds passed between 2698 # calls; assuming the latter, just try again. 2699 else: 2700 # Three strikes and we're out. 2701 self.fail("utcnow(), now(tz), or astimezone() may be broken") 2702 2703 def test_tzinfo_fromtimestamp(self): 2704 import time 2705 meth = self.theclass.fromtimestamp 2706 ts = time.time() 2707 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up). 2708 base = meth(ts) 2709 # Try with and without naming the keyword. 2710 off42 = FixedOffset(42, "42") 2711 another = meth(ts, off42) 2712 again = meth(ts, tz=off42) 2713 self.assertIs(another.tzinfo, again.tzinfo) 2714 self.assertEqual(another.utcoffset(), timedelta(minutes=42)) 2715 # Bad argument with and w/o naming the keyword. 2716 self.assertRaises(TypeError, meth, ts, 16) 2717 self.assertRaises(TypeError, meth, ts, tzinfo=16) 2718 # Bad keyword name. 2719 self.assertRaises(TypeError, meth, ts, tinfo=off42) 2720 # Too many args. 2721 self.assertRaises(TypeError, meth, ts, off42, off42) 2722 # Too few args. 2723 self.assertRaises(TypeError, meth) 2724 2725 # Try to make sure tz= actually does some conversion. 2726 timestamp = 1000000000 2727 utcdatetime = datetime.utcfromtimestamp(timestamp) 2728 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take. 2729 # But on some flavor of Mac, it's nowhere near that. So we can't have 2730 # any idea here what time that actually is, we can only test that 2731 # relative changes match. 2732 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero 2733 tz = FixedOffset(utcoffset, "tz", 0) 2734 expected = utcdatetime + utcoffset 2735 got = datetime.fromtimestamp(timestamp, tz) 2736 self.assertEqual(expected, got.replace(tzinfo=None)) 2737 2738 def test_tzinfo_utcnow(self): 2739 meth = self.theclass.utcnow 2740 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up). 2741 base = meth() 2742 # Try with and without naming the keyword; for whatever reason, 2743 # utcnow() doesn't accept a tzinfo argument. 2744 off42 = FixedOffset(42, "42") 2745 self.assertRaises(TypeError, meth, off42) 2746 self.assertRaises(TypeError, meth, tzinfo=off42) 2747 2748 def test_tzinfo_utcfromtimestamp(self): 2749 import time 2750 meth = self.theclass.utcfromtimestamp 2751 ts = time.time() 2752 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up). 2753 base = meth(ts) 2754 # Try with and without naming the keyword; for whatever reason, 2755 # utcfromtimestamp() doesn't accept a tzinfo argument. 2756 off42 = FixedOffset(42, "42") 2757 self.assertRaises(TypeError, meth, ts, off42) 2758 self.assertRaises(TypeError, meth, ts, tzinfo=off42) 2759 2760 def test_tzinfo_timetuple(self): 2761 # TestDateTime tested most of this. datetime adds a twist to the 2762 # DST flag. 2763 class DST(tzinfo): 2764 def __init__(self, dstvalue): 2765 if isinstance(dstvalue, int): 2766 dstvalue = timedelta(minutes=dstvalue) 2767 self.dstvalue = dstvalue 2768 def dst(self, dt): 2769 return self.dstvalue 2770 2771 cls = self.theclass 2772 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1): 2773 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue)) 2774 t = d.timetuple() 2775 self.assertEqual(1, t.tm_year) 2776 self.assertEqual(1, t.tm_mon) 2777 self.assertEqual(1, t.tm_mday) 2778 self.assertEqual(10, t.tm_hour) 2779 self.assertEqual(20, t.tm_min) 2780 self.assertEqual(30, t.tm_sec) 2781 self.assertEqual(0, t.tm_wday) 2782 self.assertEqual(1, t.tm_yday) 2783 self.assertEqual(flag, t.tm_isdst) 2784 2785 # dst() returns wrong type. 2786 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple) 2787 2788 # dst() at the edge. 2789 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1) 2790 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1) 2791 2792 # dst() out of range. 2793 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple) 2794 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple) 2795 2796 def test_utctimetuple(self): 2797 class DST(tzinfo): 2798 def __init__(self, dstvalue): 2799 if isinstance(dstvalue, int): 2800 dstvalue = timedelta(minutes=dstvalue) 2801 self.dstvalue = dstvalue 2802 def dst(self, dt): 2803 return self.dstvalue 2804 2805 cls = self.theclass 2806 # This can't work: DST didn't implement utcoffset. 2807 self.assertRaises(NotImplementedError, 2808 cls(1, 1, 1, tzinfo=DST(0)).utcoffset) 2809 2810 class UOFS(DST): 2811 def __init__(self, uofs, dofs=None): 2812 DST.__init__(self, dofs) 2813 self.uofs = timedelta(minutes=uofs) 2814 def utcoffset(self, dt): 2815 return self.uofs 2816 2817 # Ensure tm_isdst is 0 regardless of what dst() says: DST is never 2818 # in effect for a UTC time. 2819 for dstvalue in -33, 33, 0, None: 2820 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue)) 2821 t = d.utctimetuple() 2822 self.assertEqual(d.year, t.tm_year) 2823 self.assertEqual(d.month, t.tm_mon) 2824 self.assertEqual(d.day, t.tm_mday) 2825 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm 2826 self.assertEqual(13, t.tm_min) 2827 self.assertEqual(d.second, t.tm_sec) 2828 self.assertEqual(d.weekday(), t.tm_wday) 2829 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1, 2830 t.tm_yday) 2831 self.assertEqual(0, t.tm_isdst) 2832 2833 # At the edges, UTC adjustment can normalize into years out-of-range 2834 # for a datetime object. Ensure that a correct timetuple is 2835 # created anyway. 2836 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439)) 2837 # That goes back 1 minute less than a full day. 2838 t = tiny.utctimetuple() 2839 self.assertEqual(t.tm_year, MINYEAR-1) 2840 self.assertEqual(t.tm_mon, 12) 2841 self.assertEqual(t.tm_mday, 31) 2842 self.assertEqual(t.tm_hour, 0) 2843 self.assertEqual(t.tm_min, 1) 2844 self.assertEqual(t.tm_sec, 37) 2845 self.assertEqual(t.tm_yday, 366) # "year 0" is a leap year 2846 self.assertEqual(t.tm_isdst, 0) 2847 2848 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439)) 2849 # That goes forward 1 minute less than a full day. 2850 t = huge.utctimetuple() 2851 self.assertEqual(t.tm_year, MAXYEAR+1) 2852 self.assertEqual(t.tm_mon, 1) 2853 self.assertEqual(t.tm_mday, 1) 2854 self.assertEqual(t.tm_hour, 23) 2855 self.assertEqual(t.tm_min, 58) 2856 self.assertEqual(t.tm_sec, 37) 2857 self.assertEqual(t.tm_yday, 1) 2858 self.assertEqual(t.tm_isdst, 0) 2859 2860 def test_tzinfo_isoformat(self): 2861 zero = FixedOffset(0, "+00:00") 2862 plus = FixedOffset(220, "+03:40") 2863 minus = FixedOffset(-231, "-03:51") 2864 unknown = FixedOffset(None, "") 2865 2866 cls = self.theclass 2867 datestr = '0001-02-03' 2868 for ofs in None, zero, plus, minus, unknown: 2869 for us in 0, 987001: 2870 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs) 2871 timestr = '04:05:59' + (us and '.987001' or '') 2872 ofsstr = ofs is not None and d.tzname() or '' 2873 tailstr = timestr + ofsstr 2874 iso = d.isoformat() 2875 self.assertEqual(iso, datestr + 'T' + tailstr) 2876 self.assertEqual(iso, d.isoformat('T')) 2877 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr) 2878 self.assertEqual(str(d), datestr + ' ' + tailstr) 2879 2880 def test_replace(self): 2881 cls = self.theclass 2882 z100 = FixedOffset(100, "+100") 2883 zm200 = FixedOffset(timedelta(minutes=-200), "-200") 2884 args = [1, 2, 3, 4, 5, 6, 7, z100] 2885 base = cls(*args) 2886 self.assertEqual(base, base.replace()) 2887 2888 i = 0 2889 for name, newval in (("year", 2), 2890 ("month", 3), 2891 ("day", 4), 2892 ("hour", 5), 2893 ("minute", 6), 2894 ("second", 7), 2895 ("microsecond", 8), 2896 ("tzinfo", zm200)): 2897 newargs = args[:] 2898 newargs[i] = newval 2899 expected = cls(*newargs) 2900 got = base.replace(**{name: newval}) 2901 self.assertEqual(expected, got) 2902 i += 1 2903 2904 # Ensure we can get rid of a tzinfo. 2905 self.assertEqual(base.tzname(), "+100") 2906 base2 = base.replace(tzinfo=None) 2907 self.assertIsNone(base2.tzinfo) 2908 self.assertIsNone(base2.tzname()) 2909 2910 # Ensure we can add one. 2911 base3 = base2.replace(tzinfo=z100) 2912 self.assertEqual(base, base3) 2913 self.assertIs(base.tzinfo, base3.tzinfo) 2914 2915 # Out of bounds. 2916 base = cls(2000, 2, 29) 2917 self.assertRaises(ValueError, base.replace, year=2001) 2918 2919 def test_more_astimezone(self): 2920 # The inherited test_astimezone covered some trivial and error cases. 2921 fnone = FixedOffset(None, "None") 2922 f44m = FixedOffset(44, "44") 2923 fm5h = FixedOffset(-timedelta(hours=5), "m300") 2924 2925 dt = self.theclass.now(tz=f44m) 2926 self.assertIs(dt.tzinfo, f44m) 2927 # Replacing with degenerate tzinfo raises an exception. 2928 self.assertRaises(ValueError, dt.astimezone, fnone) 2929 # Ditto with None tz. 2930 self.assertRaises(TypeError, dt.astimezone, None) 2931 # Replacing with same tzinfo makes no change. 2932 x = dt.astimezone(dt.tzinfo) 2933 self.assertIs(x.tzinfo, f44m) 2934 self.assertEqual(x.date(), dt.date()) 2935 self.assertEqual(x.time(), dt.time()) 2936 2937 # Replacing with different tzinfo does adjust. 2938 got = dt.astimezone(fm5h) 2939 self.assertIs(got.tzinfo, fm5h) 2940 self.assertEqual(got.utcoffset(), timedelta(hours=-5)) 2941 expected = dt - dt.utcoffset() # in effect, convert to UTC 2942 expected += fm5h.utcoffset(dt) # and from there to local time 2943 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo 2944 self.assertEqual(got.date(), expected.date()) 2945 self.assertEqual(got.time(), expected.time()) 2946 self.assertEqual(got.timetz(), expected.timetz()) 2947 self.assertIs(got.tzinfo, expected.tzinfo) 2948 self.assertEqual(got, expected) 2949 2950 def test_aware_subtract(self): 2951 cls = self.theclass 2952 2953 # Ensure that utcoffset() is ignored when the operands have the 2954 # same tzinfo member. 2955 class OperandDependentOffset(tzinfo): 2956 def utcoffset(self, t): 2957 if t.minute < 10: 2958 # d0 and d1 equal after adjustment 2959 return timedelta(minutes=t.minute) 2960 else: 2961 # d2 off in the weeds 2962 return timedelta(minutes=59) 2963 2964 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset()) 2965 d0 = base.replace(minute=3) 2966 d1 = base.replace(minute=9) 2967 d2 = base.replace(minute=11) 2968 for x in d0, d1, d2: 2969 for y in d0, d1, d2: 2970 got = x - y 2971 expected = timedelta(minutes=x.minute - y.minute) 2972 self.assertEqual(got, expected) 2973 2974 # OTOH, if the tzinfo members are distinct, utcoffsets aren't 2975 # ignored. 2976 base = cls(8, 9, 10, 11, 12, 13, 14) 2977 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset()) 2978 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset()) 2979 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset()) 2980 for x in d0, d1, d2: 2981 for y in d0, d1, d2: 2982 got = x - y 2983 if (x is d0 or x is d1) and (y is d0 or y is d1): 2984 expected = timedelta(0) 2985 elif x is y is d2: 2986 expected = timedelta(0) 2987 elif x is d2: 2988 expected = timedelta(minutes=(11-59)-0) 2989 else: 2990 assert y is d2 2991 expected = timedelta(minutes=0-(11-59)) 2992 self.assertEqual(got, expected) 2993 2994 def test_mixed_compare(self): 2995 t1 = datetime(1, 2, 3, 4, 5, 6, 7) 2996 t2 = datetime(1, 2, 3, 4, 5, 6, 7) 2997 self.assertEqual(t1, t2) 2998 t2 = t2.replace(tzinfo=None) 2999 self.assertEqual(t1, t2) 3000 t2 = t2.replace(tzinfo=FixedOffset(None, "")) 3001 self.assertEqual(t1, t2) 3002 t2 = t2.replace(tzinfo=FixedOffset(0, "")) 3003 self.assertRaises(TypeError, lambda: t1 == t2) 3004 3005 # In datetime w/ identical tzinfo objects, utcoffset is ignored. 3006 class Varies(tzinfo): 3007 def __init__(self): 3008 self.offset = timedelta(minutes=22) 3009 def utcoffset(self, t): 3010 self.offset += timedelta(minutes=1) 3011 return self.offset 3012 3013 v = Varies() 3014 t1 = t2.replace(tzinfo=v) 3015 t2 = t2.replace(tzinfo=v) 3016 self.assertEqual(t1.utcoffset(), timedelta(minutes=23)) 3017 self.assertEqual(t2.utcoffset(), timedelta(minutes=24)) 3018 self.assertEqual(t1, t2) 3019 3020 # But if they're not identical, it isn't ignored. 3021 t2 = t2.replace(tzinfo=Varies()) 3022 self.assertTrue(t1 < t2) # t1's offset counter still going up 3023 3024 def test_subclass_datetimetz(self): 3025 3026 class C(self.theclass): 3027 theAnswer = 42 3028 3029 def __new__(cls, *args, **kws): 3030 temp = kws.copy() 3031 extra = temp.pop('extra') 3032 result = self.theclass.__new__(cls, *args, **temp) 3033 result.extra = extra 3034 return result 3035 3036 def newmeth(self, start): 3037 return start + self.hour + self.year 3038 3039 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1) 3040 3041 dt1 = self.theclass(*args) 3042 dt2 = C(*args, **{'extra': 7}) 3043 3044 self.assertEqual(dt2.__class__, C) 3045 self.assertEqual(dt2.theAnswer, 42) 3046 self.assertEqual(dt2.extra, 7) 3047 self.assertEqual(dt1.utcoffset(), dt2.utcoffset()) 3048 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7) 3049 3050# Pain to set up DST-aware tzinfo classes. 3051 3052def first_sunday_on_or_after(dt): 3053 days_to_go = 6 - dt.weekday() 3054 if days_to_go: 3055 dt += timedelta(days_to_go) 3056 return dt 3057 3058ZERO = timedelta(0) 3059HOUR = timedelta(hours=1) 3060DAY = timedelta(days=1) 3061# In the US, DST starts at 2am (standard time) on the first Sunday in April. 3062DSTSTART = datetime(1, 4, 1, 2) 3063# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct, 3064# which is the first Sunday on or after Oct 25. Because we view 1:MM as 3065# being standard time on that day, there is no spelling in local time of 3066# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time). 3067DSTEND = datetime(1, 10, 25, 1) 3068 3069class USTimeZone(tzinfo): 3070 3071 def __init__(self, hours, reprname, stdname, dstname): 3072 self.stdoffset = timedelta(hours=hours) 3073 self.reprname = reprname 3074 self.stdname = stdname 3075 self.dstname = dstname 3076 3077 def __repr__(self): 3078 return self.reprname 3079 3080 def tzname(self, dt): 3081 if self.dst(dt): 3082 return self.dstname 3083 else: 3084 return self.stdname 3085 3086 def utcoffset(self, dt): 3087 return self.stdoffset + self.dst(dt) 3088 3089 def dst(self, dt): 3090 if dt is None or dt.tzinfo is None: 3091 # An exception instead may be sensible here, in one or more of 3092 # the cases. 3093 return ZERO 3094 assert dt.tzinfo is self 3095 3096 # Find first Sunday in April. 3097 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year)) 3098 assert start.weekday() == 6 and start.month == 4 and start.day <= 7 3099 3100 # Find last Sunday in October. 3101 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year)) 3102 assert end.weekday() == 6 and end.month == 10 and end.day >= 25 3103 3104 # Can't compare naive to aware objects, so strip the timezone from 3105 # dt first. 3106 if start <= dt.replace(tzinfo=None) < end: 3107 return HOUR 3108 else: 3109 return ZERO 3110 3111Eastern = USTimeZone(-5, "Eastern", "EST", "EDT") 3112Central = USTimeZone(-6, "Central", "CST", "CDT") 3113Mountain = USTimeZone(-7, "Mountain", "MST", "MDT") 3114Pacific = USTimeZone(-8, "Pacific", "PST", "PDT") 3115utc_real = FixedOffset(0, "UTC", 0) 3116# For better test coverage, we want another flavor of UTC that's west of 3117# the Eastern and Pacific timezones. 3118utc_fake = FixedOffset(-12*60, "UTCfake", 0) 3119 3120class TestTimezoneConversions(unittest.TestCase): 3121 # The DST switch times for 2002, in std time. 3122 dston = datetime(2002, 4, 7, 2) 3123 dstoff = datetime(2002, 10, 27, 1) 3124 3125 theclass = datetime 3126 3127 # Check a time that's inside DST. 3128 def checkinside(self, dt, tz, utc, dston, dstoff): 3129 self.assertEqual(dt.dst(), HOUR) 3130 3131 # Conversion to our own timezone is always an identity. 3132 self.assertEqual(dt.astimezone(tz), dt) 3133 3134 asutc = dt.astimezone(utc) 3135 there_and_back = asutc.astimezone(tz) 3136 3137 # Conversion to UTC and back isn't always an identity here, 3138 # because there are redundant spellings (in local time) of 3139 # UTC time when DST begins: the clock jumps from 1:59:59 3140 # to 3:00:00, and a local time of 2:MM:SS doesn't really 3141 # make sense then. The classes above treat 2:MM:SS as 3142 # daylight time then (it's "after 2am"), really an alias 3143 # for 1:MM:SS standard time. The latter form is what 3144 # conversion back from UTC produces. 3145 if dt.date() == dston.date() and dt.hour == 2: 3146 # We're in the redundant hour, and coming back from 3147 # UTC gives the 1:MM:SS standard-time spelling. 3148 self.assertEqual(there_and_back + HOUR, dt) 3149 # Although during was considered to be in daylight 3150 # time, there_and_back is not. 3151 self.assertEqual(there_and_back.dst(), ZERO) 3152 # They're the same times in UTC. 3153 self.assertEqual(there_and_back.astimezone(utc), 3154 dt.astimezone(utc)) 3155 else: 3156 # We're not in the redundant hour. 3157 self.assertEqual(dt, there_and_back) 3158 3159 # Because we have a redundant spelling when DST begins, there is 3160 # (unfortunately) an hour when DST ends that can't be spelled at all in 3161 # local time. When DST ends, the clock jumps from 1:59 back to 1:00 3162 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be 3163 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be 3164 # daylight time. The hour 1:MM daylight == 0:MM standard can't be 3165 # expressed in local time. Nevertheless, we want conversion back 3166 # from UTC to mimic the local clock's "repeat an hour" behavior. 3167 nexthour_utc = asutc + HOUR 3168 nexthour_tz = nexthour_utc.astimezone(tz) 3169 if dt.date() == dstoff.date() and dt.hour == 0: 3170 # We're in the hour before the last DST hour. The last DST hour 3171 # is ineffable. We want the conversion back to repeat 1:MM. 3172 self.assertEqual(nexthour_tz, dt.replace(hour=1)) 3173 nexthour_utc += HOUR 3174 nexthour_tz = nexthour_utc.astimezone(tz) 3175 self.assertEqual(nexthour_tz, dt.replace(hour=1)) 3176 else: 3177 self.assertEqual(nexthour_tz - dt, HOUR) 3178 3179 # Check a time that's outside DST. 3180 def checkoutside(self, dt, tz, utc): 3181 self.assertEqual(dt.dst(), ZERO) 3182 3183 # Conversion to our own timezone is always an identity. 3184 self.assertEqual(dt.astimezone(tz), dt) 3185 3186 # Converting to UTC and back is an identity too. 3187 asutc = dt.astimezone(utc) 3188 there_and_back = asutc.astimezone(tz) 3189 self.assertEqual(dt, there_and_back) 3190 3191 def convert_between_tz_and_utc(self, tz, utc): 3192 dston = self.dston.replace(tzinfo=tz) 3193 # Because 1:MM on the day DST ends is taken as being standard time, 3194 # there is no spelling in tz for the last hour of daylight time. 3195 # For purposes of the test, the last hour of DST is 0:MM, which is 3196 # taken as being daylight time (and 1:MM is taken as being standard 3197 # time). 3198 dstoff = self.dstoff.replace(tzinfo=tz) 3199 for delta in (timedelta(weeks=13), 3200 DAY, 3201 HOUR, 3202 timedelta(minutes=1), 3203 timedelta(microseconds=1)): 3204 3205 self.checkinside(dston, tz, utc, dston, dstoff) 3206 for during in dston + delta, dstoff - delta: 3207 self.checkinside(during, tz, utc, dston, dstoff) 3208 3209 self.checkoutside(dstoff, tz, utc) 3210 for outside in dston - delta, dstoff + delta: 3211 self.checkoutside(outside, tz, utc) 3212 3213 def test_easy(self): 3214 # Despite the name of this test, the endcases are excruciating. 3215 self.convert_between_tz_and_utc(Eastern, utc_real) 3216 self.convert_between_tz_and_utc(Pacific, utc_real) 3217 self.convert_between_tz_and_utc(Eastern, utc_fake) 3218 self.convert_between_tz_and_utc(Pacific, utc_fake) 3219 # The next is really dancing near the edge. It works because 3220 # Pacific and Eastern are far enough apart that their "problem 3221 # hours" don't overlap. 3222 self.convert_between_tz_and_utc(Eastern, Pacific) 3223 self.convert_between_tz_and_utc(Pacific, Eastern) 3224 # OTOH, these fail! Don't enable them. The difficulty is that 3225 # the edge case tests assume that every hour is representable in 3226 # the "utc" class. This is always true for a fixed-offset tzinfo 3227 # class (lke utc_real and utc_fake), but not for Eastern or Central. 3228 # For these adjacent DST-aware time zones, the range of time offsets 3229 # tested ends up creating hours in the one that aren't representable 3230 # in the other. For the same reason, we would see failures in the 3231 # Eastern vs Pacific tests too if we added 3*HOUR to the list of 3232 # offset deltas in convert_between_tz_and_utc(). 3233 # 3234 # self.convert_between_tz_and_utc(Eastern, Central) # can't work 3235 # self.convert_between_tz_and_utc(Central, Eastern) # can't work 3236 3237 def test_tricky(self): 3238 # 22:00 on day before daylight starts. 3239 fourback = self.dston - timedelta(hours=4) 3240 ninewest = FixedOffset(-9*60, "-0900", 0) 3241 fourback = fourback.replace(tzinfo=ninewest) 3242 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after 3243 # 2", we should get the 3 spelling. 3244 # If we plug 22:00 the day before into Eastern, it "looks like std 3245 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4 3246 # to 22:00 lands on 2:00, which makes no sense in local time (the 3247 # local clock jumps from 1 to 3). The point here is to make sure we 3248 # get the 3 spelling. 3249 expected = self.dston.replace(hour=3) 3250 got = fourback.astimezone(Eastern).replace(tzinfo=None) 3251 self.assertEqual(expected, got) 3252 3253 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that 3254 # case we want the 1:00 spelling. 3255 sixutc = self.dston.replace(hour=6, tzinfo=utc_real) 3256 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4, 3257 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST 3258 # spelling. 3259 expected = self.dston.replace(hour=1) 3260 got = sixutc.astimezone(Eastern).replace(tzinfo=None) 3261 self.assertEqual(expected, got) 3262 3263 # Now on the day DST ends, we want "repeat an hour" behavior. 3264 # UTC 4:MM 5:MM 6:MM 7:MM checking these 3265 # EST 23:MM 0:MM 1:MM 2:MM 3266 # EDT 0:MM 1:MM 2:MM 3:MM 3267 # wall 0:MM 1:MM 1:MM 2:MM against these 3268 for utc in utc_real, utc_fake: 3269 for tz in Eastern, Pacific: 3270 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM 3271 # Convert that to UTC. 3272 first_std_hour -= tz.utcoffset(None) 3273 # Adjust for possibly fake UTC. 3274 asutc = first_std_hour + utc.utcoffset(None) 3275 # First UTC hour to convert; this is 4:00 when utc=utc_real & 3276 # tz=Eastern. 3277 asutcbase = asutc.replace(tzinfo=utc) 3278 for tzhour in (0, 1, 1, 2): 3279 expectedbase = self.dstoff.replace(hour=tzhour) 3280 for minute in 0, 30, 59: 3281 expected = expectedbase.replace(minute=minute) 3282 asutc = asutcbase.replace(minute=minute) 3283 astz = asutc.astimezone(tz) 3284 self.assertEqual(astz.replace(tzinfo=None), expected) 3285 asutcbase += HOUR 3286 3287 3288 def test_bogus_dst(self): 3289 class ok(tzinfo): 3290 def utcoffset(self, dt): return HOUR 3291 def dst(self, dt): return HOUR 3292 3293 now = self.theclass.now().replace(tzinfo=utc_real) 3294 # Doesn't blow up. 3295 now.astimezone(ok()) 3296 3297 # Does blow up. 3298 class notok(ok): 3299 def dst(self, dt): return None 3300 self.assertRaises(ValueError, now.astimezone, notok()) 3301 3302 def test_fromutc(self): 3303 self.assertRaises(TypeError, Eastern.fromutc) # not enough args 3304 now = datetime.utcnow().replace(tzinfo=utc_real) 3305 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo 3306 now = now.replace(tzinfo=Eastern) # insert correct tzinfo 3307 enow = Eastern.fromutc(now) # doesn't blow up 3308 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member 3309 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args 3310 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type 3311 3312 # Always converts UTC to standard time. 3313 class FauxUSTimeZone(USTimeZone): 3314 def fromutc(self, dt): 3315 return dt + self.stdoffset 3316 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT") 3317 3318 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM 3319 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM 3320 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM 3321 3322 # Check around DST start. 3323 start = self.dston.replace(hour=4, tzinfo=Eastern) 3324 fstart = start.replace(tzinfo=FEastern) 3325 for wall in 23, 0, 1, 3, 4, 5: 3326 expected = start.replace(hour=wall) 3327 if wall == 23: 3328 expected -= timedelta(days=1) 3329 got = Eastern.fromutc(start) 3330 self.assertEqual(expected, got) 3331 3332 expected = fstart + FEastern.stdoffset 3333 got = FEastern.fromutc(fstart) 3334 self.assertEqual(expected, got) 3335 3336 # Ensure astimezone() calls fromutc() too. 3337 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern) 3338 self.assertEqual(expected, got) 3339 3340 start += HOUR 3341 fstart += HOUR 3342 3343 # Check around DST end. 3344 start = self.dstoff.replace(hour=4, tzinfo=Eastern) 3345 fstart = start.replace(tzinfo=FEastern) 3346 for wall in 0, 1, 1, 2, 3, 4: 3347 expected = start.replace(hour=wall) 3348 got = Eastern.fromutc(start) 3349 self.assertEqual(expected, got) 3350 3351 expected = fstart + FEastern.stdoffset 3352 got = FEastern.fromutc(fstart) 3353 self.assertEqual(expected, got) 3354 3355 # Ensure astimezone() calls fromutc() too. 3356 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern) 3357 self.assertEqual(expected, got) 3358 3359 start += HOUR 3360 fstart += HOUR 3361 3362 3363############################################################################# 3364# oddballs 3365 3366class Oddballs(unittest.TestCase): 3367 3368 def test_bug_1028306(self): 3369 # Trying to compare a date to a datetime should act like a mixed- 3370 # type comparison, despite that datetime is a subclass of date. 3371 as_date = date.today() 3372 as_datetime = datetime.combine(as_date, time()) 3373 self.assertTrue(as_date != as_datetime) 3374 self.assertTrue(as_datetime != as_date) 3375 self.assertFalse(as_date == as_datetime) 3376 self.assertFalse(as_datetime == as_date) 3377 self.assertRaises(TypeError, lambda: as_date < as_datetime) 3378 self.assertRaises(TypeError, lambda: as_datetime < as_date) 3379 self.assertRaises(TypeError, lambda: as_date <= as_datetime) 3380 self.assertRaises(TypeError, lambda: as_datetime <= as_date) 3381 self.assertRaises(TypeError, lambda: as_date > as_datetime) 3382 self.assertRaises(TypeError, lambda: as_datetime > as_date) 3383 self.assertRaises(TypeError, lambda: as_date >= as_datetime) 3384 self.assertRaises(TypeError, lambda: as_datetime >= as_date) 3385 3386 # Nevertheless, comparison should work with the base-class (date) 3387 # projection if use of a date method is forced. 3388 self.assertTrue(as_date.__eq__(as_datetime)) 3389 different_day = (as_date.day + 1) % 20 + 1 3390 self.assertFalse(as_date.__eq__(as_datetime.replace(day=different_day))) 3391 3392 # And date should compare with other subclasses of date. If a 3393 # subclass wants to stop this, it's up to the subclass to do so. 3394 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day) 3395 self.assertEqual(as_date, date_sc) 3396 self.assertEqual(date_sc, as_date) 3397 3398 # Ditto for datetimes. 3399 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month, 3400 as_date.day, 0, 0, 0) 3401 self.assertEqual(as_datetime, datetime_sc) 3402 self.assertEqual(datetime_sc, as_datetime) 3403 3404def test_main(): 3405 test_support.run_unittest(__name__) 3406 3407if __name__ == "__main__": 3408 test_main() 3409